interact.js 216 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977
  1. /**
  2. * interact.js v1.2.6
  3. *
  4. * Copyright (c) 2012-2015 Taye Adeyemi <dev@taye.me>
  5. * Open source under the MIT License.
  6. * https://raw.github.com/taye/interact.js/master/LICENSE
  7. */
  8. (function (realWindow) {
  9. 'use strict';
  10. // return early if there's no window to work with (eg. Node.js)
  11. if (!realWindow) { return; }
  12. var // get wrapped window if using Shadow DOM polyfill
  13. window = (function () {
  14. // create a TextNode
  15. var el = realWindow.document.createTextNode('');
  16. // check if it's wrapped by a polyfill
  17. if (el.ownerDocument !== realWindow.document
  18. && typeof realWindow.wrap === 'function'
  19. && realWindow.wrap(el) === el) {
  20. // return wrapped window
  21. return realWindow.wrap(realWindow);
  22. }
  23. // no Shadow DOM polyfil or native implementation
  24. return realWindow;
  25. }()),
  26. document = window.document,
  27. DocumentFragment = window.DocumentFragment || blank,
  28. SVGElement = window.SVGElement || blank,
  29. SVGSVGElement = window.SVGSVGElement || blank,
  30. SVGElementInstance = window.SVGElementInstance || blank,
  31. HTMLElement = window.HTMLElement || window.Element,
  32. PointerEvent = (window.PointerEvent || window.MSPointerEvent),
  33. pEventTypes,
  34. hypot = Math.hypot || function (x, y) { return Math.sqrt(x * x + y * y); },
  35. tmpXY = {}, // reduce object creation in getXY()
  36. documents = [], // all documents being listened to
  37. interactables = [], // all set interactables
  38. interactions = [], // all interactions
  39. dynamicDrop = false,
  40. // {
  41. // type: {
  42. // selectors: ['selector', ...],
  43. // contexts : [document, ...],
  44. // listeners: [[listener, useCapture], ...]
  45. // }
  46. // }
  47. delegatedEvents = {},
  48. defaultOptions = {
  49. base: {
  50. accept : null,
  51. actionChecker : null,
  52. styleCursor : true,
  53. preventDefault: 'auto',
  54. origin : { x: 0, y: 0 },
  55. deltaSource : 'page',
  56. allowFrom : null,
  57. ignoreFrom : null,
  58. _context : document,
  59. dropChecker : null
  60. },
  61. drag: {
  62. enabled: false,
  63. manualStart: true,
  64. max: Infinity,
  65. maxPerElement: 1,
  66. snap: null,
  67. restrict: null,
  68. inertia: null,
  69. autoScroll: null,
  70. axis: 'xy'
  71. },
  72. drop: {
  73. enabled: false,
  74. accept: null,
  75. overlap: 'pointer'
  76. },
  77. resize: {
  78. enabled: false,
  79. manualStart: false,
  80. max: Infinity,
  81. maxPerElement: 1,
  82. snap: null,
  83. restrict: null,
  84. inertia: null,
  85. autoScroll: null,
  86. square: false,
  87. preserveAspectRatio: false,
  88. axis: 'xy',
  89. // use default margin
  90. margin: NaN,
  91. // object with props left, right, top, bottom which are
  92. // true/false values to resize when the pointer is over that edge,
  93. // CSS selectors to match the handles for each direction
  94. // or the Elements for each handle
  95. edges: null,
  96. // a value of 'none' will limit the resize rect to a minimum of 0x0
  97. // 'negate' will alow the rect to have negative width/height
  98. // 'reposition' will keep the width/height positive by swapping
  99. // the top and bottom edges and/or swapping the left and right edges
  100. invert: 'none'
  101. },
  102. gesture: {
  103. manualStart: false,
  104. enabled: false,
  105. max: Infinity,
  106. maxPerElement: 1,
  107. restrict: null
  108. },
  109. perAction: {
  110. manualStart: false,
  111. max: Infinity,
  112. maxPerElement: 1,
  113. snap: {
  114. enabled : false,
  115. endOnly : false,
  116. range : Infinity,
  117. targets : null,
  118. offsets : null,
  119. relativePoints: null
  120. },
  121. restrict: {
  122. enabled: false,
  123. endOnly: false
  124. },
  125. autoScroll: {
  126. enabled : false,
  127. container : null, // the item that is scrolled (Window or HTMLElement)
  128. margin : 60,
  129. speed : 300 // the scroll speed in pixels per second
  130. },
  131. inertia: {
  132. enabled : false,
  133. resistance : 10, // the lambda in exponential decay
  134. minSpeed : 100, // target speed must be above this for inertia to start
  135. endSpeed : 10, // the speed at which inertia is slow enough to stop
  136. allowResume : true, // allow resuming an action in inertia phase
  137. zeroResumeDelta : true, // if an action is resumed after launch, set dx/dy to 0
  138. smoothEndDuration: 300 // animate to snap/restrict endOnly if there's no inertia
  139. }
  140. },
  141. _holdDuration: 600
  142. },
  143. // Things related to autoScroll
  144. autoScroll = {
  145. interaction: null,
  146. i: null, // the handle returned by window.setInterval
  147. x: 0, y: 0, // Direction each pulse is to scroll in
  148. // scroll the window by the values in scroll.x/y
  149. scroll: function () {
  150. var options = autoScroll.interaction.target.options[autoScroll.interaction.prepared.name].autoScroll,
  151. container = options.container || getWindow(autoScroll.interaction.element),
  152. now = new Date().getTime(),
  153. // change in time in seconds
  154. dtx = (now - autoScroll.prevTimeX) / 1000,
  155. dty = (now - autoScroll.prevTimeY) / 1000,
  156. vx, vy, sx, sy;
  157. // displacement
  158. if (options.velocity) {
  159. vx = options.velocity.x;
  160. vy = options.velocity.y;
  161. }
  162. else {
  163. vx = vy = options.speed
  164. }
  165. sx = vx * dtx;
  166. sy = vy * dty;
  167. if (sx >= 1 || sy >= 1) {
  168. if (isWindow(container)) {
  169. container.scrollBy(autoScroll.x * sx, autoScroll.y * sy);
  170. }
  171. else if (container) {
  172. container.scrollLeft += autoScroll.x * sx;
  173. container.scrollTop += autoScroll.y * sy;
  174. }
  175. if (sx >=1) autoScroll.prevTimeX = now;
  176. if (sy >= 1) autoScroll.prevTimeY = now;
  177. }
  178. if (autoScroll.isScrolling) {
  179. cancelFrame(autoScroll.i);
  180. autoScroll.i = reqFrame(autoScroll.scroll);
  181. }
  182. },
  183. isScrolling: false,
  184. prevTimeX: 0,
  185. prevTimeY: 0,
  186. start: function (interaction) {
  187. autoScroll.isScrolling = true;
  188. cancelFrame(autoScroll.i);
  189. autoScroll.interaction = interaction;
  190. autoScroll.prevTimeX = new Date().getTime();
  191. autoScroll.prevTimeY = new Date().getTime();
  192. autoScroll.i = reqFrame(autoScroll.scroll);
  193. },
  194. stop: function () {
  195. autoScroll.isScrolling = false;
  196. cancelFrame(autoScroll.i);
  197. }
  198. },
  199. // Does the browser support touch input?
  200. supportsTouch = (('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch),
  201. // Does the browser support PointerEvents
  202. supportsPointerEvent = !!PointerEvent,
  203. // Less Precision with touch input
  204. margin = supportsTouch || supportsPointerEvent? 20: 10,
  205. pointerMoveTolerance = 1,
  206. // for ignoring browser's simulated mouse events
  207. prevTouchTime = 0,
  208. // Allow this many interactions to happen simultaneously
  209. maxInteractions = Infinity,
  210. // Check if is IE9 or older
  211. actionCursors = (document.all && !window.atob) ? {
  212. drag : 'move',
  213. resizex : 'e-resize',
  214. resizey : 's-resize',
  215. resizexy: 'se-resize',
  216. resizetop : 'n-resize',
  217. resizeleft : 'w-resize',
  218. resizebottom : 's-resize',
  219. resizeright : 'e-resize',
  220. resizetopleft : 'se-resize',
  221. resizebottomright: 'se-resize',
  222. resizetopright : 'ne-resize',
  223. resizebottomleft : 'ne-resize',
  224. gesture : ''
  225. } : {
  226. drag : 'move',
  227. resizex : 'ew-resize',
  228. resizey : 'ns-resize',
  229. resizexy: 'nwse-resize',
  230. resizetop : 'ns-resize',
  231. resizeleft : 'ew-resize',
  232. resizebottom : 'ns-resize',
  233. resizeright : 'ew-resize',
  234. resizetopleft : 'nwse-resize',
  235. resizebottomright: 'nwse-resize',
  236. resizetopright : 'nesw-resize',
  237. resizebottomleft : 'nesw-resize',
  238. gesture : ''
  239. },
  240. actionIsEnabled = {
  241. drag : true,
  242. resize : true,
  243. gesture: true
  244. },
  245. // because Webkit and Opera still use 'mousewheel' event type
  246. wheelEvent = 'onmousewheel' in document? 'mousewheel': 'wheel',
  247. eventTypes = [
  248. 'dragstart',
  249. 'dragmove',
  250. 'draginertiastart',
  251. 'dragend',
  252. 'dragenter',
  253. 'dragleave',
  254. 'dropactivate',
  255. 'dropdeactivate',
  256. 'dropmove',
  257. 'drop',
  258. 'resizestart',
  259. 'resizemove',
  260. 'resizeinertiastart',
  261. 'resizeend',
  262. 'gesturestart',
  263. 'gesturemove',
  264. 'gestureinertiastart',
  265. 'gestureend',
  266. 'down',
  267. 'move',
  268. 'up',
  269. 'cancel',
  270. 'tap',
  271. 'doubletap',
  272. 'hold'
  273. ],
  274. globalEvents = {},
  275. // Opera Mobile must be handled differently
  276. isOperaMobile = navigator.appName == 'Opera' &&
  277. supportsTouch &&
  278. navigator.userAgent.match('Presto'),
  279. // scrolling doesn't change the result of getClientRects on iOS 7
  280. isIOS7 = (/iP(hone|od|ad)/.test(navigator.platform)
  281. && /OS 7[^\d]/.test(navigator.appVersion)),
  282. // prefix matchesSelector
  283. prefixedMatchesSelector = 'matches' in Element.prototype?
  284. 'matches': 'webkitMatchesSelector' in Element.prototype?
  285. 'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype?
  286. 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype?
  287. 'oMatchesSelector': 'msMatchesSelector',
  288. // will be polyfill function if browser is IE8
  289. ie8MatchesSelector,
  290. // native requestAnimationFrame or polyfill
  291. reqFrame = realWindow.requestAnimationFrame,
  292. cancelFrame = realWindow.cancelAnimationFrame,
  293. // Events wrapper
  294. events = (function () {
  295. var useAttachEvent = ('attachEvent' in window) && !('addEventListener' in window),
  296. addEvent = useAttachEvent? 'attachEvent': 'addEventListener',
  297. removeEvent = useAttachEvent? 'detachEvent': 'removeEventListener',
  298. on = useAttachEvent? 'on': '',
  299. elements = [],
  300. targets = [],
  301. attachedListeners = [];
  302. function add (element, type, listener, useCapture) {
  303. var elementIndex = indexOf(elements, element),
  304. target = targets[elementIndex];
  305. if (!target) {
  306. target = {
  307. events: {},
  308. typeCount: 0
  309. };
  310. elementIndex = elements.push(element) - 1;
  311. targets.push(target);
  312. attachedListeners.push((useAttachEvent ? {
  313. supplied: [],
  314. wrapped : [],
  315. useCount: []
  316. } : null));
  317. }
  318. if (!target.events[type]) {
  319. target.events[type] = [];
  320. target.typeCount++;
  321. }
  322. if (!contains(target.events[type], listener)) {
  323. var ret;
  324. if (useAttachEvent) {
  325. var listeners = attachedListeners[elementIndex],
  326. listenerIndex = indexOf(listeners.supplied, listener);
  327. var wrapped = listeners.wrapped[listenerIndex] || function (event) {
  328. if (!event.immediatePropagationStopped) {
  329. event.target = event.srcElement;
  330. event.currentTarget = element;
  331. event.preventDefault = event.preventDefault || preventDef;
  332. event.stopPropagation = event.stopPropagation || stopProp;
  333. event.stopImmediatePropagation = event.stopImmediatePropagation || stopImmProp;
  334. if (/mouse|click/.test(event.type)) {
  335. event.pageX = event.clientX + getWindow(element).document.documentElement.scrollLeft;
  336. event.pageY = event.clientY + getWindow(element).document.documentElement.scrollTop;
  337. }
  338. listener(event);
  339. }
  340. };
  341. ret = element[addEvent](on + type, wrapped, Boolean(useCapture));
  342. if (listenerIndex === -1) {
  343. listeners.supplied.push(listener);
  344. listeners.wrapped.push(wrapped);
  345. listeners.useCount.push(1);
  346. }
  347. else {
  348. listeners.useCount[listenerIndex]++;
  349. }
  350. }
  351. else {
  352. ret = element[addEvent](type, listener, useCapture || false);
  353. }
  354. target.events[type].push(listener);
  355. return ret;
  356. }
  357. }
  358. function remove (element, type, listener, useCapture) {
  359. var i,
  360. elementIndex = indexOf(elements, element),
  361. target = targets[elementIndex],
  362. listeners,
  363. listenerIndex,
  364. wrapped = listener;
  365. if (!target || !target.events) {
  366. return;
  367. }
  368. if (useAttachEvent) {
  369. listeners = attachedListeners[elementIndex];
  370. listenerIndex = indexOf(listeners.supplied, listener);
  371. wrapped = listeners.wrapped[listenerIndex];
  372. }
  373. if (type === 'all') {
  374. for (type in target.events) {
  375. if (target.events.hasOwnProperty(type)) {
  376. remove(element, type, 'all');
  377. }
  378. }
  379. return;
  380. }
  381. if (target.events[type]) {
  382. var len = target.events[type].length;
  383. if (listener === 'all') {
  384. for (i = 0; i < len; i++) {
  385. remove(element, type, target.events[type][i], Boolean(useCapture));
  386. }
  387. return;
  388. } else {
  389. for (i = 0; i < len; i++) {
  390. if (target.events[type][i] === listener) {
  391. element[removeEvent](on + type, wrapped, useCapture || false);
  392. target.events[type].splice(i, 1);
  393. if (useAttachEvent && listeners) {
  394. listeners.useCount[listenerIndex]--;
  395. if (listeners.useCount[listenerIndex] === 0) {
  396. listeners.supplied.splice(listenerIndex, 1);
  397. listeners.wrapped.splice(listenerIndex, 1);
  398. listeners.useCount.splice(listenerIndex, 1);
  399. }
  400. }
  401. break;
  402. }
  403. }
  404. }
  405. if (target.events[type] && target.events[type].length === 0) {
  406. target.events[type] = null;
  407. target.typeCount--;
  408. }
  409. }
  410. if (!target.typeCount) {
  411. targets.splice(elementIndex, 1);
  412. elements.splice(elementIndex, 1);
  413. attachedListeners.splice(elementIndex, 1);
  414. }
  415. }
  416. function preventDef () {
  417. this.returnValue = false;
  418. }
  419. function stopProp () {
  420. this.cancelBubble = true;
  421. }
  422. function stopImmProp () {
  423. this.cancelBubble = true;
  424. this.immediatePropagationStopped = true;
  425. }
  426. return {
  427. add: add,
  428. remove: remove,
  429. useAttachEvent: useAttachEvent,
  430. _elements: elements,
  431. _targets: targets,
  432. _attachedListeners: attachedListeners
  433. };
  434. }());
  435. function blank () {}
  436. function isElement (o) {
  437. if (!o || (typeof o !== 'object')) { return false; }
  438. var _window = getWindow(o) || window;
  439. return (/object|function/.test(typeof _window.Element)
  440. ? o instanceof _window.Element //DOM2
  441. : o.nodeType === 1 && typeof o.nodeName === "string");
  442. }
  443. function isWindow (thing) { return thing === window || !!(thing && thing.Window) && (thing instanceof thing.Window); }
  444. function isDocFrag (thing) { return !!thing && thing instanceof DocumentFragment; }
  445. function isArray (thing) {
  446. return isObject(thing)
  447. && (typeof thing.length !== undefined)
  448. && isFunction(thing.splice);
  449. }
  450. function isObject (thing) { return !!thing && (typeof thing === 'object'); }
  451. function isFunction (thing) { return typeof thing === 'function'; }
  452. function isNumber (thing) { return typeof thing === 'number' ; }
  453. function isBool (thing) { return typeof thing === 'boolean' ; }
  454. function isString (thing) { return typeof thing === 'string' ; }
  455. function trySelector (value) {
  456. if (!isString(value)) { return false; }
  457. // an exception will be raised if it is invalid
  458. document.querySelector(value);
  459. return true;
  460. }
  461. function extend (dest, source) {
  462. for (var prop in source) {
  463. dest[prop] = source[prop];
  464. }
  465. return dest;
  466. }
  467. var prefixedPropREs = {
  468. webkit: /(Movement[XY]|Radius[XY]|RotationAngle|Force)$/
  469. };
  470. function pointerExtend (dest, source) {
  471. for (var prop in source) {
  472. var deprecated = false;
  473. // skip deprecated prefixed properties
  474. for (var vendor in prefixedPropREs) {
  475. if (prop.indexOf(vendor) === 0 && prefixedPropREs[vendor].test(prop)) {
  476. deprecated = true;
  477. break;
  478. }
  479. }
  480. if (!deprecated) {
  481. dest[prop] = source[prop];
  482. }
  483. }
  484. return dest;
  485. }
  486. function copyCoords (dest, src) {
  487. dest.page = dest.page || {};
  488. dest.page.x = src.page.x;
  489. dest.page.y = src.page.y;
  490. dest.client = dest.client || {};
  491. dest.client.x = src.client.x;
  492. dest.client.y = src.client.y;
  493. dest.timeStamp = src.timeStamp;
  494. }
  495. function setEventXY (targetObj, pointers, interaction) {
  496. var pointer = (pointers.length > 1
  497. ? pointerAverage(pointers)
  498. : pointers[0]);
  499. getPageXY(pointer, tmpXY, interaction);
  500. targetObj.page.x = tmpXY.x;
  501. targetObj.page.y = tmpXY.y;
  502. getClientXY(pointer, tmpXY, interaction);
  503. targetObj.client.x = tmpXY.x;
  504. targetObj.client.y = tmpXY.y;
  505. targetObj.timeStamp = new Date().getTime();
  506. }
  507. function setEventDeltas (targetObj, prev, cur) {
  508. targetObj.page.x = cur.page.x - prev.page.x;
  509. targetObj.page.y = cur.page.y - prev.page.y;
  510. targetObj.client.x = cur.client.x - prev.client.x;
  511. targetObj.client.y = cur.client.y - prev.client.y;
  512. targetObj.timeStamp = new Date().getTime() - prev.timeStamp;
  513. // set pointer velocity
  514. var dt = Math.max(targetObj.timeStamp / 1000, 0.001);
  515. targetObj.page.speed = hypot(targetObj.page.x, targetObj.page.y) / dt;
  516. targetObj.page.vx = targetObj.page.x / dt;
  517. targetObj.page.vy = targetObj.page.y / dt;
  518. targetObj.client.speed = hypot(targetObj.client.x, targetObj.page.y) / dt;
  519. targetObj.client.vx = targetObj.client.x / dt;
  520. targetObj.client.vy = targetObj.client.y / dt;
  521. }
  522. function isNativePointer (pointer) {
  523. return (pointer instanceof window.Event
  524. || (supportsTouch && window.Touch && pointer instanceof window.Touch));
  525. }
  526. // Get specified X/Y coords for mouse or event.touches[0]
  527. function getXY (type, pointer, xy) {
  528. xy = xy || {};
  529. type = type || 'page';
  530. xy.x = pointer[type + 'X'];
  531. xy.y = pointer[type + 'Y'];
  532. return xy;
  533. }
  534. function getPageXY (pointer, page) {
  535. page = page || {};
  536. // Opera Mobile handles the viewport and scrolling oddly
  537. if (isOperaMobile && isNativePointer(pointer)) {
  538. getXY('screen', pointer, page);
  539. page.x += window.scrollX;
  540. page.y += window.scrollY;
  541. }
  542. else {
  543. getXY('page', pointer, page);
  544. }
  545. return page;
  546. }
  547. function getClientXY (pointer, client) {
  548. client = client || {};
  549. if (isOperaMobile && isNativePointer(pointer)) {
  550. // Opera Mobile handles the viewport and scrolling oddly
  551. getXY('screen', pointer, client);
  552. }
  553. else {
  554. getXY('client', pointer, client);
  555. }
  556. return client;
  557. }
  558. function getScrollXY (win) {
  559. win = win || window;
  560. return {
  561. x: win.scrollX || win.document.documentElement.scrollLeft,
  562. y: win.scrollY || win.document.documentElement.scrollTop
  563. };
  564. }
  565. function getPointerId (pointer) {
  566. return isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier;
  567. }
  568. function getActualElement (element) {
  569. return (element instanceof SVGElementInstance
  570. ? element.correspondingUseElement
  571. : element);
  572. }
  573. function getWindow (node) {
  574. if (isWindow(node)) {
  575. return node;
  576. }
  577. var rootNode = (node.ownerDocument || node);
  578. return rootNode.defaultView || rootNode.parentWindow || window;
  579. }
  580. function getElementClientRect (element) {
  581. var clientRect = (element instanceof SVGElement
  582. ? element.getBoundingClientRect()
  583. : element.getClientRects()[0]);
  584. return clientRect && {
  585. left : clientRect.left,
  586. right : clientRect.right,
  587. top : clientRect.top,
  588. bottom: clientRect.bottom,
  589. width : clientRect.width || clientRect.right - clientRect.left,
  590. height: clientRect.height || clientRect.bottom - clientRect.top
  591. };
  592. }
  593. function getElementRect (element) {
  594. var clientRect = getElementClientRect(element);
  595. if (!isIOS7 && clientRect) {
  596. var scroll = getScrollXY(getWindow(element));
  597. clientRect.left += scroll.x;
  598. clientRect.right += scroll.x;
  599. clientRect.top += scroll.y;
  600. clientRect.bottom += scroll.y;
  601. }
  602. return clientRect;
  603. }
  604. function getTouchPair (event) {
  605. var touches = [];
  606. // array of touches is supplied
  607. if (isArray(event)) {
  608. touches[0] = event[0];
  609. touches[1] = event[1];
  610. }
  611. // an event
  612. else {
  613. if (event.type === 'touchend') {
  614. if (event.touches.length === 1) {
  615. touches[0] = event.touches[0];
  616. touches[1] = event.changedTouches[0];
  617. }
  618. else if (event.touches.length === 0) {
  619. touches[0] = event.changedTouches[0];
  620. touches[1] = event.changedTouches[1];
  621. }
  622. }
  623. else {
  624. touches[0] = event.touches[0];
  625. touches[1] = event.touches[1];
  626. }
  627. }
  628. return touches;
  629. }
  630. function pointerAverage (pointers) {
  631. var average = {
  632. pageX : 0,
  633. pageY : 0,
  634. clientX: 0,
  635. clientY: 0,
  636. screenX: 0,
  637. screenY: 0
  638. };
  639. var prop;
  640. for (var i = 0; i < pointers.length; i++) {
  641. for (prop in average) {
  642. average[prop] += pointers[i][prop];
  643. }
  644. }
  645. for (prop in average) {
  646. average[prop] /= pointers.length;
  647. }
  648. return average;
  649. }
  650. function touchBBox (event) {
  651. if (!event.length && !(event.touches && event.touches.length > 1)) {
  652. return;
  653. }
  654. var touches = getTouchPair(event),
  655. minX = Math.min(touches[0].pageX, touches[1].pageX),
  656. minY = Math.min(touches[0].pageY, touches[1].pageY),
  657. maxX = Math.max(touches[0].pageX, touches[1].pageX),
  658. maxY = Math.max(touches[0].pageY, touches[1].pageY);
  659. return {
  660. x: minX,
  661. y: minY,
  662. left: minX,
  663. top: minY,
  664. width: maxX - minX,
  665. height: maxY - minY
  666. };
  667. }
  668. function touchDistance (event, deltaSource) {
  669. deltaSource = deltaSource || defaultOptions.deltaSource;
  670. var sourceX = deltaSource + 'X',
  671. sourceY = deltaSource + 'Y',
  672. touches = getTouchPair(event);
  673. var dx = touches[0][sourceX] - touches[1][sourceX],
  674. dy = touches[0][sourceY] - touches[1][sourceY];
  675. return hypot(dx, dy);
  676. }
  677. function touchAngle (event, prevAngle, deltaSource) {
  678. deltaSource = deltaSource || defaultOptions.deltaSource;
  679. var sourceX = deltaSource + 'X',
  680. sourceY = deltaSource + 'Y',
  681. touches = getTouchPair(event),
  682. dx = touches[0][sourceX] - touches[1][sourceX],
  683. dy = touches[0][sourceY] - touches[1][sourceY],
  684. angle = 180 * Math.atan(dy / dx) / Math.PI;
  685. if (isNumber(prevAngle)) {
  686. var dr = angle - prevAngle,
  687. drClamped = dr % 360;
  688. if (drClamped > 315) {
  689. angle -= 360 + (angle / 360)|0 * 360;
  690. }
  691. else if (drClamped > 135) {
  692. angle -= 180 + (angle / 360)|0 * 360;
  693. }
  694. else if (drClamped < -315) {
  695. angle += 360 + (angle / 360)|0 * 360;
  696. }
  697. else if (drClamped < -135) {
  698. angle += 180 + (angle / 360)|0 * 360;
  699. }
  700. }
  701. return angle;
  702. }
  703. function getOriginXY (interactable, element) {
  704. var origin = interactable
  705. ? interactable.options.origin
  706. : defaultOptions.origin;
  707. if (origin === 'parent') {
  708. origin = parentElement(element);
  709. }
  710. else if (origin === 'self') {
  711. origin = interactable.getRect(element);
  712. }
  713. else if (trySelector(origin)) {
  714. origin = closest(element, origin) || { x: 0, y: 0 };
  715. }
  716. if (isFunction(origin)) {
  717. origin = origin(interactable && element);
  718. }
  719. if (isElement(origin)) {
  720. origin = getElementRect(origin);
  721. }
  722. origin.x = ('x' in origin)? origin.x : origin.left;
  723. origin.y = ('y' in origin)? origin.y : origin.top;
  724. return origin;
  725. }
  726. // http://stackoverflow.com/a/5634528/2280888
  727. function _getQBezierValue(t, p1, p2, p3) {
  728. var iT = 1 - t;
  729. return iT * iT * p1 + 2 * iT * t * p2 + t * t * p3;
  730. }
  731. function getQuadraticCurvePoint(startX, startY, cpX, cpY, endX, endY, position) {
  732. return {
  733. x: _getQBezierValue(position, startX, cpX, endX),
  734. y: _getQBezierValue(position, startY, cpY, endY)
  735. };
  736. }
  737. // http://gizma.com/easing/
  738. function easeOutQuad (t, b, c, d) {
  739. t /= d;
  740. return -c * t*(t-2) + b;
  741. }
  742. function nodeContains (parent, child) {
  743. while (child) {
  744. if (child === parent) {
  745. return true;
  746. }
  747. child = child.parentNode;
  748. }
  749. return false;
  750. }
  751. function closest (child, selector) {
  752. var parent = parentElement(child);
  753. while (isElement(parent)) {
  754. if (matchesSelector(parent, selector)) { return parent; }
  755. parent = parentElement(parent);
  756. }
  757. return null;
  758. }
  759. function parentElement (node) {
  760. var parent = node.parentNode;
  761. if (isDocFrag(parent)) {
  762. // skip past #shado-root fragments
  763. while ((parent = parent.host) && isDocFrag(parent)) {}
  764. return parent;
  765. }
  766. return parent;
  767. }
  768. function inContext (interactable, element) {
  769. return interactable._context === element.ownerDocument
  770. || nodeContains(interactable._context, element);
  771. }
  772. function testIgnore (interactable, interactableElement, element) {
  773. var ignoreFrom = interactable.options.ignoreFrom;
  774. if (!ignoreFrom || !isElement(element)) { return false; }
  775. if (isString(ignoreFrom)) {
  776. return matchesUpTo(element, ignoreFrom, interactableElement);
  777. }
  778. else if (isElement(ignoreFrom)) {
  779. return nodeContains(ignoreFrom, element);
  780. }
  781. return false;
  782. }
  783. function testAllow (interactable, interactableElement, element) {
  784. var allowFrom = interactable.options.allowFrom;
  785. if (!allowFrom) { return true; }
  786. if (!isElement(element)) { return false; }
  787. if (isString(allowFrom)) {
  788. return matchesUpTo(element, allowFrom, interactableElement);
  789. }
  790. else if (isElement(allowFrom)) {
  791. return nodeContains(allowFrom, element);
  792. }
  793. return false;
  794. }
  795. function checkAxis (axis, interactable) {
  796. if (!interactable) { return false; }
  797. var thisAxis = interactable.options.drag.axis;
  798. return (axis === 'xy' || thisAxis === 'xy' || thisAxis === axis);
  799. }
  800. function checkSnap (interactable, action) {
  801. var options = interactable.options;
  802. if (/^resize/.test(action)) {
  803. action = 'resize';
  804. }
  805. return options[action].snap && options[action].snap.enabled;
  806. }
  807. function checkRestrict (interactable, action) {
  808. var options = interactable.options;
  809. if (/^resize/.test(action)) {
  810. action = 'resize';
  811. }
  812. return options[action].restrict && options[action].restrict.enabled;
  813. }
  814. function checkAutoScroll (interactable, action) {
  815. var options = interactable.options;
  816. if (/^resize/.test(action)) {
  817. action = 'resize';
  818. }
  819. return options[action].autoScroll && options[action].autoScroll.enabled;
  820. }
  821. function withinInteractionLimit (interactable, element, action) {
  822. var options = interactable.options,
  823. maxActions = options[action.name].max,
  824. maxPerElement = options[action.name].maxPerElement,
  825. activeInteractions = 0,
  826. targetCount = 0,
  827. targetElementCount = 0;
  828. for (var i = 0, len = interactions.length; i < len; i++) {
  829. var interaction = interactions[i],
  830. otherAction = interaction.prepared.name,
  831. active = interaction.interacting();
  832. if (!active) { continue; }
  833. activeInteractions++;
  834. if (activeInteractions >= maxInteractions) {
  835. return false;
  836. }
  837. if (interaction.target !== interactable) { continue; }
  838. targetCount += (otherAction === action.name)|0;
  839. if (targetCount >= maxActions) {
  840. return false;
  841. }
  842. if (interaction.element === element) {
  843. targetElementCount++;
  844. if (otherAction !== action.name || targetElementCount >= maxPerElement) {
  845. return false;
  846. }
  847. }
  848. }
  849. return maxInteractions > 0;
  850. }
  851. // Test for the element that's "above" all other qualifiers
  852. function indexOfDeepestElement (elements) {
  853. var dropzone,
  854. deepestZone = elements[0],
  855. index = deepestZone? 0: -1,
  856. parent,
  857. deepestZoneParents = [],
  858. dropzoneParents = [],
  859. child,
  860. i,
  861. n;
  862. for (i = 1; i < elements.length; i++) {
  863. dropzone = elements[i];
  864. // an element might belong to multiple selector dropzones
  865. if (!dropzone || dropzone === deepestZone) {
  866. continue;
  867. }
  868. if (!deepestZone) {
  869. deepestZone = dropzone;
  870. index = i;
  871. continue;
  872. }
  873. // check if the deepest or current are document.documentElement or document.rootElement
  874. // - if the current dropzone is, do nothing and continue
  875. if (dropzone.parentNode === dropzone.ownerDocument) {
  876. continue;
  877. }
  878. // - if deepest is, update with the current dropzone and continue to next
  879. else if (deepestZone.parentNode === dropzone.ownerDocument) {
  880. deepestZone = dropzone;
  881. index = i;
  882. continue;
  883. }
  884. if (!deepestZoneParents.length) {
  885. parent = deepestZone;
  886. while (parent.parentNode && parent.parentNode !== parent.ownerDocument) {
  887. deepestZoneParents.unshift(parent);
  888. parent = parent.parentNode;
  889. }
  890. }
  891. // if this element is an svg element and the current deepest is
  892. // an HTMLElement
  893. if (deepestZone instanceof HTMLElement
  894. && dropzone instanceof SVGElement
  895. && !(dropzone instanceof SVGSVGElement)) {
  896. if (dropzone === deepestZone.parentNode) {
  897. continue;
  898. }
  899. parent = dropzone.ownerSVGElement;
  900. }
  901. else {
  902. parent = dropzone;
  903. }
  904. dropzoneParents = [];
  905. while (parent.parentNode !== parent.ownerDocument) {
  906. dropzoneParents.unshift(parent);
  907. parent = parent.parentNode;
  908. }
  909. n = 0;
  910. // get (position of last common ancestor) + 1
  911. while (dropzoneParents[n] && dropzoneParents[n] === deepestZoneParents[n]) {
  912. n++;
  913. }
  914. var parents = [
  915. dropzoneParents[n - 1],
  916. dropzoneParents[n],
  917. deepestZoneParents[n]
  918. ];
  919. child = parents[0].lastChild;
  920. while (child) {
  921. if (child === parents[1]) {
  922. deepestZone = dropzone;
  923. index = i;
  924. deepestZoneParents = [];
  925. break;
  926. }
  927. else if (child === parents[2]) {
  928. break;
  929. }
  930. child = child.previousSibling;
  931. }
  932. }
  933. return index;
  934. }
  935. function Interaction () {
  936. this.target = null; // current interactable being interacted with
  937. this.element = null; // the target element of the interactable
  938. this.dropTarget = null; // the dropzone a drag target might be dropped into
  939. this.dropElement = null; // the element at the time of checking
  940. this.prevDropTarget = null; // the dropzone that was recently dragged away from
  941. this.prevDropElement = null; // the element at the time of checking
  942. this.prepared = { // action that's ready to be fired on next move event
  943. name : null,
  944. axis : null,
  945. edges: null
  946. };
  947. this.matches = []; // all selectors that are matched by target element
  948. this.matchElements = []; // corresponding elements
  949. this.inertiaStatus = {
  950. active : false,
  951. smoothEnd : false,
  952. ending : false,
  953. startEvent: null,
  954. upCoords: {},
  955. xe: 0, ye: 0,
  956. sx: 0, sy: 0,
  957. t0: 0,
  958. vx0: 0, vys: 0,
  959. duration: 0,
  960. resumeDx: 0,
  961. resumeDy: 0,
  962. lambda_v0: 0,
  963. one_ve_v0: 0,
  964. i : null
  965. };
  966. if (isFunction(Function.prototype.bind)) {
  967. this.boundInertiaFrame = this.inertiaFrame.bind(this);
  968. this.boundSmoothEndFrame = this.smoothEndFrame.bind(this);
  969. }
  970. else {
  971. var that = this;
  972. this.boundInertiaFrame = function () { return that.inertiaFrame(); };
  973. this.boundSmoothEndFrame = function () { return that.smoothEndFrame(); };
  974. }
  975. this.activeDrops = {
  976. dropzones: [], // the dropzones that are mentioned below
  977. elements : [], // elements of dropzones that accept the target draggable
  978. rects : [] // the rects of the elements mentioned above
  979. };
  980. // keep track of added pointers
  981. this.pointers = [];
  982. this.pointerIds = [];
  983. this.downTargets = [];
  984. this.downTimes = [];
  985. this.holdTimers = [];
  986. // Previous native pointer move event coordinates
  987. this.prevCoords = {
  988. page : { x: 0, y: 0 },
  989. client : { x: 0, y: 0 },
  990. timeStamp: 0
  991. };
  992. // current native pointer move event coordinates
  993. this.curCoords = {
  994. page : { x: 0, y: 0 },
  995. client : { x: 0, y: 0 },
  996. timeStamp: 0
  997. };
  998. // Starting InteractEvent pointer coordinates
  999. this.startCoords = {
  1000. page : { x: 0, y: 0 },
  1001. client : { x: 0, y: 0 },
  1002. timeStamp: 0
  1003. };
  1004. // Change in coordinates and time of the pointer
  1005. this.pointerDelta = {
  1006. page : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 },
  1007. client : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 },
  1008. timeStamp: 0
  1009. };
  1010. this.downEvent = null; // pointerdown/mousedown/touchstart event
  1011. this.downPointer = {};
  1012. this._eventTarget = null;
  1013. this._curEventTarget = null;
  1014. this.prevEvent = null; // previous action event
  1015. this.tapTime = 0; // time of the most recent tap event
  1016. this.prevTap = null;
  1017. this.startOffset = { left: 0, right: 0, top: 0, bottom: 0 };
  1018. this.restrictOffset = { left: 0, right: 0, top: 0, bottom: 0 };
  1019. this.snapOffsets = [];
  1020. this.gesture = {
  1021. start: { x: 0, y: 0 },
  1022. startDistance: 0, // distance between two touches of touchStart
  1023. prevDistance : 0,
  1024. distance : 0,
  1025. scale: 1, // gesture.distance / gesture.startDistance
  1026. startAngle: 0, // angle of line joining two touches
  1027. prevAngle : 0 // angle of the previous gesture event
  1028. };
  1029. this.snapStatus = {
  1030. x : 0, y : 0,
  1031. dx : 0, dy : 0,
  1032. realX : 0, realY : 0,
  1033. snappedX: 0, snappedY: 0,
  1034. targets : [],
  1035. locked : false,
  1036. changed : false
  1037. };
  1038. this.restrictStatus = {
  1039. dx : 0, dy : 0,
  1040. restrictedX: 0, restrictedY: 0,
  1041. snap : null,
  1042. restricted : false,
  1043. changed : false
  1044. };
  1045. this.restrictStatus.snap = this.snapStatus;
  1046. this.pointerIsDown = false;
  1047. this.pointerWasMoved = false;
  1048. this.gesturing = false;
  1049. this.dragging = false;
  1050. this.resizing = false;
  1051. this.resizeAxes = 'xy';
  1052. this.mouse = false;
  1053. interactions.push(this);
  1054. }
  1055. Interaction.prototype = {
  1056. getPageXY : function (pointer, xy) { return getPageXY(pointer, xy, this); },
  1057. getClientXY: function (pointer, xy) { return getClientXY(pointer, xy, this); },
  1058. setEventXY : function (target, ptr) { return setEventXY(target, ptr, this); },
  1059. pointerOver: function (pointer, event, eventTarget) {
  1060. if (this.prepared.name || !this.mouse) { return; }
  1061. var curMatches = [],
  1062. curMatchElements = [],
  1063. prevTargetElement = this.element;
  1064. this.addPointer(pointer);
  1065. if (this.target
  1066. && (testIgnore(this.target, this.element, eventTarget)
  1067. || !testAllow(this.target, this.element, eventTarget))) {
  1068. // if the eventTarget should be ignored or shouldn't be allowed
  1069. // clear the previous target
  1070. this.target = null;
  1071. this.element = null;
  1072. this.matches = [];
  1073. this.matchElements = [];
  1074. }
  1075. var elementInteractable = interactables.get(eventTarget),
  1076. elementAction = (elementInteractable
  1077. && !testIgnore(elementInteractable, eventTarget, eventTarget)
  1078. && testAllow(elementInteractable, eventTarget, eventTarget)
  1079. && validateAction(
  1080. elementInteractable.getAction(pointer, event, this, eventTarget),
  1081. elementInteractable));
  1082. if (elementAction && !withinInteractionLimit(elementInteractable, eventTarget, elementAction)) {
  1083. elementAction = null;
  1084. }
  1085. function pushCurMatches (interactable, selector) {
  1086. if (interactable
  1087. && inContext(interactable, eventTarget)
  1088. && !testIgnore(interactable, eventTarget, eventTarget)
  1089. && testAllow(interactable, eventTarget, eventTarget)
  1090. && matchesSelector(eventTarget, selector)) {
  1091. curMatches.push(interactable);
  1092. curMatchElements.push(eventTarget);
  1093. }
  1094. }
  1095. if (elementAction) {
  1096. this.target = elementInteractable;
  1097. this.element = eventTarget;
  1098. this.matches = [];
  1099. this.matchElements = [];
  1100. }
  1101. else {
  1102. interactables.forEachSelector(pushCurMatches);
  1103. if (this.validateSelector(pointer, event, curMatches, curMatchElements)) {
  1104. this.matches = curMatches;
  1105. this.matchElements = curMatchElements;
  1106. this.pointerHover(pointer, event, this.matches, this.matchElements);
  1107. events.add(eventTarget,
  1108. PointerEvent? pEventTypes.move : 'mousemove',
  1109. listeners.pointerHover);
  1110. }
  1111. else if (this.target) {
  1112. if (nodeContains(prevTargetElement, eventTarget)) {
  1113. this.pointerHover(pointer, event, this.matches, this.matchElements);
  1114. events.add(this.element,
  1115. PointerEvent? pEventTypes.move : 'mousemove',
  1116. listeners.pointerHover);
  1117. }
  1118. else {
  1119. this.target = null;
  1120. this.element = null;
  1121. this.matches = [];
  1122. this.matchElements = [];
  1123. }
  1124. }
  1125. }
  1126. },
  1127. // Check what action would be performed on pointerMove target if a mouse
  1128. // button were pressed and change the cursor accordingly
  1129. pointerHover: function (pointer, event, eventTarget, curEventTarget, matches, matchElements) {
  1130. var target = this.target;
  1131. if (!this.prepared.name && this.mouse) {
  1132. var action;
  1133. // update pointer coords for defaultActionChecker to use
  1134. this.setEventXY(this.curCoords, [pointer]);
  1135. if (matches) {
  1136. action = this.validateSelector(pointer, event, matches, matchElements);
  1137. }
  1138. else if (target) {
  1139. action = validateAction(target.getAction(this.pointers[0], event, this, this.element), this.target);
  1140. }
  1141. if (target && target.options.styleCursor) {
  1142. if (action) {
  1143. target._doc.documentElement.style.cursor = getActionCursor(action);
  1144. }
  1145. else {
  1146. target._doc.documentElement.style.cursor = '';
  1147. }
  1148. }
  1149. }
  1150. else if (this.prepared.name) {
  1151. this.checkAndPreventDefault(event, target, this.element);
  1152. }
  1153. },
  1154. pointerOut: function (pointer, event, eventTarget) {
  1155. if (this.prepared.name) { return; }
  1156. // Remove temporary event listeners for selector Interactables
  1157. if (!interactables.get(eventTarget)) {
  1158. events.remove(eventTarget,
  1159. PointerEvent? pEventTypes.move : 'mousemove',
  1160. listeners.pointerHover);
  1161. }
  1162. if (this.target && this.target.options.styleCursor && !this.interacting()) {
  1163. this.target._doc.documentElement.style.cursor = '';
  1164. }
  1165. },
  1166. selectorDown: function (pointer, event, eventTarget, curEventTarget) {
  1167. var that = this,
  1168. // copy event to be used in timeout for IE8
  1169. eventCopy = events.useAttachEvent? extend({}, event) : event,
  1170. element = eventTarget,
  1171. pointerIndex = this.addPointer(pointer),
  1172. action;
  1173. this.holdTimers[pointerIndex] = setTimeout(function () {
  1174. that.pointerHold(events.useAttachEvent? eventCopy : pointer, eventCopy, eventTarget, curEventTarget);
  1175. }, defaultOptions._holdDuration);
  1176. this.pointerIsDown = true;
  1177. // Check if the down event hits the current inertia target
  1178. if (this.inertiaStatus.active && this.target.selector) {
  1179. // climb up the DOM tree from the event target
  1180. while (isElement(element)) {
  1181. // if this element is the current inertia target element
  1182. if (element === this.element
  1183. // and the prospective action is the same as the ongoing one
  1184. && validateAction(this.target.getAction(pointer, event, this, this.element), this.target).name === this.prepared.name) {
  1185. // stop inertia so that the next move will be a normal one
  1186. cancelFrame(this.inertiaStatus.i);
  1187. this.inertiaStatus.active = false;
  1188. this.collectEventTargets(pointer, event, eventTarget, 'down');
  1189. return;
  1190. }
  1191. element = parentElement(element);
  1192. }
  1193. }
  1194. // do nothing if interacting
  1195. if (this.interacting()) {
  1196. this.collectEventTargets(pointer, event, eventTarget, 'down');
  1197. return;
  1198. }
  1199. function pushMatches (interactable, selector, context) {
  1200. var elements = ie8MatchesSelector
  1201. ? context.querySelectorAll(selector)
  1202. : undefined;
  1203. if (inContext(interactable, element)
  1204. && !testIgnore(interactable, element, eventTarget)
  1205. && testAllow(interactable, element, eventTarget)
  1206. && matchesSelector(element, selector, elements)) {
  1207. that.matches.push(interactable);
  1208. that.matchElements.push(element);
  1209. }
  1210. }
  1211. // update pointer coords for defaultActionChecker to use
  1212. this.setEventXY(this.curCoords, [pointer]);
  1213. this.downEvent = event;
  1214. while (isElement(element) && !action) {
  1215. this.matches = [];
  1216. this.matchElements = [];
  1217. interactables.forEachSelector(pushMatches);
  1218. action = this.validateSelector(pointer, event, this.matches, this.matchElements);
  1219. element = parentElement(element);
  1220. }
  1221. if (action) {
  1222. this.prepared.name = action.name;
  1223. this.prepared.axis = action.axis;
  1224. this.prepared.edges = action.edges;
  1225. this.collectEventTargets(pointer, event, eventTarget, 'down');
  1226. return this.pointerDown(pointer, event, eventTarget, curEventTarget, action);
  1227. }
  1228. else {
  1229. // do these now since pointerDown isn't being called from here
  1230. this.downTimes[pointerIndex] = new Date().getTime();
  1231. this.downTargets[pointerIndex] = eventTarget;
  1232. pointerExtend(this.downPointer, pointer);
  1233. copyCoords(this.prevCoords, this.curCoords);
  1234. this.pointerWasMoved = false;
  1235. }
  1236. this.collectEventTargets(pointer, event, eventTarget, 'down');
  1237. },
  1238. // Determine action to be performed on next pointerMove and add appropriate
  1239. // style and event Listeners
  1240. pointerDown: function (pointer, event, eventTarget, curEventTarget, forceAction) {
  1241. if (!forceAction && !this.inertiaStatus.active && this.pointerWasMoved && this.prepared.name) {
  1242. this.checkAndPreventDefault(event, this.target, this.element);
  1243. return;
  1244. }
  1245. this.pointerIsDown = true;
  1246. this.downEvent = event;
  1247. var pointerIndex = this.addPointer(pointer),
  1248. action;
  1249. // If it is the second touch of a multi-touch gesture, keep the
  1250. // target the same and get a new action if a target was set by the
  1251. // first touch
  1252. if (this.pointerIds.length > 1 && this.target._element === this.element) {
  1253. var newAction = validateAction(forceAction || this.target.getAction(pointer, event, this, this.element), this.target);
  1254. if (withinInteractionLimit(this.target, this.element, newAction)) {
  1255. action = newAction;
  1256. }
  1257. this.prepared.name = null;
  1258. }
  1259. // Otherwise, set the target if there is no action prepared
  1260. else if (!this.prepared.name) {
  1261. var interactable = interactables.get(curEventTarget);
  1262. if (interactable
  1263. && !testIgnore(interactable, curEventTarget, eventTarget)
  1264. && testAllow(interactable, curEventTarget, eventTarget)
  1265. && (action = validateAction(forceAction || interactable.getAction(pointer, event, this, curEventTarget), interactable, eventTarget))
  1266. && withinInteractionLimit(interactable, curEventTarget, action)) {
  1267. this.target = interactable;
  1268. this.element = curEventTarget;
  1269. }
  1270. }
  1271. var target = this.target,
  1272. options = target && target.options;
  1273. if (target && (forceAction || !this.prepared.name)) {
  1274. action = action || validateAction(forceAction || target.getAction(pointer, event, this, curEventTarget), target, this.element);
  1275. this.setEventXY(this.startCoords, this.pointers);
  1276. if (!action) { return; }
  1277. if (options.styleCursor) {
  1278. target._doc.documentElement.style.cursor = getActionCursor(action);
  1279. }
  1280. this.resizeAxes = action.name === 'resize'? action.axis : null;
  1281. if (action === 'gesture' && this.pointerIds.length < 2) {
  1282. action = null;
  1283. }
  1284. this.prepared.name = action.name;
  1285. this.prepared.axis = action.axis;
  1286. this.prepared.edges = action.edges;
  1287. this.snapStatus.snappedX = this.snapStatus.snappedY =
  1288. this.restrictStatus.restrictedX = this.restrictStatus.restrictedY = NaN;
  1289. this.downTimes[pointerIndex] = new Date().getTime();
  1290. this.downTargets[pointerIndex] = eventTarget;
  1291. pointerExtend(this.downPointer, pointer);
  1292. copyCoords(this.prevCoords, this.startCoords);
  1293. this.pointerWasMoved = false;
  1294. this.checkAndPreventDefault(event, target, this.element);
  1295. }
  1296. // if inertia is active try to resume action
  1297. else if (this.inertiaStatus.active
  1298. && curEventTarget === this.element
  1299. && validateAction(target.getAction(pointer, event, this, this.element), target).name === this.prepared.name) {
  1300. cancelFrame(this.inertiaStatus.i);
  1301. this.inertiaStatus.active = false;
  1302. this.checkAndPreventDefault(event, target, this.element);
  1303. }
  1304. },
  1305. setModifications: function (coords, preEnd) {
  1306. var target = this.target,
  1307. shouldMove = true,
  1308. shouldSnap = checkSnap(target, this.prepared.name) && (!target.options[this.prepared.name].snap.endOnly || preEnd),
  1309. shouldRestrict = checkRestrict(target, this.prepared.name) && (!target.options[this.prepared.name].restrict.endOnly || preEnd);
  1310. if (shouldSnap ) { this.setSnapping (coords); } else { this.snapStatus .locked = false; }
  1311. if (shouldRestrict) { this.setRestriction(coords); } else { this.restrictStatus.restricted = false; }
  1312. if (shouldSnap && this.snapStatus.locked && !this.snapStatus.changed) {
  1313. shouldMove = shouldRestrict && this.restrictStatus.restricted && this.restrictStatus.changed;
  1314. }
  1315. else if (shouldRestrict && this.restrictStatus.restricted && !this.restrictStatus.changed) {
  1316. shouldMove = false;
  1317. }
  1318. return shouldMove;
  1319. },
  1320. setStartOffsets: function (action, interactable, element) {
  1321. var rect = interactable.getRect(element),
  1322. origin = getOriginXY(interactable, element),
  1323. snap = interactable.options[this.prepared.name].snap,
  1324. restrict = interactable.options[this.prepared.name].restrict,
  1325. width, height;
  1326. if (rect) {
  1327. this.startOffset.left = this.startCoords.page.x - rect.left;
  1328. this.startOffset.top = this.startCoords.page.y - rect.top;
  1329. this.startOffset.right = rect.right - this.startCoords.page.x;
  1330. this.startOffset.bottom = rect.bottom - this.startCoords.page.y;
  1331. if ('width' in rect) { width = rect.width; }
  1332. else { width = rect.right - rect.left; }
  1333. if ('height' in rect) { height = rect.height; }
  1334. else { height = rect.bottom - rect.top; }
  1335. }
  1336. else {
  1337. this.startOffset.left = this.startOffset.top = this.startOffset.right = this.startOffset.bottom = 0;
  1338. }
  1339. this.snapOffsets.splice(0);
  1340. var snapOffset = snap && snap.offset === 'startCoords'
  1341. ? {
  1342. x: this.startCoords.page.x - origin.x,
  1343. y: this.startCoords.page.y - origin.y
  1344. }
  1345. : snap && snap.offset || { x: 0, y: 0 };
  1346. if (rect && snap && snap.relativePoints && snap.relativePoints.length) {
  1347. for (var i = 0; i < snap.relativePoints.length; i++) {
  1348. this.snapOffsets.push({
  1349. x: this.startOffset.left - (width * snap.relativePoints[i].x) + snapOffset.x,
  1350. y: this.startOffset.top - (height * snap.relativePoints[i].y) + snapOffset.y
  1351. });
  1352. }
  1353. }
  1354. else {
  1355. this.snapOffsets.push(snapOffset);
  1356. }
  1357. if (rect && restrict.elementRect) {
  1358. this.restrictOffset.left = this.startOffset.left - (width * restrict.elementRect.left);
  1359. this.restrictOffset.top = this.startOffset.top - (height * restrict.elementRect.top);
  1360. this.restrictOffset.right = this.startOffset.right - (width * (1 - restrict.elementRect.right));
  1361. this.restrictOffset.bottom = this.startOffset.bottom - (height * (1 - restrict.elementRect.bottom));
  1362. }
  1363. else {
  1364. this.restrictOffset.left = this.restrictOffset.top = this.restrictOffset.right = this.restrictOffset.bottom = 0;
  1365. }
  1366. },
  1367. /*\
  1368. * Interaction.start
  1369. [ method ]
  1370. *
  1371. * Start an action with the given Interactable and Element as tartgets. The
  1372. * action must be enabled for the target Interactable and an appropriate number
  1373. * of pointers must be held down – 1 for drag/resize, 2 for gesture.
  1374. *
  1375. * Use it with `interactable.<action>able({ manualStart: false })` to always
  1376. * [start actions manually](https://github.com/taye/interact.js/issues/114)
  1377. *
  1378. - action (object) The action to be performed - drag, resize, etc.
  1379. - interactable (Interactable) The Interactable to target
  1380. - element (Element) The DOM Element to target
  1381. = (object) interact
  1382. **
  1383. | interact(target)
  1384. | .draggable({
  1385. | // disable the default drag start by down->move
  1386. | manualStart: true
  1387. | })
  1388. | // start dragging after the user holds the pointer down
  1389. | .on('hold', function (event) {
  1390. | var interaction = event.interaction;
  1391. |
  1392. | if (!interaction.interacting()) {
  1393. | interaction.start({ name: 'drag' },
  1394. | event.interactable,
  1395. | event.currentTarget);
  1396. | }
  1397. | });
  1398. \*/
  1399. start: function (action, interactable, element) {
  1400. if (this.interacting()
  1401. || !this.pointerIsDown
  1402. || this.pointerIds.length < (action.name === 'gesture'? 2 : 1)) {
  1403. return;
  1404. }
  1405. // if this interaction had been removed after stopping
  1406. // add it back
  1407. if (indexOf(interactions, this) === -1) {
  1408. interactions.push(this);
  1409. }
  1410. // set the startCoords if there was no prepared action
  1411. if (!this.prepared.name) {
  1412. this.setEventXY(this.startCoords);
  1413. }
  1414. this.prepared.name = action.name;
  1415. this.prepared.axis = action.axis;
  1416. this.prepared.edges = action.edges;
  1417. this.target = interactable;
  1418. this.element = element;
  1419. this.setStartOffsets(action.name, interactable, element);
  1420. this.setModifications(this.startCoords.page);
  1421. this.prevEvent = this[this.prepared.name + 'Start'](this.downEvent);
  1422. },
  1423. pointerMove: function (pointer, event, eventTarget, curEventTarget, preEnd) {
  1424. if (this.inertiaStatus.active) {
  1425. var pageUp = this.inertiaStatus.upCoords.page;
  1426. var clientUp = this.inertiaStatus.upCoords.client;
  1427. var inertiaPosition = {
  1428. pageX : pageUp.x + this.inertiaStatus.sx,
  1429. pageY : pageUp.y + this.inertiaStatus.sy,
  1430. clientX: clientUp.x + this.inertiaStatus.sx,
  1431. clientY: clientUp.y + this.inertiaStatus.sy
  1432. };
  1433. this.setEventXY(this.curCoords, [inertiaPosition]);
  1434. }
  1435. else {
  1436. this.recordPointer(pointer);
  1437. this.setEventXY(this.curCoords, this.pointers);
  1438. }
  1439. var duplicateMove = (this.curCoords.page.x === this.prevCoords.page.x
  1440. && this.curCoords.page.y === this.prevCoords.page.y
  1441. && this.curCoords.client.x === this.prevCoords.client.x
  1442. && this.curCoords.client.y === this.prevCoords.client.y);
  1443. var dx, dy,
  1444. pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer));
  1445. // register movement greater than pointerMoveTolerance
  1446. if (this.pointerIsDown && !this.pointerWasMoved) {
  1447. dx = this.curCoords.client.x - this.startCoords.client.x;
  1448. dy = this.curCoords.client.y - this.startCoords.client.y;
  1449. this.pointerWasMoved = hypot(dx, dy) > pointerMoveTolerance;
  1450. }
  1451. if (!duplicateMove && (!this.pointerIsDown || this.pointerWasMoved)) {
  1452. if (this.pointerIsDown) {
  1453. clearTimeout(this.holdTimers[pointerIndex]);
  1454. }
  1455. this.collectEventTargets(pointer, event, eventTarget, 'move');
  1456. }
  1457. if (!this.pointerIsDown) { return; }
  1458. if (duplicateMove && this.pointerWasMoved && !preEnd) {
  1459. this.checkAndPreventDefault(event, this.target, this.element);
  1460. return;
  1461. }
  1462. // set pointer coordinate, time changes and speeds
  1463. setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords);
  1464. if (!this.prepared.name) { return; }
  1465. if (this.pointerWasMoved
  1466. // ignore movement while inertia is active
  1467. && (!this.inertiaStatus.active || (pointer instanceof InteractEvent && /inertiastart/.test(pointer.type)))) {
  1468. // if just starting an action, calculate the pointer speed now
  1469. if (!this.interacting()) {
  1470. setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords);
  1471. // check if a drag is in the correct axis
  1472. if (this.prepared.name === 'drag') {
  1473. var absX = Math.abs(dx),
  1474. absY = Math.abs(dy),
  1475. targetAxis = this.target.options.drag.axis,
  1476. axis = (absX > absY ? 'x' : absX < absY ? 'y' : 'xy');
  1477. // if the movement isn't in the axis of the interactable
  1478. if (axis !== 'xy' && targetAxis !== 'xy' && targetAxis !== axis) {
  1479. // cancel the prepared action
  1480. this.prepared.name = null;
  1481. // then try to get a drag from another ineractable
  1482. var element = eventTarget;
  1483. // check element interactables
  1484. while (isElement(element)) {
  1485. var elementInteractable = interactables.get(element);
  1486. if (elementInteractable
  1487. && elementInteractable !== this.target
  1488. && !elementInteractable.options.drag.manualStart
  1489. && elementInteractable.getAction(this.downPointer, this.downEvent, this, element).name === 'drag'
  1490. && checkAxis(axis, elementInteractable)) {
  1491. this.prepared.name = 'drag';
  1492. this.target = elementInteractable;
  1493. this.element = element;
  1494. break;
  1495. }
  1496. element = parentElement(element);
  1497. }
  1498. // if there's no drag from element interactables,
  1499. // check the selector interactables
  1500. if (!this.prepared.name) {
  1501. var thisInteraction = this;
  1502. var getDraggable = function (interactable, selector, context) {
  1503. var elements = ie8MatchesSelector
  1504. ? context.querySelectorAll(selector)
  1505. : undefined;
  1506. if (interactable === thisInteraction.target) { return; }
  1507. if (inContext(interactable, eventTarget)
  1508. && !interactable.options.drag.manualStart
  1509. && !testIgnore(interactable, element, eventTarget)
  1510. && testAllow(interactable, element, eventTarget)
  1511. && matchesSelector(element, selector, elements)
  1512. && interactable.getAction(thisInteraction.downPointer, thisInteraction.downEvent, thisInteraction, element).name === 'drag'
  1513. && checkAxis(axis, interactable)
  1514. && withinInteractionLimit(interactable, element, 'drag')) {
  1515. return interactable;
  1516. }
  1517. };
  1518. element = eventTarget;
  1519. while (isElement(element)) {
  1520. var selectorInteractable = interactables.forEachSelector(getDraggable);
  1521. if (selectorInteractable) {
  1522. this.prepared.name = 'drag';
  1523. this.target = selectorInteractable;
  1524. this.element = element;
  1525. break;
  1526. }
  1527. element = parentElement(element);
  1528. }
  1529. }
  1530. }
  1531. }
  1532. }
  1533. var starting = !!this.prepared.name && !this.interacting();
  1534. if (starting
  1535. && (this.target.options[this.prepared.name].manualStart
  1536. || !withinInteractionLimit(this.target, this.element, this.prepared))) {
  1537. this.stop(event);
  1538. return;
  1539. }
  1540. if (this.prepared.name && this.target) {
  1541. if (starting) {
  1542. this.start(this.prepared, this.target, this.element);
  1543. }
  1544. var shouldMove = this.setModifications(this.curCoords.page, preEnd);
  1545. // move if snapping or restriction doesn't prevent it
  1546. if (shouldMove || starting) {
  1547. this.prevEvent = this[this.prepared.name + 'Move'](event);
  1548. }
  1549. this.checkAndPreventDefault(event, this.target, this.element);
  1550. }
  1551. }
  1552. copyCoords(this.prevCoords, this.curCoords);
  1553. if (this.dragging || this.resizing) {
  1554. this.autoScrollMove(pointer);
  1555. }
  1556. },
  1557. dragStart: function (event) {
  1558. var dragEvent = new InteractEvent(this, event, 'drag', 'start', this.element);
  1559. this.dragging = true;
  1560. this.target.fire(dragEvent);
  1561. // reset active dropzones
  1562. this.activeDrops.dropzones = [];
  1563. this.activeDrops.elements = [];
  1564. this.activeDrops.rects = [];
  1565. if (!this.dynamicDrop) {
  1566. this.setActiveDrops(this.element);
  1567. }
  1568. var dropEvents = this.getDropEvents(event, dragEvent);
  1569. if (dropEvents.activate) {
  1570. this.fireActiveDrops(dropEvents.activate);
  1571. }
  1572. return dragEvent;
  1573. },
  1574. dragMove: function (event) {
  1575. var target = this.target,
  1576. dragEvent = new InteractEvent(this, event, 'drag', 'move', this.element),
  1577. draggableElement = this.element,
  1578. drop = this.getDrop(dragEvent, event, draggableElement);
  1579. this.dropTarget = drop.dropzone;
  1580. this.dropElement = drop.element;
  1581. var dropEvents = this.getDropEvents(event, dragEvent);
  1582. target.fire(dragEvent);
  1583. if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); }
  1584. if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); }
  1585. if (dropEvents.move ) { this.dropTarget.fire(dropEvents.move ); }
  1586. this.prevDropTarget = this.dropTarget;
  1587. this.prevDropElement = this.dropElement;
  1588. return dragEvent;
  1589. },
  1590. resizeStart: function (event) {
  1591. var resizeEvent = new InteractEvent(this, event, 'resize', 'start', this.element);
  1592. if (this.prepared.edges) {
  1593. var startRect = this.target.getRect(this.element);
  1594. /*
  1595. * When using the `resizable.square` or `resizable.preserveAspectRatio` options, resizing from one edge
  1596. * will affect another. E.g. with `resizable.square`, resizing to make the right edge larger will make
  1597. * the bottom edge larger by the same amount. We call these 'linked' edges. Any linked edges will depend
  1598. * on the active edges and the edge being interacted with.
  1599. */
  1600. if (this.target.options.resize.square || this.target.options.resize.preserveAspectRatio) {
  1601. var linkedEdges = extend({}, this.prepared.edges);
  1602. linkedEdges.top = linkedEdges.top || (linkedEdges.left && !linkedEdges.bottom);
  1603. linkedEdges.left = linkedEdges.left || (linkedEdges.top && !linkedEdges.right );
  1604. linkedEdges.bottom = linkedEdges.bottom || (linkedEdges.right && !linkedEdges.top );
  1605. linkedEdges.right = linkedEdges.right || (linkedEdges.bottom && !linkedEdges.left );
  1606. this.prepared._linkedEdges = linkedEdges;
  1607. }
  1608. else {
  1609. this.prepared._linkedEdges = null;
  1610. }
  1611. // if using `resizable.preserveAspectRatio` option, record aspect ratio at the start of the resize
  1612. if (this.target.options.resize.preserveAspectRatio) {
  1613. this.resizeStartAspectRatio = startRect.width / startRect.height;
  1614. }
  1615. this.resizeRects = {
  1616. start : startRect,
  1617. current : extend({}, startRect),
  1618. restricted: extend({}, startRect),
  1619. previous : extend({}, startRect),
  1620. delta : {
  1621. left: 0, right : 0, width : 0,
  1622. top : 0, bottom: 0, height: 0
  1623. }
  1624. };
  1625. resizeEvent.rect = this.resizeRects.restricted;
  1626. resizeEvent.deltaRect = this.resizeRects.delta;
  1627. }
  1628. this.target.fire(resizeEvent);
  1629. this.resizing = true;
  1630. return resizeEvent;
  1631. },
  1632. resizeMove: function (event) {
  1633. var resizeEvent = new InteractEvent(this, event, 'resize', 'move', this.element);
  1634. var edges = this.prepared.edges,
  1635. invert = this.target.options.resize.invert,
  1636. invertible = invert === 'reposition' || invert === 'negate';
  1637. if (edges) {
  1638. var dx = resizeEvent.dx,
  1639. dy = resizeEvent.dy,
  1640. start = this.resizeRects.start,
  1641. current = this.resizeRects.current,
  1642. restricted = this.resizeRects.restricted,
  1643. delta = this.resizeRects.delta,
  1644. previous = extend(this.resizeRects.previous, restricted),
  1645. originalEdges = edges;
  1646. // `resize.preserveAspectRatio` takes precedence over `resize.square`
  1647. if (this.target.options.resize.preserveAspectRatio) {
  1648. var resizeStartAspectRatio = this.resizeStartAspectRatio;
  1649. edges = this.prepared._linkedEdges;
  1650. if ((originalEdges.left && originalEdges.bottom)
  1651. || (originalEdges.right && originalEdges.top)) {
  1652. dy = -dx / resizeStartAspectRatio;
  1653. }
  1654. else if (originalEdges.left || originalEdges.right) { dy = dx / resizeStartAspectRatio; }
  1655. else if (originalEdges.top || originalEdges.bottom) { dx = dy * resizeStartAspectRatio; }
  1656. }
  1657. else if (this.target.options.resize.square) {
  1658. edges = this.prepared._linkedEdges;
  1659. if ((originalEdges.left && originalEdges.bottom)
  1660. || (originalEdges.right && originalEdges.top)) {
  1661. dy = -dx;
  1662. }
  1663. else if (originalEdges.left || originalEdges.right) { dy = dx; }
  1664. else if (originalEdges.top || originalEdges.bottom) { dx = dy; }
  1665. }
  1666. // update the 'current' rect without modifications
  1667. if (edges.top ) { current.top += dy; }
  1668. if (edges.bottom) { current.bottom += dy; }
  1669. if (edges.left ) { current.left += dx; }
  1670. if (edges.right ) { current.right += dx; }
  1671. if (invertible) {
  1672. // if invertible, copy the current rect
  1673. extend(restricted, current);
  1674. if (invert === 'reposition') {
  1675. // swap edge values if necessary to keep width/height positive
  1676. var swap;
  1677. if (restricted.top > restricted.bottom) {
  1678. swap = restricted.top;
  1679. restricted.top = restricted.bottom;
  1680. restricted.bottom = swap;
  1681. }
  1682. if (restricted.left > restricted.right) {
  1683. swap = restricted.left;
  1684. restricted.left = restricted.right;
  1685. restricted.right = swap;
  1686. }
  1687. }
  1688. }
  1689. else {
  1690. // if not invertible, restrict to minimum of 0x0 rect
  1691. restricted.top = Math.min(current.top, start.bottom);
  1692. restricted.bottom = Math.max(current.bottom, start.top);
  1693. restricted.left = Math.min(current.left, start.right);
  1694. restricted.right = Math.max(current.right, start.left);
  1695. }
  1696. restricted.width = restricted.right - restricted.left;
  1697. restricted.height = restricted.bottom - restricted.top ;
  1698. for (var edge in restricted) {
  1699. delta[edge] = restricted[edge] - previous[edge];
  1700. }
  1701. resizeEvent.edges = this.prepared.edges;
  1702. resizeEvent.rect = restricted;
  1703. resizeEvent.deltaRect = delta;
  1704. }
  1705. this.target.fire(resizeEvent);
  1706. return resizeEvent;
  1707. },
  1708. gestureStart: function (event) {
  1709. var gestureEvent = new InteractEvent(this, event, 'gesture', 'start', this.element);
  1710. gestureEvent.ds = 0;
  1711. this.gesture.startDistance = this.gesture.prevDistance = gestureEvent.distance;
  1712. this.gesture.startAngle = this.gesture.prevAngle = gestureEvent.angle;
  1713. this.gesture.scale = 1;
  1714. this.gesturing = true;
  1715. this.target.fire(gestureEvent);
  1716. return gestureEvent;
  1717. },
  1718. gestureMove: function (event) {
  1719. if (!this.pointerIds.length) {
  1720. return this.prevEvent;
  1721. }
  1722. var gestureEvent;
  1723. gestureEvent = new InteractEvent(this, event, 'gesture', 'move', this.element);
  1724. gestureEvent.ds = gestureEvent.scale - this.gesture.scale;
  1725. this.target.fire(gestureEvent);
  1726. this.gesture.prevAngle = gestureEvent.angle;
  1727. this.gesture.prevDistance = gestureEvent.distance;
  1728. if (gestureEvent.scale !== Infinity &&
  1729. gestureEvent.scale !== null &&
  1730. gestureEvent.scale !== undefined &&
  1731. !isNaN(gestureEvent.scale)) {
  1732. this.gesture.scale = gestureEvent.scale;
  1733. }
  1734. return gestureEvent;
  1735. },
  1736. pointerHold: function (pointer, event, eventTarget) {
  1737. this.collectEventTargets(pointer, event, eventTarget, 'hold');
  1738. },
  1739. pointerUp: function (pointer, event, eventTarget, curEventTarget) {
  1740. var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer));
  1741. clearTimeout(this.holdTimers[pointerIndex]);
  1742. this.collectEventTargets(pointer, event, eventTarget, 'up' );
  1743. this.collectEventTargets(pointer, event, eventTarget, 'tap');
  1744. this.pointerEnd(pointer, event, eventTarget, curEventTarget);
  1745. this.removePointer(pointer);
  1746. },
  1747. pointerCancel: function (pointer, event, eventTarget, curEventTarget) {
  1748. var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer));
  1749. clearTimeout(this.holdTimers[pointerIndex]);
  1750. this.collectEventTargets(pointer, event, eventTarget, 'cancel');
  1751. this.pointerEnd(pointer, event, eventTarget, curEventTarget);
  1752. this.removePointer(pointer);
  1753. },
  1754. // http://www.quirksmode.org/dom/events/click.html
  1755. // >Events leading to dblclick
  1756. //
  1757. // IE8 doesn't fire down event before dblclick.
  1758. // This workaround tries to fire a tap and doubletap after dblclick
  1759. ie8Dblclick: function (pointer, event, eventTarget) {
  1760. if (this.prevTap
  1761. && event.clientX === this.prevTap.clientX
  1762. && event.clientY === this.prevTap.clientY
  1763. && eventTarget === this.prevTap.target) {
  1764. this.downTargets[0] = eventTarget;
  1765. this.downTimes[0] = new Date().getTime();
  1766. this.collectEventTargets(pointer, event, eventTarget, 'tap');
  1767. }
  1768. },
  1769. // End interact move events and stop auto-scroll unless inertia is enabled
  1770. pointerEnd: function (pointer, event, eventTarget, curEventTarget) {
  1771. var endEvent,
  1772. target = this.target,
  1773. options = target && target.options,
  1774. inertiaOptions = options && this.prepared.name && options[this.prepared.name].inertia,
  1775. inertiaStatus = this.inertiaStatus;
  1776. if (this.interacting()) {
  1777. if (inertiaStatus.active && !inertiaStatus.ending) { return; }
  1778. var pointerSpeed,
  1779. now = new Date().getTime(),
  1780. inertiaPossible = false,
  1781. inertia = false,
  1782. smoothEnd = false,
  1783. endSnap = checkSnap(target, this.prepared.name) && options[this.prepared.name].snap.endOnly,
  1784. endRestrict = checkRestrict(target, this.prepared.name) && options[this.prepared.name].restrict.endOnly,
  1785. dx = 0,
  1786. dy = 0,
  1787. startEvent;
  1788. if (this.dragging) {
  1789. if (options.drag.axis === 'x' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vx); }
  1790. else if (options.drag.axis === 'y' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vy); }
  1791. else /*options.drag.axis === 'xy'*/{ pointerSpeed = this.pointerDelta.client.speed; }
  1792. }
  1793. else {
  1794. pointerSpeed = this.pointerDelta.client.speed;
  1795. }
  1796. // check if inertia should be started
  1797. inertiaPossible = (inertiaOptions && inertiaOptions.enabled
  1798. && this.prepared.name !== 'gesture'
  1799. && event !== inertiaStatus.startEvent);
  1800. inertia = (inertiaPossible
  1801. && (now - this.curCoords.timeStamp) < 50
  1802. && pointerSpeed > inertiaOptions.minSpeed
  1803. && pointerSpeed > inertiaOptions.endSpeed);
  1804. if (inertiaPossible && !inertia && (endSnap || endRestrict)) {
  1805. var snapRestrict = {};
  1806. snapRestrict.snap = snapRestrict.restrict = snapRestrict;
  1807. if (endSnap) {
  1808. this.setSnapping(this.curCoords.page, snapRestrict);
  1809. if (snapRestrict.locked) {
  1810. dx += snapRestrict.dx;
  1811. dy += snapRestrict.dy;
  1812. }
  1813. }
  1814. if (endRestrict) {
  1815. this.setRestriction(this.curCoords.page, snapRestrict);
  1816. if (snapRestrict.restricted) {
  1817. dx += snapRestrict.dx;
  1818. dy += snapRestrict.dy;
  1819. }
  1820. }
  1821. if (dx || dy) {
  1822. smoothEnd = true;
  1823. }
  1824. }
  1825. if (inertia || smoothEnd) {
  1826. copyCoords(inertiaStatus.upCoords, this.curCoords);
  1827. this.pointers[0] = inertiaStatus.startEvent = startEvent =
  1828. new InteractEvent(this, event, this.prepared.name, 'inertiastart', this.element);
  1829. inertiaStatus.t0 = now;
  1830. target.fire(inertiaStatus.startEvent);
  1831. if (inertia) {
  1832. inertiaStatus.vx0 = this.pointerDelta.client.vx;
  1833. inertiaStatus.vy0 = this.pointerDelta.client.vy;
  1834. inertiaStatus.v0 = pointerSpeed;
  1835. this.calcInertia(inertiaStatus);
  1836. var page = extend({}, this.curCoords.page),
  1837. origin = getOriginXY(target, this.element),
  1838. statusObject;
  1839. page.x = page.x + inertiaStatus.xe - origin.x;
  1840. page.y = page.y + inertiaStatus.ye - origin.y;
  1841. statusObject = {
  1842. useStatusXY: true,
  1843. x: page.x,
  1844. y: page.y,
  1845. dx: 0,
  1846. dy: 0,
  1847. snap: null
  1848. };
  1849. statusObject.snap = statusObject;
  1850. dx = dy = 0;
  1851. if (endSnap) {
  1852. var snap = this.setSnapping(this.curCoords.page, statusObject);
  1853. if (snap.locked) {
  1854. dx += snap.dx;
  1855. dy += snap.dy;
  1856. }
  1857. }
  1858. if (endRestrict) {
  1859. var restrict = this.setRestriction(this.curCoords.page, statusObject);
  1860. if (restrict.restricted) {
  1861. dx += restrict.dx;
  1862. dy += restrict.dy;
  1863. }
  1864. }
  1865. inertiaStatus.modifiedXe += dx;
  1866. inertiaStatus.modifiedYe += dy;
  1867. inertiaStatus.i = reqFrame(this.boundInertiaFrame);
  1868. }
  1869. else {
  1870. inertiaStatus.smoothEnd = true;
  1871. inertiaStatus.xe = dx;
  1872. inertiaStatus.ye = dy;
  1873. inertiaStatus.sx = inertiaStatus.sy = 0;
  1874. inertiaStatus.i = reqFrame(this.boundSmoothEndFrame);
  1875. }
  1876. inertiaStatus.active = true;
  1877. return;
  1878. }
  1879. if (endSnap || endRestrict) {
  1880. // fire a move event at the snapped coordinates
  1881. this.pointerMove(pointer, event, eventTarget, curEventTarget, true);
  1882. }
  1883. }
  1884. if (this.dragging) {
  1885. endEvent = new InteractEvent(this, event, 'drag', 'end', this.element);
  1886. var draggableElement = this.element,
  1887. drop = this.getDrop(endEvent, event, draggableElement);
  1888. this.dropTarget = drop.dropzone;
  1889. this.dropElement = drop.element;
  1890. var dropEvents = this.getDropEvents(event, endEvent);
  1891. if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); }
  1892. if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); }
  1893. if (dropEvents.drop ) { this.dropTarget.fire(dropEvents.drop ); }
  1894. if (dropEvents.deactivate) {
  1895. this.fireActiveDrops(dropEvents.deactivate);
  1896. }
  1897. target.fire(endEvent);
  1898. }
  1899. else if (this.resizing) {
  1900. endEvent = new InteractEvent(this, event, 'resize', 'end', this.element);
  1901. target.fire(endEvent);
  1902. }
  1903. else if (this.gesturing) {
  1904. endEvent = new InteractEvent(this, event, 'gesture', 'end', this.element);
  1905. target.fire(endEvent);
  1906. }
  1907. this.stop(event);
  1908. },
  1909. collectDrops: function (element) {
  1910. var drops = [],
  1911. elements = [],
  1912. i;
  1913. element = element || this.element;
  1914. // collect all dropzones and their elements which qualify for a drop
  1915. for (i = 0; i < interactables.length; i++) {
  1916. if (!interactables[i].options.drop.enabled) { continue; }
  1917. var current = interactables[i],
  1918. accept = current.options.drop.accept;
  1919. // test the draggable element against the dropzone's accept setting
  1920. if ((isElement(accept) && accept !== element)
  1921. || (isString(accept)
  1922. && !matchesSelector(element, accept))) {
  1923. continue;
  1924. }
  1925. // query for new elements if necessary
  1926. var dropElements = current.selector? current._context.querySelectorAll(current.selector) : [current._element];
  1927. for (var j = 0, len = dropElements.length; j < len; j++) {
  1928. var currentElement = dropElements[j];
  1929. if (currentElement === element) {
  1930. continue;
  1931. }
  1932. drops.push(current);
  1933. elements.push(currentElement);
  1934. }
  1935. }
  1936. return {
  1937. dropzones: drops,
  1938. elements: elements
  1939. };
  1940. },
  1941. fireActiveDrops: function (event) {
  1942. var i,
  1943. current,
  1944. currentElement,
  1945. prevElement;
  1946. // loop through all active dropzones and trigger event
  1947. for (i = 0; i < this.activeDrops.dropzones.length; i++) {
  1948. current = this.activeDrops.dropzones[i];
  1949. currentElement = this.activeDrops.elements [i];
  1950. // prevent trigger of duplicate events on same element
  1951. if (currentElement !== prevElement) {
  1952. // set current element as event target
  1953. event.target = currentElement;
  1954. current.fire(event);
  1955. }
  1956. prevElement = currentElement;
  1957. }
  1958. },
  1959. // Collect a new set of possible drops and save them in activeDrops.
  1960. // setActiveDrops should always be called when a drag has just started or a
  1961. // drag event happens while dynamicDrop is true
  1962. setActiveDrops: function (dragElement) {
  1963. // get dropzones and their elements that could receive the draggable
  1964. var possibleDrops = this.collectDrops(dragElement, true);
  1965. this.activeDrops.dropzones = possibleDrops.dropzones;
  1966. this.activeDrops.elements = possibleDrops.elements;
  1967. this.activeDrops.rects = [];
  1968. for (var i = 0; i < this.activeDrops.dropzones.length; i++) {
  1969. this.activeDrops.rects[i] = this.activeDrops.dropzones[i].getRect(this.activeDrops.elements[i]);
  1970. }
  1971. },
  1972. getDrop: function (dragEvent, event, dragElement) {
  1973. var validDrops = [];
  1974. if (dynamicDrop) {
  1975. this.setActiveDrops(dragElement);
  1976. }
  1977. // collect all dropzones and their elements which qualify for a drop
  1978. for (var j = 0; j < this.activeDrops.dropzones.length; j++) {
  1979. var current = this.activeDrops.dropzones[j],
  1980. currentElement = this.activeDrops.elements [j],
  1981. rect = this.activeDrops.rects [j];
  1982. validDrops.push(current.dropCheck(dragEvent, event, this.target, dragElement, currentElement, rect)
  1983. ? currentElement
  1984. : null);
  1985. }
  1986. // get the most appropriate dropzone based on DOM depth and order
  1987. var dropIndex = indexOfDeepestElement(validDrops),
  1988. dropzone = this.activeDrops.dropzones[dropIndex] || null,
  1989. element = this.activeDrops.elements [dropIndex] || null;
  1990. return {
  1991. dropzone: dropzone,
  1992. element: element
  1993. };
  1994. },
  1995. getDropEvents: function (pointerEvent, dragEvent) {
  1996. var dropEvents = {
  1997. enter : null,
  1998. leave : null,
  1999. activate : null,
  2000. deactivate: null,
  2001. move : null,
  2002. drop : null
  2003. };
  2004. if (this.dropElement !== this.prevDropElement) {
  2005. // if there was a prevDropTarget, create a dragleave event
  2006. if (this.prevDropTarget) {
  2007. dropEvents.leave = {
  2008. target : this.prevDropElement,
  2009. dropzone : this.prevDropTarget,
  2010. relatedTarget: dragEvent.target,
  2011. draggable : dragEvent.interactable,
  2012. dragEvent : dragEvent,
  2013. interaction : this,
  2014. timeStamp : dragEvent.timeStamp,
  2015. type : 'dragleave'
  2016. };
  2017. dragEvent.dragLeave = this.prevDropElement;
  2018. dragEvent.prevDropzone = this.prevDropTarget;
  2019. }
  2020. // if the dropTarget is not null, create a dragenter event
  2021. if (this.dropTarget) {
  2022. dropEvents.enter = {
  2023. target : this.dropElement,
  2024. dropzone : this.dropTarget,
  2025. relatedTarget: dragEvent.target,
  2026. draggable : dragEvent.interactable,
  2027. dragEvent : dragEvent,
  2028. interaction : this,
  2029. timeStamp : dragEvent.timeStamp,
  2030. type : 'dragenter'
  2031. };
  2032. dragEvent.dragEnter = this.dropElement;
  2033. dragEvent.dropzone = this.dropTarget;
  2034. }
  2035. }
  2036. if (dragEvent.type === 'dragend' && this.dropTarget) {
  2037. dropEvents.drop = {
  2038. target : this.dropElement,
  2039. dropzone : this.dropTarget,
  2040. relatedTarget: dragEvent.target,
  2041. draggable : dragEvent.interactable,
  2042. dragEvent : dragEvent,
  2043. interaction : this,
  2044. timeStamp : dragEvent.timeStamp,
  2045. type : 'drop'
  2046. };
  2047. dragEvent.dropzone = this.dropTarget;
  2048. }
  2049. if (dragEvent.type === 'dragstart') {
  2050. dropEvents.activate = {
  2051. target : null,
  2052. dropzone : null,
  2053. relatedTarget: dragEvent.target,
  2054. draggable : dragEvent.interactable,
  2055. dragEvent : dragEvent,
  2056. interaction : this,
  2057. timeStamp : dragEvent.timeStamp,
  2058. type : 'dropactivate'
  2059. };
  2060. }
  2061. if (dragEvent.type === 'dragend') {
  2062. dropEvents.deactivate = {
  2063. target : null,
  2064. dropzone : null,
  2065. relatedTarget: dragEvent.target,
  2066. draggable : dragEvent.interactable,
  2067. dragEvent : dragEvent,
  2068. interaction : this,
  2069. timeStamp : dragEvent.timeStamp,
  2070. type : 'dropdeactivate'
  2071. };
  2072. }
  2073. if (dragEvent.type === 'dragmove' && this.dropTarget) {
  2074. dropEvents.move = {
  2075. target : this.dropElement,
  2076. dropzone : this.dropTarget,
  2077. relatedTarget: dragEvent.target,
  2078. draggable : dragEvent.interactable,
  2079. dragEvent : dragEvent,
  2080. interaction : this,
  2081. dragmove : dragEvent,
  2082. timeStamp : dragEvent.timeStamp,
  2083. type : 'dropmove'
  2084. };
  2085. dragEvent.dropzone = this.dropTarget;
  2086. }
  2087. return dropEvents;
  2088. },
  2089. currentAction: function () {
  2090. return (this.dragging && 'drag') || (this.resizing && 'resize') || (this.gesturing && 'gesture') || null;
  2091. },
  2092. interacting: function () {
  2093. return this.dragging || this.resizing || this.gesturing;
  2094. },
  2095. clearTargets: function () {
  2096. this.target = this.element = null;
  2097. this.dropTarget = this.dropElement = this.prevDropTarget = this.prevDropElement = null;
  2098. },
  2099. stop: function (event) {
  2100. if (this.interacting()) {
  2101. autoScroll.stop();
  2102. this.matches = [];
  2103. this.matchElements = [];
  2104. var target = this.target;
  2105. if (target.options.styleCursor) {
  2106. target._doc.documentElement.style.cursor = '';
  2107. }
  2108. // prevent Default only if were previously interacting
  2109. if (event && isFunction(event.preventDefault)) {
  2110. this.checkAndPreventDefault(event, target, this.element);
  2111. }
  2112. if (this.dragging) {
  2113. this.activeDrops.dropzones = this.activeDrops.elements = this.activeDrops.rects = null;
  2114. }
  2115. }
  2116. this.clearTargets();
  2117. this.pointerIsDown = this.snapStatus.locked = this.dragging = this.resizing = this.gesturing = false;
  2118. this.prepared.name = this.prevEvent = null;
  2119. this.inertiaStatus.resumeDx = this.inertiaStatus.resumeDy = 0;
  2120. // remove pointers if their ID isn't in this.pointerIds
  2121. for (var i = 0; i < this.pointers.length; i++) {
  2122. if (indexOf(this.pointerIds, getPointerId(this.pointers[i])) === -1) {
  2123. this.pointers.splice(i, 1);
  2124. }
  2125. }
  2126. },
  2127. inertiaFrame: function () {
  2128. var inertiaStatus = this.inertiaStatus,
  2129. options = this.target.options[this.prepared.name].inertia,
  2130. lambda = options.resistance,
  2131. t = new Date().getTime() / 1000 - inertiaStatus.t0;
  2132. if (t < inertiaStatus.te) {
  2133. var progress = 1 - (Math.exp(-lambda * t) - inertiaStatus.lambda_v0) / inertiaStatus.one_ve_v0;
  2134. if (inertiaStatus.modifiedXe === inertiaStatus.xe && inertiaStatus.modifiedYe === inertiaStatus.ye) {
  2135. inertiaStatus.sx = inertiaStatus.xe * progress;
  2136. inertiaStatus.sy = inertiaStatus.ye * progress;
  2137. }
  2138. else {
  2139. var quadPoint = getQuadraticCurvePoint(
  2140. 0, 0,
  2141. inertiaStatus.xe, inertiaStatus.ye,
  2142. inertiaStatus.modifiedXe, inertiaStatus.modifiedYe,
  2143. progress);
  2144. inertiaStatus.sx = quadPoint.x;
  2145. inertiaStatus.sy = quadPoint.y;
  2146. }
  2147. this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);
  2148. inertiaStatus.i = reqFrame(this.boundInertiaFrame);
  2149. }
  2150. else {
  2151. inertiaStatus.ending = true;
  2152. inertiaStatus.sx = inertiaStatus.modifiedXe;
  2153. inertiaStatus.sy = inertiaStatus.modifiedYe;
  2154. this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);
  2155. this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent);
  2156. inertiaStatus.active = inertiaStatus.ending = false;
  2157. }
  2158. },
  2159. smoothEndFrame: function () {
  2160. var inertiaStatus = this.inertiaStatus,
  2161. t = new Date().getTime() - inertiaStatus.t0,
  2162. duration = this.target.options[this.prepared.name].inertia.smoothEndDuration;
  2163. if (t < duration) {
  2164. inertiaStatus.sx = easeOutQuad(t, 0, inertiaStatus.xe, duration);
  2165. inertiaStatus.sy = easeOutQuad(t, 0, inertiaStatus.ye, duration);
  2166. this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);
  2167. inertiaStatus.i = reqFrame(this.boundSmoothEndFrame);
  2168. }
  2169. else {
  2170. inertiaStatus.ending = true;
  2171. inertiaStatus.sx = inertiaStatus.xe;
  2172. inertiaStatus.sy = inertiaStatus.ye;
  2173. this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);
  2174. this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent);
  2175. inertiaStatus.smoothEnd =
  2176. inertiaStatus.active = inertiaStatus.ending = false;
  2177. }
  2178. },
  2179. addPointer: function (pointer) {
  2180. var id = getPointerId(pointer),
  2181. index = this.mouse? 0 : indexOf(this.pointerIds, id);
  2182. if (index === -1) {
  2183. index = this.pointerIds.length;
  2184. }
  2185. this.pointerIds[index] = id;
  2186. this.pointers[index] = pointer;
  2187. return index;
  2188. },
  2189. removePointer: function (pointer) {
  2190. var id = getPointerId(pointer),
  2191. index = this.mouse? 0 : indexOf(this.pointerIds, id);
  2192. if (index === -1) { return; }
  2193. this.pointers .splice(index, 1);
  2194. this.pointerIds .splice(index, 1);
  2195. this.downTargets.splice(index, 1);
  2196. this.downTimes .splice(index, 1);
  2197. this.holdTimers .splice(index, 1);
  2198. },
  2199. recordPointer: function (pointer) {
  2200. var index = this.mouse? 0: indexOf(this.pointerIds, getPointerId(pointer));
  2201. if (index === -1) { return; }
  2202. this.pointers[index] = pointer;
  2203. },
  2204. collectEventTargets: function (pointer, event, eventTarget, eventType) {
  2205. var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer));
  2206. // do not fire a tap event if the pointer was moved before being lifted
  2207. if (eventType === 'tap' && (this.pointerWasMoved
  2208. // or if the pointerup target is different to the pointerdown target
  2209. || !(this.downTargets[pointerIndex] && this.downTargets[pointerIndex] === eventTarget))) {
  2210. return;
  2211. }
  2212. var targets = [],
  2213. elements = [],
  2214. element = eventTarget;
  2215. function collectSelectors (interactable, selector, context) {
  2216. var els = ie8MatchesSelector
  2217. ? context.querySelectorAll(selector)
  2218. : undefined;
  2219. if (interactable._iEvents[eventType]
  2220. && isElement(element)
  2221. && inContext(interactable, element)
  2222. && !testIgnore(interactable, element, eventTarget)
  2223. && testAllow(interactable, element, eventTarget)
  2224. && matchesSelector(element, selector, els)) {
  2225. targets.push(interactable);
  2226. elements.push(element);
  2227. }
  2228. }
  2229. while (element) {
  2230. if (interact.isSet(element) && interact(element)._iEvents[eventType]) {
  2231. targets.push(interact(element));
  2232. elements.push(element);
  2233. }
  2234. interactables.forEachSelector(collectSelectors);
  2235. element = parentElement(element);
  2236. }
  2237. // create the tap event even if there are no listeners so that
  2238. // doubletap can still be created and fired
  2239. if (targets.length || eventType === 'tap') {
  2240. this.firePointers(pointer, event, eventTarget, targets, elements, eventType);
  2241. }
  2242. },
  2243. firePointers: function (pointer, event, eventTarget, targets, elements, eventType) {
  2244. var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer)),
  2245. pointerEvent = {},
  2246. i,
  2247. // for tap events
  2248. interval, createNewDoubleTap;
  2249. // if it's a doubletap then the event properties would have been
  2250. // copied from the tap event and provided as the pointer argument
  2251. if (eventType === 'doubletap') {
  2252. pointerEvent = pointer;
  2253. }
  2254. else {
  2255. pointerExtend(pointerEvent, event);
  2256. if (event !== pointer) {
  2257. pointerExtend(pointerEvent, pointer);
  2258. }
  2259. pointerEvent.preventDefault = preventOriginalDefault;
  2260. pointerEvent.stopPropagation = InteractEvent.prototype.stopPropagation;
  2261. pointerEvent.stopImmediatePropagation = InteractEvent.prototype.stopImmediatePropagation;
  2262. pointerEvent.interaction = this;
  2263. pointerEvent.timeStamp = new Date().getTime();
  2264. pointerEvent.originalEvent = event;
  2265. pointerEvent.originalPointer = pointer;
  2266. pointerEvent.type = eventType;
  2267. pointerEvent.pointerId = getPointerId(pointer);
  2268. pointerEvent.pointerType = this.mouse? 'mouse' : !supportsPointerEvent? 'touch'
  2269. : isString(pointer.pointerType)
  2270. ? pointer.pointerType
  2271. : [,,'touch', 'pen', 'mouse'][pointer.pointerType];
  2272. }
  2273. if (eventType === 'tap') {
  2274. pointerEvent.dt = pointerEvent.timeStamp - this.downTimes[pointerIndex];
  2275. interval = pointerEvent.timeStamp - this.tapTime;
  2276. createNewDoubleTap = !!(this.prevTap && this.prevTap.type !== 'doubletap'
  2277. && this.prevTap.target === pointerEvent.target
  2278. && interval < 500);
  2279. pointerEvent.double = createNewDoubleTap;
  2280. this.tapTime = pointerEvent.timeStamp;
  2281. }
  2282. for (i = 0; i < targets.length; i++) {
  2283. pointerEvent.currentTarget = elements[i];
  2284. pointerEvent.interactable = targets[i];
  2285. targets[i].fire(pointerEvent);
  2286. if (pointerEvent.immediatePropagationStopped
  2287. ||(pointerEvent.propagationStopped && elements[i + 1] !== pointerEvent.currentTarget)) {
  2288. break;
  2289. }
  2290. }
  2291. if (createNewDoubleTap) {
  2292. var doubleTap = {};
  2293. extend(doubleTap, pointerEvent);
  2294. doubleTap.dt = interval;
  2295. doubleTap.type = 'doubletap';
  2296. this.collectEventTargets(doubleTap, event, eventTarget, 'doubletap');
  2297. this.prevTap = doubleTap;
  2298. }
  2299. else if (eventType === 'tap') {
  2300. this.prevTap = pointerEvent;
  2301. }
  2302. },
  2303. validateSelector: function (pointer, event, matches, matchElements) {
  2304. for (var i = 0, len = matches.length; i < len; i++) {
  2305. var match = matches[i],
  2306. matchElement = matchElements[i],
  2307. action = validateAction(match.getAction(pointer, event, this, matchElement), match);
  2308. if (action && withinInteractionLimit(match, matchElement, action)) {
  2309. this.target = match;
  2310. this.element = matchElement;
  2311. return action;
  2312. }
  2313. }
  2314. },
  2315. setSnapping: function (pageCoords, status) {
  2316. var snap = this.target.options[this.prepared.name].snap,
  2317. targets = [],
  2318. target,
  2319. page,
  2320. i;
  2321. status = status || this.snapStatus;
  2322. if (status.useStatusXY) {
  2323. page = { x: status.x, y: status.y };
  2324. }
  2325. else {
  2326. var origin = getOriginXY(this.target, this.element);
  2327. page = extend({}, pageCoords);
  2328. page.x -= origin.x;
  2329. page.y -= origin.y;
  2330. }
  2331. status.realX = page.x;
  2332. status.realY = page.y;
  2333. page.x = page.x - this.inertiaStatus.resumeDx;
  2334. page.y = page.y - this.inertiaStatus.resumeDy;
  2335. var len = snap.targets? snap.targets.length : 0;
  2336. for (var relIndex = 0; relIndex < this.snapOffsets.length; relIndex++) {
  2337. var relative = {
  2338. x: page.x - this.snapOffsets[relIndex].x,
  2339. y: page.y - this.snapOffsets[relIndex].y
  2340. };
  2341. for (i = 0; i < len; i++) {
  2342. if (isFunction(snap.targets[i])) {
  2343. target = snap.targets[i](relative.x, relative.y, this);
  2344. }
  2345. else {
  2346. target = snap.targets[i];
  2347. }
  2348. if (!target) { continue; }
  2349. targets.push({
  2350. x: isNumber(target.x) ? (target.x + this.snapOffsets[relIndex].x) : relative.x,
  2351. y: isNumber(target.y) ? (target.y + this.snapOffsets[relIndex].y) : relative.y,
  2352. range: isNumber(target.range)? target.range: snap.range
  2353. });
  2354. }
  2355. }
  2356. var closest = {
  2357. target: null,
  2358. inRange: false,
  2359. distance: 0,
  2360. range: 0,
  2361. dx: 0,
  2362. dy: 0
  2363. };
  2364. for (i = 0, len = targets.length; i < len; i++) {
  2365. target = targets[i];
  2366. var range = target.range,
  2367. dx = target.x - page.x,
  2368. dy = target.y - page.y,
  2369. distance = hypot(dx, dy),
  2370. inRange = distance <= range;
  2371. // Infinite targets count as being out of range
  2372. // compared to non infinite ones that are in range
  2373. if (range === Infinity && closest.inRange && closest.range !== Infinity) {
  2374. inRange = false;
  2375. }
  2376. if (!closest.target || (inRange
  2377. // is the closest target in range?
  2378. ? (closest.inRange && range !== Infinity
  2379. // the pointer is relatively deeper in this target
  2380. ? distance / range < closest.distance / closest.range
  2381. // this target has Infinite range and the closest doesn't
  2382. : (range === Infinity && closest.range !== Infinity)
  2383. // OR this target is closer that the previous closest
  2384. || distance < closest.distance)
  2385. // The other is not in range and the pointer is closer to this target
  2386. : (!closest.inRange && distance < closest.distance))) {
  2387. if (range === Infinity) {
  2388. inRange = true;
  2389. }
  2390. closest.target = target;
  2391. closest.distance = distance;
  2392. closest.range = range;
  2393. closest.inRange = inRange;
  2394. closest.dx = dx;
  2395. closest.dy = dy;
  2396. status.range = range;
  2397. }
  2398. }
  2399. var snapChanged;
  2400. if (closest.target) {
  2401. snapChanged = (status.snappedX !== closest.target.x || status.snappedY !== closest.target.y);
  2402. status.snappedX = closest.target.x;
  2403. status.snappedY = closest.target.y;
  2404. }
  2405. else {
  2406. snapChanged = true;
  2407. status.snappedX = NaN;
  2408. status.snappedY = NaN;
  2409. }
  2410. status.dx = closest.dx;
  2411. status.dy = closest.dy;
  2412. status.changed = (snapChanged || (closest.inRange && !status.locked));
  2413. status.locked = closest.inRange;
  2414. return status;
  2415. },
  2416. setRestriction: function (pageCoords, status) {
  2417. var target = this.target,
  2418. restrict = target && target.options[this.prepared.name].restrict,
  2419. restriction = restrict && restrict.restriction,
  2420. page;
  2421. if (!restriction) {
  2422. return status;
  2423. }
  2424. status = status || this.restrictStatus;
  2425. page = status.useStatusXY
  2426. ? page = { x: status.x, y: status.y }
  2427. : page = extend({}, pageCoords);
  2428. if (status.snap && status.snap.locked) {
  2429. page.x += status.snap.dx || 0;
  2430. page.y += status.snap.dy || 0;
  2431. }
  2432. page.x -= this.inertiaStatus.resumeDx;
  2433. page.y -= this.inertiaStatus.resumeDy;
  2434. status.dx = 0;
  2435. status.dy = 0;
  2436. status.restricted = false;
  2437. var rect, restrictedX, restrictedY;
  2438. if (isString(restriction)) {
  2439. if (restriction === 'parent') {
  2440. restriction = parentElement(this.element);
  2441. }
  2442. else if (restriction === 'self') {
  2443. restriction = target.getRect(this.element);
  2444. }
  2445. else {
  2446. restriction = closest(this.element, restriction);
  2447. }
  2448. if (!restriction) { return status; }
  2449. }
  2450. if (isFunction(restriction)) {
  2451. restriction = restriction(page.x, page.y, this.element);
  2452. }
  2453. if (isElement(restriction)) {
  2454. restriction = getElementRect(restriction);
  2455. }
  2456. rect = restriction;
  2457. if (!restriction) {
  2458. restrictedX = page.x;
  2459. restrictedY = page.y;
  2460. }
  2461. // object is assumed to have
  2462. // x, y, width, height or
  2463. // left, top, right, bottom
  2464. else if ('x' in restriction && 'y' in restriction) {
  2465. restrictedX = Math.max(Math.min(rect.x + rect.width - this.restrictOffset.right , page.x), rect.x + this.restrictOffset.left);
  2466. restrictedY = Math.max(Math.min(rect.y + rect.height - this.restrictOffset.bottom, page.y), rect.y + this.restrictOffset.top );
  2467. }
  2468. else {
  2469. restrictedX = Math.max(Math.min(rect.right - this.restrictOffset.right , page.x), rect.left + this.restrictOffset.left);
  2470. restrictedY = Math.max(Math.min(rect.bottom - this.restrictOffset.bottom, page.y), rect.top + this.restrictOffset.top );
  2471. }
  2472. status.dx = restrictedX - page.x;
  2473. status.dy = restrictedY - page.y;
  2474. status.changed = status.restrictedX !== restrictedX || status.restrictedY !== restrictedY;
  2475. status.restricted = !!(status.dx || status.dy);
  2476. status.restrictedX = restrictedX;
  2477. status.restrictedY = restrictedY;
  2478. return status;
  2479. },
  2480. checkAndPreventDefault: function (event, interactable, element) {
  2481. if (!(interactable = interactable || this.target)) { return; }
  2482. var options = interactable.options,
  2483. prevent = options.preventDefault;
  2484. if (prevent === 'auto' && element && !/^(input|select|textarea)$/i.test(event.target.nodeName)) {
  2485. // do not preventDefault on pointerdown if the prepared action is a drag
  2486. // and dragging can only start from a certain direction - this allows
  2487. // a touch to pan the viewport if a drag isn't in the right direction
  2488. if (/down|start/i.test(event.type)
  2489. && this.prepared.name === 'drag' && options.drag.axis !== 'xy') {
  2490. return;
  2491. }
  2492. // with manualStart, only preventDefault while interacting
  2493. if (options[this.prepared.name] && options[this.prepared.name].manualStart
  2494. && !this.interacting()) {
  2495. return;
  2496. }
  2497. event.preventDefault();
  2498. return;
  2499. }
  2500. if (prevent === 'always') {
  2501. event.preventDefault();
  2502. return;
  2503. }
  2504. },
  2505. calcInertia: function (status) {
  2506. var inertiaOptions = this.target.options[this.prepared.name].inertia,
  2507. lambda = inertiaOptions.resistance,
  2508. inertiaDur = -Math.log(inertiaOptions.endSpeed / status.v0) / lambda;
  2509. status.x0 = this.prevEvent.pageX;
  2510. status.y0 = this.prevEvent.pageY;
  2511. status.t0 = status.startEvent.timeStamp / 1000;
  2512. status.sx = status.sy = 0;
  2513. status.modifiedXe = status.xe = (status.vx0 - inertiaDur) / lambda;
  2514. status.modifiedYe = status.ye = (status.vy0 - inertiaDur) / lambda;
  2515. status.te = inertiaDur;
  2516. status.lambda_v0 = lambda / status.v0;
  2517. status.one_ve_v0 = 1 - inertiaOptions.endSpeed / status.v0;
  2518. },
  2519. autoScrollMove: function (pointer) {
  2520. if (!(this.interacting()
  2521. && checkAutoScroll(this.target, this.prepared.name))) {
  2522. return;
  2523. }
  2524. if (this.inertiaStatus.active) {
  2525. autoScroll.x = autoScroll.y = 0;
  2526. return;
  2527. }
  2528. var top,
  2529. right,
  2530. bottom,
  2531. left,
  2532. options = this.target.options[this.prepared.name].autoScroll,
  2533. container = options.container || getWindow(this.element);
  2534. if (isWindow(container)) {
  2535. left = pointer.clientX < autoScroll.margin;
  2536. top = pointer.clientY < autoScroll.margin;
  2537. right = pointer.clientX > container.innerWidth - autoScroll.margin;
  2538. bottom = pointer.clientY > container.innerHeight - autoScroll.margin;
  2539. }
  2540. else {
  2541. var rect = getElementClientRect(container);
  2542. left = pointer.clientX < rect.left + autoScroll.margin;
  2543. top = pointer.clientY < rect.top + autoScroll.margin;
  2544. right = pointer.clientX > rect.right - autoScroll.margin;
  2545. bottom = pointer.clientY > rect.bottom - autoScroll.margin;
  2546. }
  2547. autoScroll.x = (right ? 1: left? -1: 0);
  2548. autoScroll.y = (bottom? 1: top? -1: 0);
  2549. if (!autoScroll.isScrolling) {
  2550. // set the autoScroll properties to those of the target
  2551. autoScroll.margin = options.margin;
  2552. autoScroll.speed = options.speed;
  2553. autoScroll.start(this);
  2554. }
  2555. },
  2556. _updateEventTargets: function (target, currentTarget) {
  2557. this._eventTarget = target;
  2558. this._curEventTarget = currentTarget;
  2559. }
  2560. };
  2561. function getInteractionFromPointer (pointer, eventType, eventTarget) {
  2562. var i = 0, len = interactions.length,
  2563. mouseEvent = (/mouse/i.test(pointer.pointerType || eventType)
  2564. // MSPointerEvent.MSPOINTER_TYPE_MOUSE
  2565. || pointer.pointerType === 4),
  2566. interaction;
  2567. var id = getPointerId(pointer);
  2568. // try to resume inertia with a new pointer
  2569. if (/down|start/i.test(eventType)) {
  2570. for (i = 0; i < len; i++) {
  2571. interaction = interactions[i];
  2572. var element = eventTarget;
  2573. if (interaction.inertiaStatus.active && interaction.target.options[interaction.prepared.name].inertia.allowResume
  2574. && (interaction.mouse === mouseEvent)) {
  2575. while (element) {
  2576. // if the element is the interaction element
  2577. if (element === interaction.element) {
  2578. return interaction;
  2579. }
  2580. element = parentElement(element);
  2581. }
  2582. }
  2583. }
  2584. }
  2585. // if it's a mouse interaction
  2586. if (mouseEvent || !(supportsTouch || supportsPointerEvent)) {
  2587. // find a mouse interaction that's not in inertia phase
  2588. for (i = 0; i < len; i++) {
  2589. if (interactions[i].mouse && !interactions[i].inertiaStatus.active) {
  2590. return interactions[i];
  2591. }
  2592. }
  2593. // find any interaction specifically for mouse.
  2594. // if the eventType is a mousedown, and inertia is active
  2595. // ignore the interaction
  2596. for (i = 0; i < len; i++) {
  2597. if (interactions[i].mouse && !(/down/.test(eventType) && interactions[i].inertiaStatus.active)) {
  2598. return interaction;
  2599. }
  2600. }
  2601. // create a new interaction for mouse
  2602. interaction = new Interaction();
  2603. interaction.mouse = true;
  2604. return interaction;
  2605. }
  2606. // get interaction that has this pointer
  2607. for (i = 0; i < len; i++) {
  2608. if (contains(interactions[i].pointerIds, id)) {
  2609. return interactions[i];
  2610. }
  2611. }
  2612. // at this stage, a pointerUp should not return an interaction
  2613. if (/up|end|out/i.test(eventType)) {
  2614. return null;
  2615. }
  2616. // get first idle interaction
  2617. for (i = 0; i < len; i++) {
  2618. interaction = interactions[i];
  2619. if ((!interaction.prepared.name || (interaction.target.options.gesture.enabled))
  2620. && !interaction.interacting()
  2621. && !(!mouseEvent && interaction.mouse)) {
  2622. return interaction;
  2623. }
  2624. }
  2625. return new Interaction();
  2626. }
  2627. function doOnInteractions (method) {
  2628. return (function (event) {
  2629. var interaction,
  2630. eventTarget = getActualElement(event.path
  2631. ? event.path[0]
  2632. : event.target),
  2633. curEventTarget = getActualElement(event.currentTarget),
  2634. i;
  2635. if (supportsTouch && /touch/.test(event.type)) {
  2636. prevTouchTime = new Date().getTime();
  2637. for (i = 0; i < event.changedTouches.length; i++) {
  2638. var pointer = event.changedTouches[i];
  2639. interaction = getInteractionFromPointer(pointer, event.type, eventTarget);
  2640. if (!interaction) { continue; }
  2641. interaction._updateEventTargets(eventTarget, curEventTarget);
  2642. interaction[method](pointer, event, eventTarget, curEventTarget);
  2643. }
  2644. }
  2645. else {
  2646. if (!supportsPointerEvent && /mouse/.test(event.type)) {
  2647. // ignore mouse events while touch interactions are active
  2648. for (i = 0; i < interactions.length; i++) {
  2649. if (!interactions[i].mouse && interactions[i].pointerIsDown) {
  2650. return;
  2651. }
  2652. }
  2653. // try to ignore mouse events that are simulated by the browser
  2654. // after a touch event
  2655. if (new Date().getTime() - prevTouchTime < 500) {
  2656. return;
  2657. }
  2658. }
  2659. interaction = getInteractionFromPointer(event, event.type, eventTarget);
  2660. if (!interaction) { return; }
  2661. interaction._updateEventTargets(eventTarget, curEventTarget);
  2662. interaction[method](event, event, eventTarget, curEventTarget);
  2663. }
  2664. });
  2665. }
  2666. function InteractEvent (interaction, event, action, phase, element, related) {
  2667. var client,
  2668. page,
  2669. target = interaction.target,
  2670. snapStatus = interaction.snapStatus,
  2671. restrictStatus = interaction.restrictStatus,
  2672. pointers = interaction.pointers,
  2673. deltaSource = (target && target.options || defaultOptions).deltaSource,
  2674. sourceX = deltaSource + 'X',
  2675. sourceY = deltaSource + 'Y',
  2676. options = target? target.options: defaultOptions,
  2677. origin = getOriginXY(target, element),
  2678. starting = phase === 'start',
  2679. ending = phase === 'end',
  2680. coords = starting? interaction.startCoords : interaction.curCoords;
  2681. element = element || interaction.element;
  2682. page = extend({}, coords.page);
  2683. client = extend({}, coords.client);
  2684. page.x -= origin.x;
  2685. page.y -= origin.y;
  2686. client.x -= origin.x;
  2687. client.y -= origin.y;
  2688. var relativePoints = options[action].snap && options[action].snap.relativePoints ;
  2689. if (checkSnap(target, action) && !(starting && relativePoints && relativePoints.length)) {
  2690. this.snap = {
  2691. range : snapStatus.range,
  2692. locked : snapStatus.locked,
  2693. x : snapStatus.snappedX,
  2694. y : snapStatus.snappedY,
  2695. realX : snapStatus.realX,
  2696. realY : snapStatus.realY,
  2697. dx : snapStatus.dx,
  2698. dy : snapStatus.dy
  2699. };
  2700. if (snapStatus.locked) {
  2701. page.x += snapStatus.dx;
  2702. page.y += snapStatus.dy;
  2703. client.x += snapStatus.dx;
  2704. client.y += snapStatus.dy;
  2705. }
  2706. }
  2707. if (checkRestrict(target, action) && !(starting && options[action].restrict.elementRect) && restrictStatus.restricted) {
  2708. page.x += restrictStatus.dx;
  2709. page.y += restrictStatus.dy;
  2710. client.x += restrictStatus.dx;
  2711. client.y += restrictStatus.dy;
  2712. this.restrict = {
  2713. dx: restrictStatus.dx,
  2714. dy: restrictStatus.dy
  2715. };
  2716. }
  2717. this.pageX = page.x;
  2718. this.pageY = page.y;
  2719. this.clientX = client.x;
  2720. this.clientY = client.y;
  2721. this.x0 = interaction.startCoords.page.x - origin.x;
  2722. this.y0 = interaction.startCoords.page.y - origin.y;
  2723. this.clientX0 = interaction.startCoords.client.x - origin.x;
  2724. this.clientY0 = interaction.startCoords.client.y - origin.y;
  2725. this.ctrlKey = event.ctrlKey;
  2726. this.altKey = event.altKey;
  2727. this.shiftKey = event.shiftKey;
  2728. this.metaKey = event.metaKey;
  2729. this.button = event.button;
  2730. this.buttons = event.buttons;
  2731. this.target = element;
  2732. this.t0 = interaction.downTimes[0];
  2733. this.type = action + (phase || '');
  2734. this.interaction = interaction;
  2735. this.interactable = target;
  2736. var inertiaStatus = interaction.inertiaStatus;
  2737. if (inertiaStatus.active) {
  2738. this.detail = 'inertia';
  2739. }
  2740. if (related) {
  2741. this.relatedTarget = related;
  2742. }
  2743. // end event dx, dy is difference between start and end points
  2744. if (ending) {
  2745. if (deltaSource === 'client') {
  2746. this.dx = client.x - interaction.startCoords.client.x;
  2747. this.dy = client.y - interaction.startCoords.client.y;
  2748. }
  2749. else {
  2750. this.dx = page.x - interaction.startCoords.page.x;
  2751. this.dy = page.y - interaction.startCoords.page.y;
  2752. }
  2753. }
  2754. else if (starting) {
  2755. this.dx = 0;
  2756. this.dy = 0;
  2757. }
  2758. // copy properties from previousmove if starting inertia
  2759. else if (phase === 'inertiastart') {
  2760. this.dx = interaction.prevEvent.dx;
  2761. this.dy = interaction.prevEvent.dy;
  2762. }
  2763. else {
  2764. if (deltaSource === 'client') {
  2765. this.dx = client.x - interaction.prevEvent.clientX;
  2766. this.dy = client.y - interaction.prevEvent.clientY;
  2767. }
  2768. else {
  2769. this.dx = page.x - interaction.prevEvent.pageX;
  2770. this.dy = page.y - interaction.prevEvent.pageY;
  2771. }
  2772. }
  2773. if (interaction.prevEvent && interaction.prevEvent.detail === 'inertia'
  2774. && !inertiaStatus.active
  2775. && options[action].inertia && options[action].inertia.zeroResumeDelta) {
  2776. inertiaStatus.resumeDx += this.dx;
  2777. inertiaStatus.resumeDy += this.dy;
  2778. this.dx = this.dy = 0;
  2779. }
  2780. if (action === 'resize' && interaction.resizeAxes) {
  2781. if (options.resize.square) {
  2782. if (interaction.resizeAxes === 'y') {
  2783. this.dx = this.dy;
  2784. }
  2785. else {
  2786. this.dy = this.dx;
  2787. }
  2788. this.axes = 'xy';
  2789. }
  2790. else {
  2791. this.axes = interaction.resizeAxes;
  2792. if (interaction.resizeAxes === 'x') {
  2793. this.dy = 0;
  2794. }
  2795. else if (interaction.resizeAxes === 'y') {
  2796. this.dx = 0;
  2797. }
  2798. }
  2799. }
  2800. else if (action === 'gesture') {
  2801. this.touches = [pointers[0], pointers[1]];
  2802. if (starting) {
  2803. this.distance = touchDistance(pointers, deltaSource);
  2804. this.box = touchBBox(pointers);
  2805. this.scale = 1;
  2806. this.ds = 0;
  2807. this.angle = touchAngle(pointers, undefined, deltaSource);
  2808. this.da = 0;
  2809. }
  2810. else if (ending || event instanceof InteractEvent) {
  2811. this.distance = interaction.prevEvent.distance;
  2812. this.box = interaction.prevEvent.box;
  2813. this.scale = interaction.prevEvent.scale;
  2814. this.ds = this.scale - 1;
  2815. this.angle = interaction.prevEvent.angle;
  2816. this.da = this.angle - interaction.gesture.startAngle;
  2817. }
  2818. else {
  2819. this.distance = touchDistance(pointers, deltaSource);
  2820. this.box = touchBBox(pointers);
  2821. this.scale = this.distance / interaction.gesture.startDistance;
  2822. this.angle = touchAngle(pointers, interaction.gesture.prevAngle, deltaSource);
  2823. this.ds = this.scale - interaction.gesture.prevScale;
  2824. this.da = this.angle - interaction.gesture.prevAngle;
  2825. }
  2826. }
  2827. if (starting) {
  2828. this.timeStamp = interaction.downTimes[0];
  2829. this.dt = 0;
  2830. this.duration = 0;
  2831. this.speed = 0;
  2832. this.velocityX = 0;
  2833. this.velocityY = 0;
  2834. }
  2835. else if (phase === 'inertiastart') {
  2836. this.timeStamp = interaction.prevEvent.timeStamp;
  2837. this.dt = interaction.prevEvent.dt;
  2838. this.duration = interaction.prevEvent.duration;
  2839. this.speed = interaction.prevEvent.speed;
  2840. this.velocityX = interaction.prevEvent.velocityX;
  2841. this.velocityY = interaction.prevEvent.velocityY;
  2842. }
  2843. else {
  2844. this.timeStamp = new Date().getTime();
  2845. this.dt = this.timeStamp - interaction.prevEvent.timeStamp;
  2846. this.duration = this.timeStamp - interaction.downTimes[0];
  2847. if (event instanceof InteractEvent) {
  2848. var dx = this[sourceX] - interaction.prevEvent[sourceX],
  2849. dy = this[sourceY] - interaction.prevEvent[sourceY],
  2850. dt = this.dt / 1000;
  2851. this.speed = hypot(dx, dy) / dt;
  2852. this.velocityX = dx / dt;
  2853. this.velocityY = dy / dt;
  2854. }
  2855. // if normal move or end event, use previous user event coords
  2856. else {
  2857. // speed and velocity in pixels per second
  2858. this.speed = interaction.pointerDelta[deltaSource].speed;
  2859. this.velocityX = interaction.pointerDelta[deltaSource].vx;
  2860. this.velocityY = interaction.pointerDelta[deltaSource].vy;
  2861. }
  2862. }
  2863. if ((ending || phase === 'inertiastart')
  2864. && interaction.prevEvent.speed > 600 && this.timeStamp - interaction.prevEvent.timeStamp < 150) {
  2865. var angle = 180 * Math.atan2(interaction.prevEvent.velocityY, interaction.prevEvent.velocityX) / Math.PI,
  2866. overlap = 22.5;
  2867. if (angle < 0) {
  2868. angle += 360;
  2869. }
  2870. var left = 135 - overlap <= angle && angle < 225 + overlap,
  2871. up = 225 - overlap <= angle && angle < 315 + overlap,
  2872. right = !left && (315 - overlap <= angle || angle < 45 + overlap),
  2873. down = !up && 45 - overlap <= angle && angle < 135 + overlap;
  2874. this.swipe = {
  2875. up : up,
  2876. down : down,
  2877. left : left,
  2878. right: right,
  2879. angle: angle,
  2880. speed: interaction.prevEvent.speed,
  2881. velocity: {
  2882. x: interaction.prevEvent.velocityX,
  2883. y: interaction.prevEvent.velocityY
  2884. }
  2885. };
  2886. }
  2887. }
  2888. InteractEvent.prototype = {
  2889. preventDefault: blank,
  2890. stopImmediatePropagation: function () {
  2891. this.immediatePropagationStopped = this.propagationStopped = true;
  2892. },
  2893. stopPropagation: function () {
  2894. this.propagationStopped = true;
  2895. }
  2896. };
  2897. function preventOriginalDefault () {
  2898. this.originalEvent.preventDefault();
  2899. }
  2900. function getActionCursor (action) {
  2901. var cursor = '';
  2902. if (action.name === 'drag') {
  2903. cursor = actionCursors.drag;
  2904. }
  2905. if (action.name === 'resize') {
  2906. if (action.axis) {
  2907. cursor = actionCursors[action.name + action.axis];
  2908. }
  2909. else if (action.edges) {
  2910. var cursorKey = 'resize',
  2911. edgeNames = ['top', 'bottom', 'left', 'right'];
  2912. for (var i = 0; i < 4; i++) {
  2913. if (action.edges[edgeNames[i]]) {
  2914. cursorKey += edgeNames[i];
  2915. }
  2916. }
  2917. cursor = actionCursors[cursorKey];
  2918. }
  2919. }
  2920. return cursor;
  2921. }
  2922. function checkResizeEdge (name, value, page, element, interactableElement, rect, margin) {
  2923. // false, '', undefined, null
  2924. if (!value) { return false; }
  2925. // true value, use pointer coords and element rect
  2926. if (value === true) {
  2927. // if dimensions are negative, "switch" edges
  2928. var width = isNumber(rect.width)? rect.width : rect.right - rect.left,
  2929. height = isNumber(rect.height)? rect.height : rect.bottom - rect.top;
  2930. if (width < 0) {
  2931. if (name === 'left' ) { name = 'right'; }
  2932. else if (name === 'right') { name = 'left' ; }
  2933. }
  2934. if (height < 0) {
  2935. if (name === 'top' ) { name = 'bottom'; }
  2936. else if (name === 'bottom') { name = 'top' ; }
  2937. }
  2938. if (name === 'left' ) { return page.x < ((width >= 0? rect.left: rect.right ) + margin); }
  2939. if (name === 'top' ) { return page.y < ((height >= 0? rect.top : rect.bottom) + margin); }
  2940. if (name === 'right' ) { return page.x > ((width >= 0? rect.right : rect.left) - margin); }
  2941. if (name === 'bottom') { return page.y > ((height >= 0? rect.bottom: rect.top ) - margin); }
  2942. }
  2943. // the remaining checks require an element
  2944. if (!isElement(element)) { return false; }
  2945. return isElement(value)
  2946. // the value is an element to use as a resize handle
  2947. ? value === element
  2948. // otherwise check if element matches value as selector
  2949. : matchesUpTo(element, value, interactableElement);
  2950. }
  2951. function defaultActionChecker (pointer, interaction, element) {
  2952. var rect = this.getRect(element),
  2953. shouldResize = false,
  2954. action = null,
  2955. resizeAxes = null,
  2956. resizeEdges,
  2957. page = extend({}, interaction.curCoords.page),
  2958. options = this.options;
  2959. if (!rect) { return null; }
  2960. if (actionIsEnabled.resize && options.resize.enabled) {
  2961. var resizeOptions = options.resize;
  2962. resizeEdges = {
  2963. left: false, right: false, top: false, bottom: false
  2964. };
  2965. // if using resize.edges
  2966. if (isObject(resizeOptions.edges)) {
  2967. for (var edge in resizeEdges) {
  2968. resizeEdges[edge] = checkResizeEdge(edge,
  2969. resizeOptions.edges[edge],
  2970. page,
  2971. interaction._eventTarget,
  2972. element,
  2973. rect,
  2974. resizeOptions.margin || margin);
  2975. }
  2976. resizeEdges.left = resizeEdges.left && !resizeEdges.right;
  2977. resizeEdges.top = resizeEdges.top && !resizeEdges.bottom;
  2978. shouldResize = resizeEdges.left || resizeEdges.right || resizeEdges.top || resizeEdges.bottom;
  2979. }
  2980. else {
  2981. var right = options.resize.axis !== 'y' && page.x > (rect.right - margin),
  2982. bottom = options.resize.axis !== 'x' && page.y > (rect.bottom - margin);
  2983. shouldResize = right || bottom;
  2984. resizeAxes = (right? 'x' : '') + (bottom? 'y' : '');
  2985. }
  2986. }
  2987. action = shouldResize
  2988. ? 'resize'
  2989. : actionIsEnabled.drag && options.drag.enabled
  2990. ? 'drag'
  2991. : null;
  2992. if (actionIsEnabled.gesture
  2993. && interaction.pointerIds.length >=2
  2994. && !(interaction.dragging || interaction.resizing)) {
  2995. action = 'gesture';
  2996. }
  2997. if (action) {
  2998. return {
  2999. name: action,
  3000. axis: resizeAxes,
  3001. edges: resizeEdges
  3002. };
  3003. }
  3004. return null;
  3005. }
  3006. // Check if action is enabled globally and the current target supports it
  3007. // If so, return the validated action. Otherwise, return null
  3008. function validateAction (action, interactable) {
  3009. if (!isObject(action)) { return null; }
  3010. var actionName = action.name,
  3011. options = interactable.options;
  3012. if (( (actionName === 'resize' && options.resize.enabled )
  3013. || (actionName === 'drag' && options.drag.enabled )
  3014. || (actionName === 'gesture' && options.gesture.enabled))
  3015. && actionIsEnabled[actionName]) {
  3016. if (actionName === 'resize' || actionName === 'resizeyx') {
  3017. actionName = 'resizexy';
  3018. }
  3019. return action;
  3020. }
  3021. return null;
  3022. }
  3023. var listeners = {},
  3024. interactionListeners = [
  3025. 'dragStart', 'dragMove', 'resizeStart', 'resizeMove', 'gestureStart', 'gestureMove',
  3026. 'pointerOver', 'pointerOut', 'pointerHover', 'selectorDown',
  3027. 'pointerDown', 'pointerMove', 'pointerUp', 'pointerCancel', 'pointerEnd',
  3028. 'addPointer', 'removePointer', 'recordPointer', 'autoScrollMove'
  3029. ];
  3030. for (var i = 0, len = interactionListeners.length; i < len; i++) {
  3031. var name = interactionListeners[i];
  3032. listeners[name] = doOnInteractions(name);
  3033. }
  3034. // bound to the interactable context when a DOM event
  3035. // listener is added to a selector interactable
  3036. function delegateListener (event, useCapture) {
  3037. var fakeEvent = {},
  3038. delegated = delegatedEvents[event.type],
  3039. eventTarget = getActualElement(event.path
  3040. ? event.path[0]
  3041. : event.target),
  3042. element = eventTarget;
  3043. useCapture = useCapture? true: false;
  3044. // duplicate the event so that currentTarget can be changed
  3045. for (var prop in event) {
  3046. fakeEvent[prop] = event[prop];
  3047. }
  3048. fakeEvent.originalEvent = event;
  3049. fakeEvent.preventDefault = preventOriginalDefault;
  3050. // climb up document tree looking for selector matches
  3051. while (isElement(element)) {
  3052. for (var i = 0; i < delegated.selectors.length; i++) {
  3053. var selector = delegated.selectors[i],
  3054. context = delegated.contexts[i];
  3055. if (matchesSelector(element, selector)
  3056. && nodeContains(context, eventTarget)
  3057. && nodeContains(context, element)) {
  3058. var listeners = delegated.listeners[i];
  3059. fakeEvent.currentTarget = element;
  3060. for (var j = 0; j < listeners.length; j++) {
  3061. if (listeners[j][1] === useCapture) {
  3062. listeners[j][0](fakeEvent);
  3063. }
  3064. }
  3065. }
  3066. }
  3067. element = parentElement(element);
  3068. }
  3069. }
  3070. function delegateUseCapture (event) {
  3071. return delegateListener.call(this, event, true);
  3072. }
  3073. interactables.indexOfElement = function indexOfElement (element, context) {
  3074. context = context || document;
  3075. for (var i = 0; i < this.length; i++) {
  3076. var interactable = this[i];
  3077. if ((interactable.selector === element
  3078. && (interactable._context === context))
  3079. || (!interactable.selector && interactable._element === element)) {
  3080. return i;
  3081. }
  3082. }
  3083. return -1;
  3084. };
  3085. interactables.get = function interactableGet (element, options) {
  3086. return this[this.indexOfElement(element, options && options.context)];
  3087. };
  3088. interactables.forEachSelector = function (callback) {
  3089. for (var i = 0; i < this.length; i++) {
  3090. var interactable = this[i];
  3091. if (!interactable.selector) {
  3092. continue;
  3093. }
  3094. var ret = callback(interactable, interactable.selector, interactable._context, i, this);
  3095. if (ret !== undefined) {
  3096. return ret;
  3097. }
  3098. }
  3099. };
  3100. /*\
  3101. * interact
  3102. [ method ]
  3103. *
  3104. * The methods of this variable can be used to set elements as
  3105. * interactables and also to change various default settings.
  3106. *
  3107. * Calling it as a function and passing an element or a valid CSS selector
  3108. * string returns an Interactable object which has various methods to
  3109. * configure it.
  3110. *
  3111. - element (Element | string) The HTML or SVG Element to interact with or CSS selector
  3112. = (object) An @Interactable
  3113. *
  3114. > Usage
  3115. | interact(document.getElementById('draggable')).draggable(true);
  3116. |
  3117. | var rectables = interact('rect');
  3118. | rectables
  3119. | .gesturable(true)
  3120. | .on('gesturemove', function (event) {
  3121. | // something cool...
  3122. | })
  3123. | .autoScroll(true);
  3124. \*/
  3125. function interact (element, options) {
  3126. return interactables.get(element, options) || new Interactable(element, options);
  3127. }
  3128. /*\
  3129. * Interactable
  3130. [ property ]
  3131. **
  3132. * Object type returned by @interact
  3133. \*/
  3134. function Interactable (element, options) {
  3135. this._element = element;
  3136. this._iEvents = this._iEvents || {};
  3137. var _window;
  3138. if (trySelector(element)) {
  3139. this.selector = element;
  3140. var context = options && options.context;
  3141. _window = context? getWindow(context) : window;
  3142. if (context && (_window.Node
  3143. ? context instanceof _window.Node
  3144. : (isElement(context) || context === _window.document))) {
  3145. this._context = context;
  3146. }
  3147. }
  3148. else {
  3149. _window = getWindow(element);
  3150. if (isElement(element, _window)) {
  3151. if (PointerEvent) {
  3152. events.add(this._element, pEventTypes.down, listeners.pointerDown );
  3153. events.add(this._element, pEventTypes.move, listeners.pointerHover);
  3154. }
  3155. else {
  3156. events.add(this._element, 'mousedown' , listeners.pointerDown );
  3157. events.add(this._element, 'mousemove' , listeners.pointerHover);
  3158. events.add(this._element, 'touchstart', listeners.pointerDown );
  3159. events.add(this._element, 'touchmove' , listeners.pointerHover);
  3160. }
  3161. }
  3162. }
  3163. this._doc = _window.document;
  3164. if (!contains(documents, this._doc)) {
  3165. listenToDocument(this._doc);
  3166. }
  3167. interactables.push(this);
  3168. this.set(options);
  3169. }
  3170. Interactable.prototype = {
  3171. setOnEvents: function (action, phases) {
  3172. if (action === 'drop') {
  3173. if (isFunction(phases.ondrop) ) { this.ondrop = phases.ondrop ; }
  3174. if (isFunction(phases.ondropactivate) ) { this.ondropactivate = phases.ondropactivate ; }
  3175. if (isFunction(phases.ondropdeactivate)) { this.ondropdeactivate = phases.ondropdeactivate; }
  3176. if (isFunction(phases.ondragenter) ) { this.ondragenter = phases.ondragenter ; }
  3177. if (isFunction(phases.ondragleave) ) { this.ondragleave = phases.ondragleave ; }
  3178. if (isFunction(phases.ondropmove) ) { this.ondropmove = phases.ondropmove ; }
  3179. }
  3180. else {
  3181. action = 'on' + action;
  3182. if (isFunction(phases.onstart) ) { this[action + 'start' ] = phases.onstart ; }
  3183. if (isFunction(phases.onmove) ) { this[action + 'move' ] = phases.onmove ; }
  3184. if (isFunction(phases.onend) ) { this[action + 'end' ] = phases.onend ; }
  3185. if (isFunction(phases.oninertiastart)) { this[action + 'inertiastart' ] = phases.oninertiastart ; }
  3186. }
  3187. return this;
  3188. },
  3189. /*\
  3190. * Interactable.draggable
  3191. [ method ]
  3192. *
  3193. * Gets or sets whether drag actions can be performed on the
  3194. * Interactable
  3195. *
  3196. = (boolean) Indicates if this can be the target of drag events
  3197. | var isDraggable = interact('ul li').draggable();
  3198. * or
  3199. - options (boolean | object) #optional true/false or An object with event listeners to be fired on drag events (object makes the Interactable draggable)
  3200. = (object) This Interactable
  3201. | interact(element).draggable({
  3202. | onstart: function (event) {},
  3203. | onmove : function (event) {},
  3204. | onend : function (event) {},
  3205. |
  3206. | // the axis in which the first movement must be
  3207. | // for the drag sequence to start
  3208. | // 'xy' by default - any direction
  3209. | axis: 'x' || 'y' || 'xy',
  3210. |
  3211. | // max number of drags that can happen concurrently
  3212. | // with elements of this Interactable. Infinity by default
  3213. | max: Infinity,
  3214. |
  3215. | // max number of drags that can target the same element+Interactable
  3216. | // 1 by default
  3217. | maxPerElement: 2
  3218. | });
  3219. \*/
  3220. draggable: function (options) {
  3221. if (isObject(options)) {
  3222. this.options.drag.enabled = options.enabled === false? false: true;
  3223. this.setPerAction('drag', options);
  3224. this.setOnEvents('drag', options);
  3225. if (/^x$|^y$|^xy$/.test(options.axis)) {
  3226. this.options.drag.axis = options.axis;
  3227. }
  3228. else if (options.axis === null) {
  3229. delete this.options.drag.axis;
  3230. }
  3231. return this;
  3232. }
  3233. if (isBool(options)) {
  3234. this.options.drag.enabled = options;
  3235. return this;
  3236. }
  3237. return this.options.drag;
  3238. },
  3239. setPerAction: function (action, options) {
  3240. // for all the default per-action options
  3241. for (var option in options) {
  3242. // if this option exists for this action
  3243. if (option in defaultOptions[action]) {
  3244. // if the option in the options arg is an object value
  3245. if (isObject(options[option])) {
  3246. // duplicate the object
  3247. this.options[action][option] = extend(this.options[action][option] || {}, options[option]);
  3248. if (isObject(defaultOptions.perAction[option]) && 'enabled' in defaultOptions.perAction[option]) {
  3249. this.options[action][option].enabled = options[option].enabled === false? false : true;
  3250. }
  3251. }
  3252. else if (isBool(options[option]) && isObject(defaultOptions.perAction[option])) {
  3253. this.options[action][option].enabled = options[option];
  3254. }
  3255. else if (options[option] !== undefined) {
  3256. // or if it's not undefined, do a plain assignment
  3257. this.options[action][option] = options[option];
  3258. }
  3259. }
  3260. }
  3261. },
  3262. /*\
  3263. * Interactable.dropzone
  3264. [ method ]
  3265. *
  3266. * Returns or sets whether elements can be dropped onto this
  3267. * Interactable to trigger drop events
  3268. *
  3269. * Dropzones can receive the following events:
  3270. * - `dropactivate` and `dropdeactivate` when an acceptable drag starts and ends
  3271. * - `dragenter` and `dragleave` when a draggable enters and leaves the dropzone
  3272. * - `dragmove` when a draggable that has entered the dropzone is moved
  3273. * - `drop` when a draggable is dropped into this dropzone
  3274. *
  3275. * Use the `accept` option to allow only elements that match the given CSS selector or element.
  3276. *
  3277. * Use the `overlap` option to set how drops are checked for. The allowed values are:
  3278. * - `'pointer'`, the pointer must be over the dropzone (default)
  3279. * - `'center'`, the draggable element's center must be over the dropzone
  3280. * - a number from 0-1 which is the `(intersection area) / (draggable area)`.
  3281. * e.g. `0.5` for drop to happen when half of the area of the
  3282. * draggable is over the dropzone
  3283. *
  3284. - options (boolean | object | null) #optional The new value to be set.
  3285. | interact('.drop').dropzone({
  3286. | accept: '.can-drop' || document.getElementById('single-drop'),
  3287. | overlap: 'pointer' || 'center' || zeroToOne
  3288. | }
  3289. = (boolean | object) The current setting or this Interactable
  3290. \*/
  3291. dropzone: function (options) {
  3292. if (isObject(options)) {
  3293. this.options.drop.enabled = options.enabled === false? false: true;
  3294. this.setOnEvents('drop', options);
  3295. if (/^(pointer|center)$/.test(options.overlap)) {
  3296. this.options.drop.overlap = options.overlap;
  3297. }
  3298. else if (isNumber(options.overlap)) {
  3299. this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0);
  3300. }
  3301. if ('accept' in options) {
  3302. this.options.drop.accept = options.accept;
  3303. }
  3304. if ('checker' in options) {
  3305. this.options.drop.checker = options.checker;
  3306. }
  3307. return this;
  3308. }
  3309. if (isBool(options)) {
  3310. this.options.drop.enabled = options;
  3311. return this;
  3312. }
  3313. return this.options.drop;
  3314. },
  3315. dropCheck: function (dragEvent, event, draggable, draggableElement, dropElement, rect) {
  3316. var dropped = false;
  3317. // if the dropzone has no rect (eg. display: none)
  3318. // call the custom dropChecker or just return false
  3319. if (!(rect = rect || this.getRect(dropElement))) {
  3320. return (this.options.drop.checker
  3321. ? this.options.drop.checker(dragEvent, event, dropped, this, dropElement, draggable, draggableElement)
  3322. : false);
  3323. }
  3324. var dropOverlap = this.options.drop.overlap;
  3325. if (dropOverlap === 'pointer') {
  3326. var page = getPageXY(dragEvent),
  3327. origin = getOriginXY(draggable, draggableElement),
  3328. horizontal,
  3329. vertical;
  3330. page.x += origin.x;
  3331. page.y += origin.y;
  3332. horizontal = (page.x > rect.left) && (page.x < rect.right);
  3333. vertical = (page.y > rect.top ) && (page.y < rect.bottom);
  3334. dropped = horizontal && vertical;
  3335. }
  3336. var dragRect = draggable.getRect(draggableElement);
  3337. if (dropOverlap === 'center') {
  3338. var cx = dragRect.left + dragRect.width / 2,
  3339. cy = dragRect.top + dragRect.height / 2;
  3340. dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom;
  3341. }
  3342. if (isNumber(dropOverlap)) {
  3343. var overlapArea = (Math.max(0, Math.min(rect.right , dragRect.right ) - Math.max(rect.left, dragRect.left))
  3344. * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top , dragRect.top ))),
  3345. overlapRatio = overlapArea / (dragRect.width * dragRect.height);
  3346. dropped = overlapRatio >= dropOverlap;
  3347. }
  3348. if (this.options.drop.checker) {
  3349. dropped = this.options.drop.checker(dragEvent, event, dropped, this, dropElement, draggable, draggableElement);
  3350. }
  3351. return dropped;
  3352. },
  3353. /*\
  3354. * Interactable.dropChecker
  3355. [ method ]
  3356. *
  3357. * DEPRECATED. Use interactable.dropzone({ checker: function... }) instead.
  3358. *
  3359. * Gets or sets the function used to check if a dragged element is
  3360. * over this Interactable.
  3361. *
  3362. - checker (function) #optional The function that will be called when checking for a drop
  3363. = (Function | Interactable) The checker function or this Interactable
  3364. *
  3365. * The checker function takes the following arguments:
  3366. *
  3367. - dragEvent (InteractEvent) The related dragmove or dragend event
  3368. - event (TouchEvent | PointerEvent | MouseEvent) The user move/up/end Event related to the dragEvent
  3369. - dropped (boolean) The value from the default drop checker
  3370. - dropzone (Interactable) The dropzone interactable
  3371. - dropElement (Element) The dropzone element
  3372. - draggable (Interactable) The Interactable being dragged
  3373. - draggableElement (Element) The actual element that's being dragged
  3374. *
  3375. > Usage:
  3376. | interact(target)
  3377. | .dropChecker(function(dragEvent, // related dragmove or dragend event
  3378. | event, // TouchEvent/PointerEvent/MouseEvent
  3379. | dropped, // bool result of the default checker
  3380. | dropzone, // dropzone Interactable
  3381. | dropElement, // dropzone elemnt
  3382. | draggable, // draggable Interactable
  3383. | draggableElement) {// draggable element
  3384. |
  3385. | return dropped && event.target.hasAttribute('allow-drop');
  3386. | }
  3387. \*/
  3388. dropChecker: function (checker) {
  3389. if (isFunction(checker)) {
  3390. this.options.drop.checker = checker;
  3391. return this;
  3392. }
  3393. if (checker === null) {
  3394. delete this.options.getRect;
  3395. return this;
  3396. }
  3397. return this.options.drop.checker;
  3398. },
  3399. /*\
  3400. * Interactable.accept
  3401. [ method ]
  3402. *
  3403. * Deprecated. add an `accept` property to the options object passed to
  3404. * @Interactable.dropzone instead.
  3405. *
  3406. * Gets or sets the Element or CSS selector match that this
  3407. * Interactable accepts if it is a dropzone.
  3408. *
  3409. - newValue (Element | string | null) #optional
  3410. * If it is an Element, then only that element can be dropped into this dropzone.
  3411. * If it is a string, the element being dragged must match it as a selector.
  3412. * If it is null, the accept options is cleared - it accepts any element.
  3413. *
  3414. = (string | Element | null | Interactable) The current accept option if given `undefined` or this Interactable
  3415. \*/
  3416. accept: function (newValue) {
  3417. if (isElement(newValue)) {
  3418. this.options.drop.accept = newValue;
  3419. return this;
  3420. }
  3421. // test if it is a valid CSS selector
  3422. if (trySelector(newValue)) {
  3423. this.options.drop.accept = newValue;
  3424. return this;
  3425. }
  3426. if (newValue === null) {
  3427. delete this.options.drop.accept;
  3428. return this;
  3429. }
  3430. return this.options.drop.accept;
  3431. },
  3432. /*\
  3433. * Interactable.resizable
  3434. [ method ]
  3435. *
  3436. * Gets or sets whether resize actions can be performed on the
  3437. * Interactable
  3438. *
  3439. = (boolean) Indicates if this can be the target of resize elements
  3440. | var isResizeable = interact('input[type=text]').resizable();
  3441. * or
  3442. - options (boolean | object) #optional true/false or An object with event listeners to be fired on resize events (object makes the Interactable resizable)
  3443. = (object) This Interactable
  3444. | interact(element).resizable({
  3445. | onstart: function (event) {},
  3446. | onmove : function (event) {},
  3447. | onend : function (event) {},
  3448. |
  3449. | edges: {
  3450. | top : true, // Use pointer coords to check for resize.
  3451. | left : false, // Disable resizing from left edge.
  3452. | bottom: '.resize-s',// Resize if pointer target matches selector
  3453. | right : handleEl // Resize if pointer target is the given Element
  3454. | },
  3455. |
  3456. | // Width and height can be adjusted independently. When `true`, width and
  3457. | // height are adjusted at a 1:1 ratio.
  3458. | square: false,
  3459. |
  3460. | // Width and height can be adjusted independently. When `true`, width and
  3461. | // height maintain the aspect ratio they had when resizing started.
  3462. | preserveAspectRatio: false,
  3463. |
  3464. | // a value of 'none' will limit the resize rect to a minimum of 0x0
  3465. | // 'negate' will allow the rect to have negative width/height
  3466. | // 'reposition' will keep the width/height positive by swapping
  3467. | // the top and bottom edges and/or swapping the left and right edges
  3468. | invert: 'none' || 'negate' || 'reposition'
  3469. |
  3470. | // limit multiple resizes.
  3471. | // See the explanation in the @Interactable.draggable example
  3472. | max: Infinity,
  3473. | maxPerElement: 1,
  3474. | });
  3475. \*/
  3476. resizable: function (options) {
  3477. if (isObject(options)) {
  3478. this.options.resize.enabled = options.enabled === false? false: true;
  3479. this.setPerAction('resize', options);
  3480. this.setOnEvents('resize', options);
  3481. if (/^x$|^y$|^xy$/.test(options.axis)) {
  3482. this.options.resize.axis = options.axis;
  3483. }
  3484. else if (options.axis === null) {
  3485. this.options.resize.axis = defaultOptions.resize.axis;
  3486. }
  3487. if (isBool(options.preserveAspectRatio)) {
  3488. this.options.resize.preserveAspectRatio = options.preserveAspectRatio;
  3489. }
  3490. else if (isBool(options.square)) {
  3491. this.options.resize.square = options.square;
  3492. }
  3493. return this;
  3494. }
  3495. if (isBool(options)) {
  3496. this.options.resize.enabled = options;
  3497. return this;
  3498. }
  3499. return this.options.resize;
  3500. },
  3501. /*\
  3502. * Interactable.squareResize
  3503. [ method ]
  3504. *
  3505. * Deprecated. Add a `square: true || false` property to @Interactable.resizable instead
  3506. *
  3507. * Gets or sets whether resizing is forced 1:1 aspect
  3508. *
  3509. = (boolean) Current setting
  3510. *
  3511. * or
  3512. *
  3513. - newValue (boolean) #optional
  3514. = (object) this Interactable
  3515. \*/
  3516. squareResize: function (newValue) {
  3517. if (isBool(newValue)) {
  3518. this.options.resize.square = newValue;
  3519. return this;
  3520. }
  3521. if (newValue === null) {
  3522. delete this.options.resize.square;
  3523. return this;
  3524. }
  3525. return this.options.resize.square;
  3526. },
  3527. /*\
  3528. * Interactable.gesturable
  3529. [ method ]
  3530. *
  3531. * Gets or sets whether multitouch gestures can be performed on the
  3532. * Interactable's element
  3533. *
  3534. = (boolean) Indicates if this can be the target of gesture events
  3535. | var isGestureable = interact(element).gesturable();
  3536. * or
  3537. - options (boolean | object) #optional true/false or An object with event listeners to be fired on gesture events (makes the Interactable gesturable)
  3538. = (object) this Interactable
  3539. | interact(element).gesturable({
  3540. | onstart: function (event) {},
  3541. | onmove : function (event) {},
  3542. | onend : function (event) {},
  3543. |
  3544. | // limit multiple gestures.
  3545. | // See the explanation in @Interactable.draggable example
  3546. | max: Infinity,
  3547. | maxPerElement: 1,
  3548. | });
  3549. \*/
  3550. gesturable: function (options) {
  3551. if (isObject(options)) {
  3552. this.options.gesture.enabled = options.enabled === false? false: true;
  3553. this.setPerAction('gesture', options);
  3554. this.setOnEvents('gesture', options);
  3555. return this;
  3556. }
  3557. if (isBool(options)) {
  3558. this.options.gesture.enabled = options;
  3559. return this;
  3560. }
  3561. return this.options.gesture;
  3562. },
  3563. /*\
  3564. * Interactable.autoScroll
  3565. [ method ]
  3566. **
  3567. * Deprecated. Add an `autoscroll` property to the options object
  3568. * passed to @Interactable.draggable or @Interactable.resizable instead.
  3569. *
  3570. * Returns or sets whether dragging and resizing near the edges of the
  3571. * window/container trigger autoScroll for this Interactable
  3572. *
  3573. = (object) Object with autoScroll properties
  3574. *
  3575. * or
  3576. *
  3577. - options (object | boolean) #optional
  3578. * options can be:
  3579. * - an object with margin, distance and interval properties,
  3580. * - true or false to enable or disable autoScroll or
  3581. = (Interactable) this Interactable
  3582. \*/
  3583. autoScroll: function (options) {
  3584. if (isObject(options)) {
  3585. options = extend({ actions: ['drag', 'resize']}, options);
  3586. }
  3587. else if (isBool(options)) {
  3588. options = { actions: ['drag', 'resize'], enabled: options };
  3589. }
  3590. return this.setOptions('autoScroll', options);
  3591. },
  3592. /*\
  3593. * Interactable.snap
  3594. [ method ]
  3595. **
  3596. * Deprecated. Add a `snap` property to the options object passed
  3597. * to @Interactable.draggable or @Interactable.resizable instead.
  3598. *
  3599. * Returns or sets if and how action coordinates are snapped. By
  3600. * default, snapping is relative to the pointer coordinates. You can
  3601. * change this by setting the
  3602. * [`elementOrigin`](https://github.com/taye/interact.js/pull/72).
  3603. **
  3604. = (boolean | object) `false` if snap is disabled; object with snap properties if snap is enabled
  3605. **
  3606. * or
  3607. **
  3608. - options (object | boolean | null) #optional
  3609. = (Interactable) this Interactable
  3610. > Usage
  3611. | interact(document.querySelector('#thing')).snap({
  3612. | targets: [
  3613. | // snap to this specific point
  3614. | {
  3615. | x: 100,
  3616. | y: 100,
  3617. | range: 25
  3618. | },
  3619. | // give this function the x and y page coords and snap to the object returned
  3620. | function (x, y) {
  3621. | return {
  3622. | x: x,
  3623. | y: (75 + 50 * Math.sin(x * 0.04)),
  3624. | range: 40
  3625. | };
  3626. | },
  3627. | // create a function that snaps to a grid
  3628. | interact.createSnapGrid({
  3629. | x: 50,
  3630. | y: 50,
  3631. | range: 10, // optional
  3632. | offset: { x: 5, y: 10 } // optional
  3633. | })
  3634. | ],
  3635. | // do not snap during normal movement.
  3636. | // Instead, trigger only one snapped move event
  3637. | // immediately before the end event.
  3638. | endOnly: true,
  3639. |
  3640. | relativePoints: [
  3641. | { x: 0, y: 0 }, // snap relative to the top left of the element
  3642. | { x: 1, y: 1 }, // and also to the bottom right
  3643. | ],
  3644. |
  3645. | // offset the snap target coordinates
  3646. | // can be an object with x/y or 'startCoords'
  3647. | offset: { x: 50, y: 50 }
  3648. | }
  3649. | });
  3650. \*/
  3651. snap: function (options) {
  3652. var ret = this.setOptions('snap', options);
  3653. if (ret === this) { return this; }
  3654. return ret.drag;
  3655. },
  3656. setOptions: function (option, options) {
  3657. var actions = options && isArray(options.actions)
  3658. ? options.actions
  3659. : ['drag'];
  3660. var i;
  3661. if (isObject(options) || isBool(options)) {
  3662. for (i = 0; i < actions.length; i++) {
  3663. var action = /resize/.test(actions[i])? 'resize' : actions[i];
  3664. if (!isObject(this.options[action])) { continue; }
  3665. var thisOption = this.options[action][option];
  3666. if (isObject(options)) {
  3667. extend(thisOption, options);
  3668. thisOption.enabled = options.enabled === false? false: true;
  3669. if (option === 'snap') {
  3670. if (thisOption.mode === 'grid') {
  3671. thisOption.targets = [
  3672. interact.createSnapGrid(extend({
  3673. offset: thisOption.gridOffset || { x: 0, y: 0 }
  3674. }, thisOption.grid || {}))
  3675. ];
  3676. }
  3677. else if (thisOption.mode === 'anchor') {
  3678. thisOption.targets = thisOption.anchors;
  3679. }
  3680. else if (thisOption.mode === 'path') {
  3681. thisOption.targets = thisOption.paths;
  3682. }
  3683. if ('elementOrigin' in options) {
  3684. thisOption.relativePoints = [options.elementOrigin];
  3685. }
  3686. }
  3687. }
  3688. else if (isBool(options)) {
  3689. thisOption.enabled = options;
  3690. }
  3691. }
  3692. return this;
  3693. }
  3694. var ret = {},
  3695. allActions = ['drag', 'resize', 'gesture'];
  3696. for (i = 0; i < allActions.length; i++) {
  3697. if (option in defaultOptions[allActions[i]]) {
  3698. ret[allActions[i]] = this.options[allActions[i]][option];
  3699. }
  3700. }
  3701. return ret;
  3702. },
  3703. /*\
  3704. * Interactable.inertia
  3705. [ method ]
  3706. **
  3707. * Deprecated. Add an `inertia` property to the options object passed
  3708. * to @Interactable.draggable or @Interactable.resizable instead.
  3709. *
  3710. * Returns or sets if and how events continue to run after the pointer is released
  3711. **
  3712. = (boolean | object) `false` if inertia is disabled; `object` with inertia properties if inertia is enabled
  3713. **
  3714. * or
  3715. **
  3716. - options (object | boolean | null) #optional
  3717. = (Interactable) this Interactable
  3718. > Usage
  3719. | // enable and use default settings
  3720. | interact(element).inertia(true);
  3721. |
  3722. | // enable and use custom settings
  3723. | interact(element).inertia({
  3724. | // value greater than 0
  3725. | // high values slow the object down more quickly
  3726. | resistance : 16,
  3727. |
  3728. | // the minimum launch speed (pixels per second) that results in inertia start
  3729. | minSpeed : 200,
  3730. |
  3731. | // inertia will stop when the object slows down to this speed
  3732. | endSpeed : 20,
  3733. |
  3734. | // boolean; should actions be resumed when the pointer goes down during inertia
  3735. | allowResume : true,
  3736. |
  3737. | // boolean; should the jump when resuming from inertia be ignored in event.dx/dy
  3738. | zeroResumeDelta: false,
  3739. |
  3740. | // if snap/restrict are set to be endOnly and inertia is enabled, releasing
  3741. | // the pointer without triggering inertia will animate from the release
  3742. | // point to the snaped/restricted point in the given amount of time (ms)
  3743. | smoothEndDuration: 300,
  3744. |
  3745. | // an array of action types that can have inertia (no gesture)
  3746. | actions : ['drag', 'resize']
  3747. | });
  3748. |
  3749. | // reset custom settings and use all defaults
  3750. | interact(element).inertia(null);
  3751. \*/
  3752. inertia: function (options) {
  3753. var ret = this.setOptions('inertia', options);
  3754. if (ret === this) { return this; }
  3755. return ret.drag;
  3756. },
  3757. getAction: function (pointer, event, interaction, element) {
  3758. var action = this.defaultActionChecker(pointer, interaction, element);
  3759. if (this.options.actionChecker) {
  3760. return this.options.actionChecker(pointer, event, action, this, element, interaction);
  3761. }
  3762. return action;
  3763. },
  3764. defaultActionChecker: defaultActionChecker,
  3765. /*\
  3766. * Interactable.actionChecker
  3767. [ method ]
  3768. *
  3769. * Gets or sets the function used to check action to be performed on
  3770. * pointerDown
  3771. *
  3772. - checker (function | null) #optional A function which takes a pointer event, defaultAction string, interactable, element and interaction as parameters and returns an object with name property 'drag' 'resize' or 'gesture' and optionally an `edges` object with boolean 'top', 'left', 'bottom' and right props.
  3773. = (Function | Interactable) The checker function or this Interactable
  3774. *
  3775. | interact('.resize-drag')
  3776. | .resizable(true)
  3777. | .draggable(true)
  3778. | .actionChecker(function (pointer, event, action, interactable, element, interaction) {
  3779. |
  3780. | if (interact.matchesSelector(event.target, '.drag-handle') {
  3781. | // force drag with handle target
  3782. | action.name = drag;
  3783. | }
  3784. | else {
  3785. | // resize from the top and right edges
  3786. | action.name = 'resize';
  3787. | action.edges = { top: true, right: true };
  3788. | }
  3789. |
  3790. | return action;
  3791. | });
  3792. \*/
  3793. actionChecker: function (checker) {
  3794. if (isFunction(checker)) {
  3795. this.options.actionChecker = checker;
  3796. return this;
  3797. }
  3798. if (checker === null) {
  3799. delete this.options.actionChecker;
  3800. return this;
  3801. }
  3802. return this.options.actionChecker;
  3803. },
  3804. /*\
  3805. * Interactable.getRect
  3806. [ method ]
  3807. *
  3808. * The default function to get an Interactables bounding rect. Can be
  3809. * overridden using @Interactable.rectChecker.
  3810. *
  3811. - element (Element) #optional The element to measure.
  3812. = (object) The object's bounding rectangle.
  3813. o {
  3814. o top : 0,
  3815. o left : 0,
  3816. o bottom: 0,
  3817. o right : 0,
  3818. o width : 0,
  3819. o height: 0
  3820. o }
  3821. \*/
  3822. getRect: function rectCheck (element) {
  3823. element = element || this._element;
  3824. if (this.selector && !(isElement(element))) {
  3825. element = this._context.querySelector(this.selector);
  3826. }
  3827. return getElementRect(element);
  3828. },
  3829. /*\
  3830. * Interactable.rectChecker
  3831. [ method ]
  3832. *
  3833. * Returns or sets the function used to calculate the interactable's
  3834. * element's rectangle
  3835. *
  3836. - checker (function) #optional A function which returns this Interactable's bounding rectangle. See @Interactable.getRect
  3837. = (function | object) The checker function or this Interactable
  3838. \*/
  3839. rectChecker: function (checker) {
  3840. if (isFunction(checker)) {
  3841. this.getRect = checker;
  3842. return this;
  3843. }
  3844. if (checker === null) {
  3845. delete this.options.getRect;
  3846. return this;
  3847. }
  3848. return this.getRect;
  3849. },
  3850. /*\
  3851. * Interactable.styleCursor
  3852. [ method ]
  3853. *
  3854. * Returns or sets whether the action that would be performed when the
  3855. * mouse on the element are checked on `mousemove` so that the cursor
  3856. * may be styled appropriately
  3857. *
  3858. - newValue (boolean) #optional
  3859. = (boolean | Interactable) The current setting or this Interactable
  3860. \*/
  3861. styleCursor: function (newValue) {
  3862. if (isBool(newValue)) {
  3863. this.options.styleCursor = newValue;
  3864. return this;
  3865. }
  3866. if (newValue === null) {
  3867. delete this.options.styleCursor;
  3868. return this;
  3869. }
  3870. return this.options.styleCursor;
  3871. },
  3872. /*\
  3873. * Interactable.preventDefault
  3874. [ method ]
  3875. *
  3876. * Returns or sets whether to prevent the browser's default behaviour
  3877. * in response to pointer events. Can be set to:
  3878. * - `'always'` to always prevent
  3879. * - `'never'` to never prevent
  3880. * - `'auto'` to let interact.js try to determine what would be best
  3881. *
  3882. - newValue (string) #optional `true`, `false` or `'auto'`
  3883. = (string | Interactable) The current setting or this Interactable
  3884. \*/
  3885. preventDefault: function (newValue) {
  3886. if (/^(always|never|auto)$/.test(newValue)) {
  3887. this.options.preventDefault = newValue;
  3888. return this;
  3889. }
  3890. if (isBool(newValue)) {
  3891. this.options.preventDefault = newValue? 'always' : 'never';
  3892. return this;
  3893. }
  3894. return this.options.preventDefault;
  3895. },
  3896. /*\
  3897. * Interactable.origin
  3898. [ method ]
  3899. *
  3900. * Gets or sets the origin of the Interactable's element. The x and y
  3901. * of the origin will be subtracted from action event coordinates.
  3902. *
  3903. - origin (object | string) #optional An object eg. { x: 0, y: 0 } or string 'parent', 'self' or any CSS selector
  3904. * OR
  3905. - origin (Element) #optional An HTML or SVG Element whose rect will be used
  3906. **
  3907. = (object) The current origin or this Interactable
  3908. \*/
  3909. origin: function (newValue) {
  3910. if (trySelector(newValue)) {
  3911. this.options.origin = newValue;
  3912. return this;
  3913. }
  3914. else if (isObject(newValue)) {
  3915. this.options.origin = newValue;
  3916. return this;
  3917. }
  3918. return this.options.origin;
  3919. },
  3920. /*\
  3921. * Interactable.deltaSource
  3922. [ method ]
  3923. *
  3924. * Returns or sets the mouse coordinate types used to calculate the
  3925. * movement of the pointer.
  3926. *
  3927. - newValue (string) #optional Use 'client' if you will be scrolling while interacting; Use 'page' if you want autoScroll to work
  3928. = (string | object) The current deltaSource or this Interactable
  3929. \*/
  3930. deltaSource: function (newValue) {
  3931. if (newValue === 'page' || newValue === 'client') {
  3932. this.options.deltaSource = newValue;
  3933. return this;
  3934. }
  3935. return this.options.deltaSource;
  3936. },
  3937. /*\
  3938. * Interactable.restrict
  3939. [ method ]
  3940. **
  3941. * Deprecated. Add a `restrict` property to the options object passed to
  3942. * @Interactable.draggable, @Interactable.resizable or @Interactable.gesturable instead.
  3943. *
  3944. * Returns or sets the rectangles within which actions on this
  3945. * interactable (after snap calculations) are restricted. By default,
  3946. * restricting is relative to the pointer coordinates. You can change
  3947. * this by setting the
  3948. * [`elementRect`](https://github.com/taye/interact.js/pull/72).
  3949. **
  3950. - options (object) #optional an object with keys drag, resize, and/or gesture whose values are rects, Elements, CSS selectors, or 'parent' or 'self'
  3951. = (object) The current restrictions object or this Interactable
  3952. **
  3953. | interact(element).restrict({
  3954. | // the rect will be `interact.getElementRect(element.parentNode)`
  3955. | drag: element.parentNode,
  3956. |
  3957. | // x and y are relative to the the interactable's origin
  3958. | resize: { x: 100, y: 100, width: 200, height: 200 }
  3959. | })
  3960. |
  3961. | interact('.draggable').restrict({
  3962. | // the rect will be the selected element's parent
  3963. | drag: 'parent',
  3964. |
  3965. | // do not restrict during normal movement.
  3966. | // Instead, trigger only one restricted move event
  3967. | // immediately before the end event.
  3968. | endOnly: true,
  3969. |
  3970. | // https://github.com/taye/interact.js/pull/72#issue-41813493
  3971. | elementRect: { top: 0, left: 0, bottom: 1, right: 1 }
  3972. | });
  3973. \*/
  3974. restrict: function (options) {
  3975. if (!isObject(options)) {
  3976. return this.setOptions('restrict', options);
  3977. }
  3978. var actions = ['drag', 'resize', 'gesture'],
  3979. ret;
  3980. for (var i = 0; i < actions.length; i++) {
  3981. var action = actions[i];
  3982. if (action in options) {
  3983. var perAction = extend({
  3984. actions: [action],
  3985. restriction: options[action]
  3986. }, options);
  3987. ret = this.setOptions('restrict', perAction);
  3988. }
  3989. }
  3990. return ret;
  3991. },
  3992. /*\
  3993. * Interactable.context
  3994. [ method ]
  3995. *
  3996. * Gets the selector context Node of the Interactable. The default is `window.document`.
  3997. *
  3998. = (Node) The context Node of this Interactable
  3999. **
  4000. \*/
  4001. context: function () {
  4002. return this._context;
  4003. },
  4004. _context: document,
  4005. /*\
  4006. * Interactable.ignoreFrom
  4007. [ method ]
  4008. *
  4009. * If the target of the `mousedown`, `pointerdown` or `touchstart`
  4010. * event or any of it's parents match the given CSS selector or
  4011. * Element, no drag/resize/gesture is started.
  4012. *
  4013. - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to not ignore any elements
  4014. = (string | Element | object) The current ignoreFrom value or this Interactable
  4015. **
  4016. | interact(element, { ignoreFrom: document.getElementById('no-action') });
  4017. | // or
  4018. | interact(element).ignoreFrom('input, textarea, a');
  4019. \*/
  4020. ignoreFrom: function (newValue) {
  4021. if (trySelector(newValue)) { // CSS selector to match event.target
  4022. this.options.ignoreFrom = newValue;
  4023. return this;
  4024. }
  4025. if (isElement(newValue)) { // specific element
  4026. this.options.ignoreFrom = newValue;
  4027. return this;
  4028. }
  4029. return this.options.ignoreFrom;
  4030. },
  4031. /*\
  4032. * Interactable.allowFrom
  4033. [ method ]
  4034. *
  4035. * A drag/resize/gesture is started only If the target of the
  4036. * `mousedown`, `pointerdown` or `touchstart` event or any of it's
  4037. * parents match the given CSS selector or Element.
  4038. *
  4039. - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to allow from any element
  4040. = (string | Element | object) The current allowFrom value or this Interactable
  4041. **
  4042. | interact(element, { allowFrom: document.getElementById('drag-handle') });
  4043. | // or
  4044. | interact(element).allowFrom('.handle');
  4045. \*/
  4046. allowFrom: function (newValue) {
  4047. if (trySelector(newValue)) { // CSS selector to match event.target
  4048. this.options.allowFrom = newValue;
  4049. return this;
  4050. }
  4051. if (isElement(newValue)) { // specific element
  4052. this.options.allowFrom = newValue;
  4053. return this;
  4054. }
  4055. return this.options.allowFrom;
  4056. },
  4057. /*\
  4058. * Interactable.element
  4059. [ method ]
  4060. *
  4061. * If this is not a selector Interactable, it returns the element this
  4062. * interactable represents
  4063. *
  4064. = (Element) HTML / SVG Element
  4065. \*/
  4066. element: function () {
  4067. return this._element;
  4068. },
  4069. /*\
  4070. * Interactable.fire
  4071. [ method ]
  4072. *
  4073. * Calls listeners for the given InteractEvent type bound globally
  4074. * and directly to this Interactable
  4075. *
  4076. - iEvent (InteractEvent) The InteractEvent object to be fired on this Interactable
  4077. = (Interactable) this Interactable
  4078. \*/
  4079. fire: function (iEvent) {
  4080. if (!(iEvent && iEvent.type) || !contains(eventTypes, iEvent.type)) {
  4081. return this;
  4082. }
  4083. var listeners,
  4084. i,
  4085. len,
  4086. onEvent = 'on' + iEvent.type,
  4087. funcName = '';
  4088. // Interactable#on() listeners
  4089. if (iEvent.type in this._iEvents) {
  4090. listeners = this._iEvents[iEvent.type];
  4091. for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) {
  4092. funcName = listeners[i].name;
  4093. listeners[i](iEvent);
  4094. }
  4095. }
  4096. // interactable.onevent listener
  4097. if (isFunction(this[onEvent])) {
  4098. funcName = this[onEvent].name;
  4099. this[onEvent](iEvent);
  4100. }
  4101. // interact.on() listeners
  4102. if (iEvent.type in globalEvents && (listeners = globalEvents[iEvent.type])) {
  4103. for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) {
  4104. funcName = listeners[i].name;
  4105. listeners[i](iEvent);
  4106. }
  4107. }
  4108. return this;
  4109. },
  4110. /*\
  4111. * Interactable.on
  4112. [ method ]
  4113. *
  4114. * Binds a listener for an InteractEvent or DOM event.
  4115. *
  4116. - eventType (string | array | object) The types of events to listen for
  4117. - listener (function) The function to be called on the given event(s)
  4118. - useCapture (boolean) #optional useCapture flag for addEventListener
  4119. = (object) This Interactable
  4120. \*/
  4121. on: function (eventType, listener, useCapture) {
  4122. var i;
  4123. if (isString(eventType) && eventType.search(' ') !== -1) {
  4124. eventType = eventType.trim().split(/ +/);
  4125. }
  4126. if (isArray(eventType)) {
  4127. for (i = 0; i < eventType.length; i++) {
  4128. this.on(eventType[i], listener, useCapture);
  4129. }
  4130. return this;
  4131. }
  4132. if (isObject(eventType)) {
  4133. for (var prop in eventType) {
  4134. this.on(prop, eventType[prop], listener);
  4135. }
  4136. return this;
  4137. }
  4138. if (eventType === 'wheel') {
  4139. eventType = wheelEvent;
  4140. }
  4141. // convert to boolean
  4142. useCapture = useCapture? true: false;
  4143. if (contains(eventTypes, eventType)) {
  4144. // if this type of event was never bound to this Interactable
  4145. if (!(eventType in this._iEvents)) {
  4146. this._iEvents[eventType] = [listener];
  4147. }
  4148. else {
  4149. this._iEvents[eventType].push(listener);
  4150. }
  4151. }
  4152. // delegated event for selector
  4153. else if (this.selector) {
  4154. if (!delegatedEvents[eventType]) {
  4155. delegatedEvents[eventType] = {
  4156. selectors: [],
  4157. contexts : [],
  4158. listeners: []
  4159. };
  4160. // add delegate listener functions
  4161. for (i = 0; i < documents.length; i++) {
  4162. events.add(documents[i], eventType, delegateListener);
  4163. events.add(documents[i], eventType, delegateUseCapture, true);
  4164. }
  4165. }
  4166. var delegated = delegatedEvents[eventType],
  4167. index;
  4168. for (index = delegated.selectors.length - 1; index >= 0; index--) {
  4169. if (delegated.selectors[index] === this.selector
  4170. && delegated.contexts[index] === this._context) {
  4171. break;
  4172. }
  4173. }
  4174. if (index === -1) {
  4175. index = delegated.selectors.length;
  4176. delegated.selectors.push(this.selector);
  4177. delegated.contexts .push(this._context);
  4178. delegated.listeners.push([]);
  4179. }
  4180. // keep listener and useCapture flag
  4181. delegated.listeners[index].push([listener, useCapture]);
  4182. }
  4183. else {
  4184. events.add(this._element, eventType, listener, useCapture);
  4185. }
  4186. return this;
  4187. },
  4188. /*\
  4189. * Interactable.off
  4190. [ method ]
  4191. *
  4192. * Removes an InteractEvent or DOM event listener
  4193. *
  4194. - eventType (string | array | object) The types of events that were listened for
  4195. - listener (function) The listener function to be removed
  4196. - useCapture (boolean) #optional useCapture flag for removeEventListener
  4197. = (object) This Interactable
  4198. \*/
  4199. off: function (eventType, listener, useCapture) {
  4200. var i;
  4201. if (isString(eventType) && eventType.search(' ') !== -1) {
  4202. eventType = eventType.trim().split(/ +/);
  4203. }
  4204. if (isArray(eventType)) {
  4205. for (i = 0; i < eventType.length; i++) {
  4206. this.off(eventType[i], listener, useCapture);
  4207. }
  4208. return this;
  4209. }
  4210. if (isObject(eventType)) {
  4211. for (var prop in eventType) {
  4212. this.off(prop, eventType[prop], listener);
  4213. }
  4214. return this;
  4215. }
  4216. var eventList,
  4217. index = -1;
  4218. // convert to boolean
  4219. useCapture = useCapture? true: false;
  4220. if (eventType === 'wheel') {
  4221. eventType = wheelEvent;
  4222. }
  4223. // if it is an action event type
  4224. if (contains(eventTypes, eventType)) {
  4225. eventList = this._iEvents[eventType];
  4226. if (eventList && (index = indexOf(eventList, listener)) !== -1) {
  4227. this._iEvents[eventType].splice(index, 1);
  4228. }
  4229. }
  4230. // delegated event
  4231. else if (this.selector) {
  4232. var delegated = delegatedEvents[eventType],
  4233. matchFound = false;
  4234. if (!delegated) { return this; }
  4235. // count from last index of delegated to 0
  4236. for (index = delegated.selectors.length - 1; index >= 0; index--) {
  4237. // look for matching selector and context Node
  4238. if (delegated.selectors[index] === this.selector
  4239. && delegated.contexts[index] === this._context) {
  4240. var listeners = delegated.listeners[index];
  4241. // each item of the listeners array is an array: [function, useCaptureFlag]
  4242. for (i = listeners.length - 1; i >= 0; i--) {
  4243. var fn = listeners[i][0],
  4244. useCap = listeners[i][1];
  4245. // check if the listener functions and useCapture flags match
  4246. if (fn === listener && useCap === useCapture) {
  4247. // remove the listener from the array of listeners
  4248. listeners.splice(i, 1);
  4249. // if all listeners for this interactable have been removed
  4250. // remove the interactable from the delegated arrays
  4251. if (!listeners.length) {
  4252. delegated.selectors.splice(index, 1);
  4253. delegated.contexts .splice(index, 1);
  4254. delegated.listeners.splice(index, 1);
  4255. // remove delegate function from context
  4256. events.remove(this._context, eventType, delegateListener);
  4257. events.remove(this._context, eventType, delegateUseCapture, true);
  4258. // remove the arrays if they are empty
  4259. if (!delegated.selectors.length) {
  4260. delegatedEvents[eventType] = null;
  4261. }
  4262. }
  4263. // only remove one listener
  4264. matchFound = true;
  4265. break;
  4266. }
  4267. }
  4268. if (matchFound) { break; }
  4269. }
  4270. }
  4271. }
  4272. // remove listener from this Interatable's element
  4273. else {
  4274. events.remove(this._element, eventType, listener, useCapture);
  4275. }
  4276. return this;
  4277. },
  4278. /*\
  4279. * Interactable.set
  4280. [ method ]
  4281. *
  4282. * Reset the options of this Interactable
  4283. - options (object) The new settings to apply
  4284. = (object) This Interactable
  4285. \*/
  4286. set: function (options) {
  4287. if (!isObject(options)) {
  4288. options = {};
  4289. }
  4290. this.options = extend({}, defaultOptions.base);
  4291. var i,
  4292. actions = ['drag', 'drop', 'resize', 'gesture'],
  4293. methods = ['draggable', 'dropzone', 'resizable', 'gesturable'],
  4294. perActions = extend(extend({}, defaultOptions.perAction), options[action] || {});
  4295. for (i = 0; i < actions.length; i++) {
  4296. var action = actions[i];
  4297. this.options[action] = extend({}, defaultOptions[action]);
  4298. this.setPerAction(action, perActions);
  4299. this[methods[i]](options[action]);
  4300. }
  4301. var settings = [
  4302. 'accept', 'actionChecker', 'allowFrom', 'deltaSource',
  4303. 'dropChecker', 'ignoreFrom', 'origin', 'preventDefault',
  4304. 'rectChecker', 'styleCursor'
  4305. ];
  4306. for (i = 0, len = settings.length; i < len; i++) {
  4307. var setting = settings[i];
  4308. this.options[setting] = defaultOptions.base[setting];
  4309. if (setting in options) {
  4310. this[setting](options[setting]);
  4311. }
  4312. }
  4313. return this;
  4314. },
  4315. /*\
  4316. * Interactable.unset
  4317. [ method ]
  4318. *
  4319. * Remove this interactable from the list of interactables and remove
  4320. * it's drag, drop, resize and gesture capabilities
  4321. *
  4322. = (object) @interact
  4323. \*/
  4324. unset: function () {
  4325. events.remove(this._element, 'all');
  4326. if (!isString(this.selector)) {
  4327. events.remove(this, 'all');
  4328. if (this.options.styleCursor) {
  4329. this._element.style.cursor = '';
  4330. }
  4331. }
  4332. else {
  4333. // remove delegated events
  4334. for (var type in delegatedEvents) {
  4335. var delegated = delegatedEvents[type];
  4336. for (var i = 0; i < delegated.selectors.length; i++) {
  4337. if (delegated.selectors[i] === this.selector
  4338. && delegated.contexts[i] === this._context) {
  4339. delegated.selectors.splice(i, 1);
  4340. delegated.contexts .splice(i, 1);
  4341. delegated.listeners.splice(i, 1);
  4342. // remove the arrays if they are empty
  4343. if (!delegated.selectors.length) {
  4344. delegatedEvents[type] = null;
  4345. }
  4346. }
  4347. events.remove(this._context, type, delegateListener);
  4348. events.remove(this._context, type, delegateUseCapture, true);
  4349. break;
  4350. }
  4351. }
  4352. }
  4353. this.dropzone(false);
  4354. interactables.splice(indexOf(interactables, this), 1);
  4355. return interact;
  4356. }
  4357. };
  4358. function warnOnce (method, message) {
  4359. var warned = false;
  4360. return function () {
  4361. if (!warned) {
  4362. window.console.warn(message);
  4363. warned = true;
  4364. }
  4365. return method.apply(this, arguments);
  4366. };
  4367. }
  4368. Interactable.prototype.snap = warnOnce(Interactable.prototype.snap,
  4369. 'Interactable#snap is deprecated. See the new documentation for snapping at http://interactjs.io/docs/snapping');
  4370. Interactable.prototype.restrict = warnOnce(Interactable.prototype.restrict,
  4371. 'Interactable#restrict is deprecated. See the new documentation for resticting at http://interactjs.io/docs/restriction');
  4372. Interactable.prototype.inertia = warnOnce(Interactable.prototype.inertia,
  4373. 'Interactable#inertia is deprecated. See the new documentation for inertia at http://interactjs.io/docs/inertia');
  4374. Interactable.prototype.autoScroll = warnOnce(Interactable.prototype.autoScroll,
  4375. 'Interactable#autoScroll is deprecated. See the new documentation for autoScroll at http://interactjs.io/docs/#autoscroll');
  4376. Interactable.prototype.squareResize = warnOnce(Interactable.prototype.squareResize,
  4377. 'Interactable#squareResize is deprecated. See http://interactjs.io/docs/#resize-square');
  4378. Interactable.prototype.accept = warnOnce(Interactable.prototype.accept,
  4379. 'Interactable#accept is deprecated. use Interactable#dropzone({ accept: target }) instead');
  4380. Interactable.prototype.dropChecker = warnOnce(Interactable.prototype.dropChecker,
  4381. 'Interactable#dropChecker is deprecated. use Interactable#dropzone({ dropChecker: checkerFunction }) instead');
  4382. Interactable.prototype.context = warnOnce(Interactable.prototype.context,
  4383. 'Interactable#context as a method is deprecated. It will soon be a DOM Node instead');
  4384. /*\
  4385. * interact.isSet
  4386. [ method ]
  4387. *
  4388. * Check if an element has been set
  4389. - element (Element) The Element being searched for
  4390. = (boolean) Indicates if the element or CSS selector was previously passed to interact
  4391. \*/
  4392. interact.isSet = function(element, options) {
  4393. return interactables.indexOfElement(element, options && options.context) !== -1;
  4394. };
  4395. /*\
  4396. * interact.on
  4397. [ method ]
  4398. *
  4399. * Adds a global listener for an InteractEvent or adds a DOM event to
  4400. * `document`
  4401. *
  4402. - type (string | array | object) The types of events to listen for
  4403. - listener (function) The function to be called on the given event(s)
  4404. - useCapture (boolean) #optional useCapture flag for addEventListener
  4405. = (object) interact
  4406. \*/
  4407. interact.on = function (type, listener, useCapture) {
  4408. if (isString(type) && type.search(' ') !== -1) {
  4409. type = type.trim().split(/ +/);
  4410. }
  4411. if (isArray(type)) {
  4412. for (var i = 0; i < type.length; i++) {
  4413. interact.on(type[i], listener, useCapture);
  4414. }
  4415. return interact;
  4416. }
  4417. if (isObject(type)) {
  4418. for (var prop in type) {
  4419. interact.on(prop, type[prop], listener);
  4420. }
  4421. return interact;
  4422. }
  4423. // if it is an InteractEvent type, add listener to globalEvents
  4424. if (contains(eventTypes, type)) {
  4425. // if this type of event was never bound
  4426. if (!globalEvents[type]) {
  4427. globalEvents[type] = [listener];
  4428. }
  4429. else {
  4430. globalEvents[type].push(listener);
  4431. }
  4432. }
  4433. // If non InteractEvent type, addEventListener to document
  4434. else {
  4435. events.add(document, type, listener, useCapture);
  4436. }
  4437. return interact;
  4438. };
  4439. /*\
  4440. * interact.off
  4441. [ method ]
  4442. *
  4443. * Removes a global InteractEvent listener or DOM event from `document`
  4444. *
  4445. - type (string | array | object) The types of events that were listened for
  4446. - listener (function) The listener function to be removed
  4447. - useCapture (boolean) #optional useCapture flag for removeEventListener
  4448. = (object) interact
  4449. \*/
  4450. interact.off = function (type, listener, useCapture) {
  4451. if (isString(type) && type.search(' ') !== -1) {
  4452. type = type.trim().split(/ +/);
  4453. }
  4454. if (isArray(type)) {
  4455. for (var i = 0; i < type.length; i++) {
  4456. interact.off(type[i], listener, useCapture);
  4457. }
  4458. return interact;
  4459. }
  4460. if (isObject(type)) {
  4461. for (var prop in type) {
  4462. interact.off(prop, type[prop], listener);
  4463. }
  4464. return interact;
  4465. }
  4466. if (!contains(eventTypes, type)) {
  4467. events.remove(document, type, listener, useCapture);
  4468. }
  4469. else {
  4470. var index;
  4471. if (type in globalEvents
  4472. && (index = indexOf(globalEvents[type], listener)) !== -1) {
  4473. globalEvents[type].splice(index, 1);
  4474. }
  4475. }
  4476. return interact;
  4477. };
  4478. /*\
  4479. * interact.enableDragging
  4480. [ method ]
  4481. *
  4482. * Deprecated.
  4483. *
  4484. * Returns or sets whether dragging is enabled for any Interactables
  4485. *
  4486. - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables
  4487. = (boolean | object) The current setting or interact
  4488. \*/
  4489. interact.enableDragging = warnOnce(function (newValue) {
  4490. if (newValue !== null && newValue !== undefined) {
  4491. actionIsEnabled.drag = newValue;
  4492. return interact;
  4493. }
  4494. return actionIsEnabled.drag;
  4495. }, 'interact.enableDragging is deprecated and will soon be removed.');
  4496. /*\
  4497. * interact.enableResizing
  4498. [ method ]
  4499. *
  4500. * Deprecated.
  4501. *
  4502. * Returns or sets whether resizing is enabled for any Interactables
  4503. *
  4504. - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables
  4505. = (boolean | object) The current setting or interact
  4506. \*/
  4507. interact.enableResizing = warnOnce(function (newValue) {
  4508. if (newValue !== null && newValue !== undefined) {
  4509. actionIsEnabled.resize = newValue;
  4510. return interact;
  4511. }
  4512. return actionIsEnabled.resize;
  4513. }, 'interact.enableResizing is deprecated and will soon be removed.');
  4514. /*\
  4515. * interact.enableGesturing
  4516. [ method ]
  4517. *
  4518. * Deprecated.
  4519. *
  4520. * Returns or sets whether gesturing is enabled for any Interactables
  4521. *
  4522. - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables
  4523. = (boolean | object) The current setting or interact
  4524. \*/
  4525. interact.enableGesturing = warnOnce(function (newValue) {
  4526. if (newValue !== null && newValue !== undefined) {
  4527. actionIsEnabled.gesture = newValue;
  4528. return interact;
  4529. }
  4530. return actionIsEnabled.gesture;
  4531. }, 'interact.enableGesturing is deprecated and will soon be removed.');
  4532. interact.eventTypes = eventTypes;
  4533. /*\
  4534. * interact.debug
  4535. [ method ]
  4536. *
  4537. * Returns debugging data
  4538. = (object) An object with properties that outline the current state and expose internal functions and variables
  4539. \*/
  4540. interact.debug = function () {
  4541. var interaction = interactions[0] || new Interaction();
  4542. return {
  4543. interactions : interactions,
  4544. target : interaction.target,
  4545. dragging : interaction.dragging,
  4546. resizing : interaction.resizing,
  4547. gesturing : interaction.gesturing,
  4548. prepared : interaction.prepared,
  4549. matches : interaction.matches,
  4550. matchElements : interaction.matchElements,
  4551. prevCoords : interaction.prevCoords,
  4552. startCoords : interaction.startCoords,
  4553. pointerIds : interaction.pointerIds,
  4554. pointers : interaction.pointers,
  4555. addPointer : listeners.addPointer,
  4556. removePointer : listeners.removePointer,
  4557. recordPointer : listeners.recordPointer,
  4558. snap : interaction.snapStatus,
  4559. restrict : interaction.restrictStatus,
  4560. inertia : interaction.inertiaStatus,
  4561. downTime : interaction.downTimes[0],
  4562. downEvent : interaction.downEvent,
  4563. downPointer : interaction.downPointer,
  4564. prevEvent : interaction.prevEvent,
  4565. Interactable : Interactable,
  4566. interactables : interactables,
  4567. pointerIsDown : interaction.pointerIsDown,
  4568. defaultOptions : defaultOptions,
  4569. defaultActionChecker : defaultActionChecker,
  4570. actionCursors : actionCursors,
  4571. dragMove : listeners.dragMove,
  4572. resizeMove : listeners.resizeMove,
  4573. gestureMove : listeners.gestureMove,
  4574. pointerUp : listeners.pointerUp,
  4575. pointerDown : listeners.pointerDown,
  4576. pointerMove : listeners.pointerMove,
  4577. pointerHover : listeners.pointerHover,
  4578. eventTypes : eventTypes,
  4579. events : events,
  4580. globalEvents : globalEvents,
  4581. delegatedEvents : delegatedEvents,
  4582. prefixedPropREs : prefixedPropREs
  4583. };
  4584. };
  4585. // expose the functions used to calculate multi-touch properties
  4586. interact.getPointerAverage = pointerAverage;
  4587. interact.getTouchBBox = touchBBox;
  4588. interact.getTouchDistance = touchDistance;
  4589. interact.getTouchAngle = touchAngle;
  4590. interact.getElementRect = getElementRect;
  4591. interact.getElementClientRect = getElementClientRect;
  4592. interact.matchesSelector = matchesSelector;
  4593. interact.closest = closest;
  4594. /*\
  4595. * interact.margin
  4596. [ method ]
  4597. *
  4598. * Deprecated. Use `interact(target).resizable({ margin: number });` instead.
  4599. * Returns or sets the margin for autocheck resizing used in
  4600. * @Interactable.getAction. That is the distance from the bottom and right
  4601. * edges of an element clicking in which will start resizing
  4602. *
  4603. - newValue (number) #optional
  4604. = (number | interact) The current margin value or interact
  4605. \*/
  4606. interact.margin = warnOnce(function (newvalue) {
  4607. if (isNumber(newvalue)) {
  4608. margin = newvalue;
  4609. return interact;
  4610. }
  4611. return margin;
  4612. },
  4613. 'interact.margin is deprecated. Use interact(target).resizable({ margin: number }); instead.') ;
  4614. /*\
  4615. * interact.supportsTouch
  4616. [ method ]
  4617. *
  4618. = (boolean) Whether or not the browser supports touch input
  4619. \*/
  4620. interact.supportsTouch = function () {
  4621. return supportsTouch;
  4622. };
  4623. /*\
  4624. * interact.supportsPointerEvent
  4625. [ method ]
  4626. *
  4627. = (boolean) Whether or not the browser supports PointerEvents
  4628. \*/
  4629. interact.supportsPointerEvent = function () {
  4630. return supportsPointerEvent;
  4631. };
  4632. /*\
  4633. * interact.stop
  4634. [ method ]
  4635. *
  4636. * Cancels all interactions (end events are not fired)
  4637. *
  4638. - event (Event) An event on which to call preventDefault()
  4639. = (object) interact
  4640. \*/
  4641. interact.stop = function (event) {
  4642. for (var i = interactions.length - 1; i >= 0; i--) {
  4643. interactions[i].stop(event);
  4644. }
  4645. return interact;
  4646. };
  4647. /*\
  4648. * interact.dynamicDrop
  4649. [ method ]
  4650. *
  4651. * Returns or sets whether the dimensions of dropzone elements are
  4652. * calculated on every dragmove or only on dragstart for the default
  4653. * dropChecker
  4654. *
  4655. - newValue (boolean) #optional True to check on each move. False to check only before start
  4656. = (boolean | interact) The current setting or interact
  4657. \*/
  4658. interact.dynamicDrop = function (newValue) {
  4659. if (isBool(newValue)) {
  4660. //if (dragging && dynamicDrop !== newValue && !newValue) {
  4661. //calcRects(dropzones);
  4662. //}
  4663. dynamicDrop = newValue;
  4664. return interact;
  4665. }
  4666. return dynamicDrop;
  4667. };
  4668. /*\
  4669. * interact.pointerMoveTolerance
  4670. [ method ]
  4671. * Returns or sets the distance the pointer must be moved before an action
  4672. * sequence occurs. This also affects tolerance for tap events.
  4673. *
  4674. - newValue (number) #optional The movement from the start position must be greater than this value
  4675. = (number | Interactable) The current setting or interact
  4676. \*/
  4677. interact.pointerMoveTolerance = function (newValue) {
  4678. if (isNumber(newValue)) {
  4679. pointerMoveTolerance = newValue;
  4680. return this;
  4681. }
  4682. return pointerMoveTolerance;
  4683. };
  4684. /*\
  4685. * interact.maxInteractions
  4686. [ method ]
  4687. **
  4688. * Returns or sets the maximum number of concurrent interactions allowed.
  4689. * By default only 1 interaction is allowed at a time (for backwards
  4690. * compatibility). To allow multiple interactions on the same Interactables
  4691. * and elements, you need to enable it in the draggable, resizable and
  4692. * gesturable `'max'` and `'maxPerElement'` options.
  4693. **
  4694. - newValue (number) #optional Any number. newValue <= 0 means no interactions.
  4695. \*/
  4696. interact.maxInteractions = function (newValue) {
  4697. if (isNumber(newValue)) {
  4698. maxInteractions = newValue;
  4699. return this;
  4700. }
  4701. return maxInteractions;
  4702. };
  4703. interact.createSnapGrid = function (grid) {
  4704. return function (x, y) {
  4705. var offsetX = 0,
  4706. offsetY = 0;
  4707. if (isObject(grid.offset)) {
  4708. offsetX = grid.offset.x;
  4709. offsetY = grid.offset.y;
  4710. }
  4711. var gridx = Math.round((x - offsetX) / grid.x),
  4712. gridy = Math.round((y - offsetY) / grid.y),
  4713. newX = gridx * grid.x + offsetX,
  4714. newY = gridy * grid.y + offsetY;
  4715. return {
  4716. x: newX,
  4717. y: newY,
  4718. range: grid.range
  4719. };
  4720. };
  4721. };
  4722. function endAllInteractions (event) {
  4723. for (var i = 0; i < interactions.length; i++) {
  4724. interactions[i].pointerEnd(event, event);
  4725. }
  4726. }
  4727. function listenToDocument (doc) {
  4728. if (contains(documents, doc)) { return; }
  4729. var win = doc.defaultView || doc.parentWindow;
  4730. // add delegate event listener
  4731. for (var eventType in delegatedEvents) {
  4732. events.add(doc, eventType, delegateListener);
  4733. events.add(doc, eventType, delegateUseCapture, true);
  4734. }
  4735. if (PointerEvent) {
  4736. if (PointerEvent === win.MSPointerEvent) {
  4737. pEventTypes = {
  4738. up: 'MSPointerUp', down: 'MSPointerDown', over: 'mouseover',
  4739. out: 'mouseout', move: 'MSPointerMove', cancel: 'MSPointerCancel' };
  4740. }
  4741. else {
  4742. pEventTypes = {
  4743. up: 'pointerup', down: 'pointerdown', over: 'pointerover',
  4744. out: 'pointerout', move: 'pointermove', cancel: 'pointercancel' };
  4745. }
  4746. events.add(doc, pEventTypes.down , listeners.selectorDown );
  4747. events.add(doc, pEventTypes.move , listeners.pointerMove );
  4748. events.add(doc, pEventTypes.over , listeners.pointerOver );
  4749. events.add(doc, pEventTypes.out , listeners.pointerOut );
  4750. events.add(doc, pEventTypes.up , listeners.pointerUp );
  4751. events.add(doc, pEventTypes.cancel, listeners.pointerCancel);
  4752. // autoscroll
  4753. events.add(doc, pEventTypes.move, listeners.autoScrollMove);
  4754. }
  4755. else {
  4756. events.add(doc, 'mousedown', listeners.selectorDown);
  4757. events.add(doc, 'mousemove', listeners.pointerMove );
  4758. events.add(doc, 'mouseup' , listeners.pointerUp );
  4759. events.add(doc, 'mouseover', listeners.pointerOver );
  4760. events.add(doc, 'mouseout' , listeners.pointerOut );
  4761. events.add(doc, 'touchstart' , listeners.selectorDown );
  4762. events.add(doc, 'touchmove' , listeners.pointerMove );
  4763. events.add(doc, 'touchend' , listeners.pointerUp );
  4764. events.add(doc, 'touchcancel', listeners.pointerCancel);
  4765. // autoscroll
  4766. events.add(doc, 'mousemove', listeners.autoScrollMove);
  4767. events.add(doc, 'touchmove', listeners.autoScrollMove);
  4768. }
  4769. events.add(win, 'blur', endAllInteractions);
  4770. try {
  4771. if (win.frameElement) {
  4772. var parentDoc = win.frameElement.ownerDocument,
  4773. parentWindow = parentDoc.defaultView;
  4774. events.add(parentDoc , 'mouseup' , listeners.pointerEnd);
  4775. events.add(parentDoc , 'touchend' , listeners.pointerEnd);
  4776. events.add(parentDoc , 'touchcancel' , listeners.pointerEnd);
  4777. events.add(parentDoc , 'pointerup' , listeners.pointerEnd);
  4778. events.add(parentDoc , 'MSPointerUp' , listeners.pointerEnd);
  4779. events.add(parentWindow, 'blur' , endAllInteractions );
  4780. }
  4781. }
  4782. catch (error) {
  4783. interact.windowParentError = error;
  4784. }
  4785. // prevent native HTML5 drag on interact.js target elements
  4786. events.add(doc, 'dragstart', function (event) {
  4787. for (var i = 0; i < interactions.length; i++) {
  4788. var interaction = interactions[i];
  4789. if (interaction.element
  4790. && (interaction.element === event.target
  4791. || nodeContains(interaction.element, event.target))) {
  4792. interaction.checkAndPreventDefault(event, interaction.target, interaction.element);
  4793. return;
  4794. }
  4795. }
  4796. });
  4797. if (events.useAttachEvent) {
  4798. // For IE's lack of Event#preventDefault
  4799. events.add(doc, 'selectstart', function (event) {
  4800. var interaction = interactions[0];
  4801. if (interaction.currentAction()) {
  4802. interaction.checkAndPreventDefault(event);
  4803. }
  4804. });
  4805. // For IE's bad dblclick event sequence
  4806. events.add(doc, 'dblclick', doOnInteractions('ie8Dblclick'));
  4807. }
  4808. documents.push(doc);
  4809. }
  4810. listenToDocument(document);
  4811. function indexOf (array, target) {
  4812. for (var i = 0, len = array.length; i < len; i++) {
  4813. if (array[i] === target) {
  4814. return i;
  4815. }
  4816. }
  4817. return -1;
  4818. }
  4819. function contains (array, target) {
  4820. return indexOf(array, target) !== -1;
  4821. }
  4822. function matchesSelector (element, selector, nodeList) {
  4823. if (ie8MatchesSelector) {
  4824. return ie8MatchesSelector(element, selector, nodeList);
  4825. }
  4826. // remove /deep/ from selectors if shadowDOM polyfill is used
  4827. if (window !== realWindow) {
  4828. selector = selector.replace(/\/deep\//g, ' ');
  4829. }
  4830. return element[prefixedMatchesSelector](selector);
  4831. }
  4832. function matchesUpTo (element, selector, limit) {
  4833. while (isElement(element)) {
  4834. if (matchesSelector(element, selector)) {
  4835. return true;
  4836. }
  4837. element = parentElement(element);
  4838. if (element === limit) {
  4839. return matchesSelector(element, selector);
  4840. }
  4841. }
  4842. return false;
  4843. }
  4844. // For IE8's lack of an Element#matchesSelector
  4845. // taken from http://tanalin.com/en/blog/2012/12/matches-selector-ie8/ and modified
  4846. if (!(prefixedMatchesSelector in Element.prototype) || !isFunction(Element.prototype[prefixedMatchesSelector])) {
  4847. ie8MatchesSelector = function (element, selector, elems) {
  4848. elems = elems || element.parentNode.querySelectorAll(selector);
  4849. for (var i = 0, len = elems.length; i < len; i++) {
  4850. if (elems[i] === element) {
  4851. return true;
  4852. }
  4853. }
  4854. return false;
  4855. };
  4856. }
  4857. // requestAnimationFrame polyfill
  4858. (function() {
  4859. var lastTime = 0,
  4860. vendors = ['ms', 'moz', 'webkit', 'o'];
  4861. for(var x = 0; x < vendors.length && !realWindow.requestAnimationFrame; ++x) {
  4862. reqFrame = realWindow[vendors[x]+'RequestAnimationFrame'];
  4863. cancelFrame = realWindow[vendors[x]+'CancelAnimationFrame'] || realWindow[vendors[x]+'CancelRequestAnimationFrame'];
  4864. }
  4865. if (!reqFrame) {
  4866. reqFrame = function(callback) {
  4867. var currTime = new Date().getTime(),
  4868. timeToCall = Math.max(0, 16 - (currTime - lastTime)),
  4869. id = setTimeout(function() { callback(currTime + timeToCall); },
  4870. timeToCall);
  4871. lastTime = currTime + timeToCall;
  4872. return id;
  4873. };
  4874. }
  4875. if (!cancelFrame) {
  4876. cancelFrame = function(id) {
  4877. clearTimeout(id);
  4878. };
  4879. }
  4880. }());
  4881. /* global exports: true, module, define */
  4882. // http://documentcloud.github.io/underscore/docs/underscore.html#section-11
  4883. if (typeof exports !== 'undefined') {
  4884. if (typeof module !== 'undefined' && module.exports) {
  4885. exports = module.exports = interact;
  4886. }
  4887. exports.interact = interact;
  4888. }
  4889. // AMD
  4890. else if (typeof define === 'function' && define.amd) {
  4891. define('interact', function() {
  4892. return interact;
  4893. });
  4894. }
  4895. else {
  4896. realWindow.interact = interact;
  4897. }
  4898. } (typeof window === 'undefined'? undefined : window));