mdui.esm.js 182 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976
  1. /*!
  2. * mdui 1.0.2 (https://mdui.org)
  3. * Copyright 2016-2021 zdhxiong
  4. * Licensed under MIT
  5. */
  6. function isFunction(target) {
  7. return typeof target === 'function';
  8. }
  9. function isString(target) {
  10. return typeof target === 'string';
  11. }
  12. function isNumber(target) {
  13. return typeof target === 'number';
  14. }
  15. function isBoolean(target) {
  16. return typeof target === 'boolean';
  17. }
  18. function isUndefined(target) {
  19. return typeof target === 'undefined';
  20. }
  21. function isNull(target) {
  22. return target === null;
  23. }
  24. function isWindow(target) {
  25. return target instanceof Window;
  26. }
  27. function isDocument(target) {
  28. return target instanceof Document;
  29. }
  30. function isElement(target) {
  31. return target instanceof Element;
  32. }
  33. function isNode(target) {
  34. return target instanceof Node;
  35. }
  36. /**
  37. * 是否是 IE 浏览器
  38. */
  39. function isIE() {
  40. // @ts-ignore
  41. return !!window.document.documentMode;
  42. }
  43. function isArrayLike(target) {
  44. if (isFunction(target) || isWindow(target)) {
  45. return false;
  46. }
  47. return isNumber(target.length);
  48. }
  49. function isObjectLike(target) {
  50. return typeof target === 'object' && target !== null;
  51. }
  52. function toElement(target) {
  53. return isDocument(target) ? target.documentElement : target;
  54. }
  55. /**
  56. * 把用 - 分隔的字符串转为驼峰(如 box-sizing 转换为 boxSizing)
  57. * @param string
  58. */
  59. function toCamelCase(string) {
  60. return string
  61. .replace(/^-ms-/, 'ms-')
  62. .replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
  63. }
  64. /**
  65. * 把驼峰法转为用 - 分隔的字符串(如 boxSizing 转换为 box-sizing)
  66. * @param string
  67. */
  68. function toKebabCase(string) {
  69. return string.replace(/[A-Z]/g, (replacer) => '-' + replacer.toLowerCase());
  70. }
  71. /**
  72. * 获取元素的样式值
  73. * @param element
  74. * @param name
  75. */
  76. function getComputedStyleValue(element, name) {
  77. return window.getComputedStyle(element).getPropertyValue(toKebabCase(name));
  78. }
  79. /**
  80. * 检查元素的 box-sizing 是否是 border-box
  81. * @param element
  82. */
  83. function isBorderBox(element) {
  84. return getComputedStyleValue(element, 'box-sizing') === 'border-box';
  85. }
  86. /**
  87. * 获取元素的 padding, border, margin 宽度(两侧宽度的和,单位为px)
  88. * @param element
  89. * @param direction
  90. * @param extra
  91. */
  92. function getExtraWidth(element, direction, extra) {
  93. const position = direction === 'width' ? ['Left', 'Right'] : ['Top', 'Bottom'];
  94. return [0, 1].reduce((prev, _, index) => {
  95. let prop = extra + position[index];
  96. if (extra === 'border') {
  97. prop += 'Width';
  98. }
  99. return prev + parseFloat(getComputedStyleValue(element, prop) || '0');
  100. }, 0);
  101. }
  102. /**
  103. * 获取元素的样式值,对 width 和 height 进行过处理
  104. * @param element
  105. * @param name
  106. */
  107. function getStyle(element, name) {
  108. // width、height 属性使用 getComputedStyle 得到的值不准确,需要使用 getBoundingClientRect 获取
  109. if (name === 'width' || name === 'height') {
  110. const valueNumber = element.getBoundingClientRect()[name];
  111. if (isBorderBox(element)) {
  112. return `${valueNumber}px`;
  113. }
  114. return `${valueNumber -
  115. getExtraWidth(element, name, 'border') -
  116. getExtraWidth(element, name, 'padding')}px`;
  117. }
  118. return getComputedStyleValue(element, name);
  119. }
  120. /**
  121. * 获取子节点组成的数组
  122. * @param target
  123. * @param parent
  124. */
  125. function getChildNodesArray(target, parent) {
  126. const tempParent = document.createElement(parent);
  127. tempParent.innerHTML = target;
  128. return [].slice.call(tempParent.childNodes);
  129. }
  130. /**
  131. * 始终返回 false 的函数
  132. */
  133. function returnFalse() {
  134. return false;
  135. }
  136. /**
  137. * 数值单位的 CSS 属性
  138. */
  139. const cssNumber = [
  140. 'animationIterationCount',
  141. 'columnCount',
  142. 'fillOpacity',
  143. 'flexGrow',
  144. 'flexShrink',
  145. 'fontWeight',
  146. 'gridArea',
  147. 'gridColumn',
  148. 'gridColumnEnd',
  149. 'gridColumnStart',
  150. 'gridRow',
  151. 'gridRowEnd',
  152. 'gridRowStart',
  153. 'lineHeight',
  154. 'opacity',
  155. 'order',
  156. 'orphans',
  157. 'widows',
  158. 'zIndex',
  159. 'zoom',
  160. ];
  161. function each(target, callback) {
  162. if (isArrayLike(target)) {
  163. for (let i = 0; i < target.length; i += 1) {
  164. if (callback.call(target[i], i, target[i]) === false) {
  165. return target;
  166. }
  167. }
  168. }
  169. else {
  170. const keys = Object.keys(target);
  171. for (let i = 0; i < keys.length; i += 1) {
  172. if (callback.call(target[keys[i]], keys[i], target[keys[i]]) === false) {
  173. return target;
  174. }
  175. }
  176. }
  177. return target;
  178. }
  179. /**
  180. * 为了使用模块扩充,这里不能使用默认导出
  181. */
  182. class JQ {
  183. constructor(arr) {
  184. this.length = 0;
  185. if (!arr) {
  186. return this;
  187. }
  188. each(arr, (i, item) => {
  189. // @ts-ignore
  190. this[i] = item;
  191. });
  192. this.length = arr.length;
  193. return this;
  194. }
  195. }
  196. function get$() {
  197. const $ = function (selector) {
  198. if (!selector) {
  199. return new JQ();
  200. }
  201. // JQ
  202. if (selector instanceof JQ) {
  203. return selector;
  204. }
  205. // function
  206. if (isFunction(selector)) {
  207. if (/complete|loaded|interactive/.test(document.readyState) &&
  208. document.body) {
  209. selector.call(document, $);
  210. }
  211. else {
  212. document.addEventListener('DOMContentLoaded', () => selector.call(document, $), false);
  213. }
  214. return new JQ([document]);
  215. }
  216. // String
  217. if (isString(selector)) {
  218. const html = selector.trim();
  219. // 根据 HTML 字符串创建 JQ 对象
  220. if (html[0] === '<' && html[html.length - 1] === '>') {
  221. let toCreate = 'div';
  222. const tags = {
  223. li: 'ul',
  224. tr: 'tbody',
  225. td: 'tr',
  226. th: 'tr',
  227. tbody: 'table',
  228. option: 'select',
  229. };
  230. each(tags, (childTag, parentTag) => {
  231. if (html.indexOf(`<${childTag}`) === 0) {
  232. toCreate = parentTag;
  233. return false;
  234. }
  235. return;
  236. });
  237. return new JQ(getChildNodesArray(html, toCreate));
  238. }
  239. // 根据 CSS 选择器创建 JQ 对象
  240. const isIdSelector = selector[0] === '#' && !selector.match(/[ .<>:~]/);
  241. if (!isIdSelector) {
  242. return new JQ(document.querySelectorAll(selector));
  243. }
  244. const element = document.getElementById(selector.slice(1));
  245. if (element) {
  246. return new JQ([element]);
  247. }
  248. return new JQ();
  249. }
  250. if (isArrayLike(selector) && !isNode(selector)) {
  251. return new JQ(selector);
  252. }
  253. return new JQ([selector]);
  254. };
  255. $.fn = JQ.prototype;
  256. return $;
  257. }
  258. const $ = get$();
  259. // 避免页面加载完后直接执行css动画
  260. // https://css-tricks.com/transitions-only-after-page-load/
  261. setTimeout(() => $('body').addClass('mdui-loaded'));
  262. const mdui = {
  263. $: $,
  264. };
  265. $.fn.each = function (callback) {
  266. return each(this, callback);
  267. };
  268. /**
  269. * 检查 container 元素内是否包含 contains 元素
  270. * @param container 父元素
  271. * @param contains 子元素
  272. * @example
  273. ```js
  274. contains( document, document.body ); // true
  275. contains( document.getElementById('test'), document ); // false
  276. contains( $('.container').get(0), $('.contains').get(0) ); // false
  277. ```
  278. */
  279. function contains(container, contains) {
  280. return container !== contains && toElement(container).contains(contains);
  281. }
  282. /**
  283. * 把第二个数组的元素追加到第一个数组中,并返回合并后的数组
  284. * @param first 第一个数组
  285. * @param second 该数组的元素将被追加到第一个数组中
  286. * @example
  287. ```js
  288. merge( [ 0, 1, 2 ], [ 2, 3, 4 ] )
  289. // [ 0, 1, 2, 2, 3, 4 ]
  290. ```
  291. */
  292. function merge(first, second) {
  293. each(second, (_, value) => {
  294. first.push(value);
  295. });
  296. return first;
  297. }
  298. $.fn.get = function (index) {
  299. return index === undefined
  300. ? [].slice.call(this)
  301. : this[index >= 0 ? index : index + this.length];
  302. };
  303. $.fn.find = function (selector) {
  304. const foundElements = [];
  305. this.each((_, element) => {
  306. merge(foundElements, $(element.querySelectorAll(selector)).get());
  307. });
  308. return new JQ(foundElements);
  309. };
  310. // 存储事件
  311. const handlers = {};
  312. // 元素ID
  313. let mduiElementId = 1;
  314. /**
  315. * 为元素赋予一个唯一的ID
  316. */
  317. function getElementId(element) {
  318. const key = '_mduiEventId';
  319. // @ts-ignore
  320. if (!element[key]) {
  321. // @ts-ignore
  322. element[key] = ++mduiElementId;
  323. }
  324. // @ts-ignore
  325. return element[key];
  326. }
  327. /**
  328. * 解析事件名中的命名空间
  329. */
  330. function parse(type) {
  331. const parts = type.split('.');
  332. return {
  333. type: parts[0],
  334. ns: parts.slice(1).sort().join(' '),
  335. };
  336. }
  337. /**
  338. * 命名空间匹配规则
  339. */
  340. function matcherFor(ns) {
  341. return new RegExp('(?:^| )' + ns.replace(' ', ' .* ?') + '(?: |$)');
  342. }
  343. /**
  344. * 获取匹配的事件
  345. * @param element
  346. * @param type
  347. * @param func
  348. * @param selector
  349. */
  350. function getHandlers(element, type, func, selector) {
  351. const event = parse(type);
  352. return (handlers[getElementId(element)] || []).filter((handler) => handler &&
  353. (!event.type || handler.type === event.type) &&
  354. (!event.ns || matcherFor(event.ns).test(handler.ns)) &&
  355. (!func || getElementId(handler.func) === getElementId(func)) &&
  356. (!selector || handler.selector === selector));
  357. }
  358. /**
  359. * 添加事件监听
  360. * @param element
  361. * @param types
  362. * @param func
  363. * @param data
  364. * @param selector
  365. */
  366. function add(element, types, func, data, selector) {
  367. const elementId = getElementId(element);
  368. if (!handlers[elementId]) {
  369. handlers[elementId] = [];
  370. }
  371. // 传入 data.useCapture 来设置 useCapture: true
  372. let useCapture = false;
  373. if (isObjectLike(data) && data.useCapture) {
  374. useCapture = true;
  375. }
  376. types.split(' ').forEach((type) => {
  377. if (!type) {
  378. return;
  379. }
  380. const event = parse(type);
  381. function callFn(e, elem) {
  382. // 因为鼠标事件模拟事件的 detail 属性是只读的,因此在 e._detail 中存储参数
  383. const result = func.apply(elem,
  384. // @ts-ignore
  385. e._detail === undefined ? [e] : [e].concat(e._detail));
  386. if (result === false) {
  387. e.preventDefault();
  388. e.stopPropagation();
  389. }
  390. }
  391. function proxyFn(e) {
  392. // @ts-ignore
  393. if (e._ns && !matcherFor(e._ns).test(event.ns)) {
  394. return;
  395. }
  396. // @ts-ignore
  397. e._data = data;
  398. if (selector) {
  399. // 事件代理
  400. $(element)
  401. .find(selector)
  402. .get()
  403. .reverse()
  404. .forEach((elem) => {
  405. if (elem === e.target ||
  406. contains(elem, e.target)) {
  407. callFn(e, elem);
  408. }
  409. });
  410. }
  411. else {
  412. // 不使用事件代理
  413. callFn(e, element);
  414. }
  415. }
  416. const handler = {
  417. type: event.type,
  418. ns: event.ns,
  419. func,
  420. selector,
  421. id: handlers[elementId].length,
  422. proxy: proxyFn,
  423. };
  424. handlers[elementId].push(handler);
  425. element.addEventListener(handler.type, proxyFn, useCapture);
  426. });
  427. }
  428. /**
  429. * 移除事件监听
  430. * @param element
  431. * @param types
  432. * @param func
  433. * @param selector
  434. */
  435. function remove(element, types, func, selector) {
  436. const handlersInElement = handlers[getElementId(element)] || [];
  437. const removeEvent = (handler) => {
  438. delete handlersInElement[handler.id];
  439. element.removeEventListener(handler.type, handler.proxy, false);
  440. };
  441. if (!types) {
  442. handlersInElement.forEach((handler) => removeEvent(handler));
  443. }
  444. else {
  445. types.split(' ').forEach((type) => {
  446. if (type) {
  447. getHandlers(element, type, func, selector).forEach((handler) => removeEvent(handler));
  448. }
  449. });
  450. }
  451. }
  452. $.fn.trigger = function (type, extraParameters) {
  453. const event = parse(type);
  454. let eventObject;
  455. const eventParams = {
  456. bubbles: true,
  457. cancelable: true,
  458. };
  459. const isMouseEvent = ['click', 'mousedown', 'mouseup', 'mousemove'].indexOf(event.type) > -1;
  460. if (isMouseEvent) {
  461. // Note: MouseEvent 无法传入 detail 参数
  462. eventObject = new MouseEvent(event.type, eventParams);
  463. }
  464. else {
  465. eventParams.detail = extraParameters;
  466. eventObject = new CustomEvent(event.type, eventParams);
  467. }
  468. // @ts-ignore
  469. eventObject._detail = extraParameters;
  470. // @ts-ignore
  471. eventObject._ns = event.ns;
  472. return this.each(function () {
  473. this.dispatchEvent(eventObject);
  474. });
  475. };
  476. function extend(target, object1, ...objectN) {
  477. objectN.unshift(object1);
  478. each(objectN, (_, object) => {
  479. each(object, (prop, value) => {
  480. if (!isUndefined(value)) {
  481. target[prop] = value;
  482. }
  483. });
  484. });
  485. return target;
  486. }
  487. /**
  488. * 将数组或对象序列化,序列化后的字符串可作为 URL 查询字符串使用
  489. *
  490. * 若传入数组,则格式必须和 serializeArray 方法的返回值一样
  491. * @param obj 对象或数组
  492. * @example
  493. ```js
  494. param({ width: 1680, height: 1050 });
  495. // width=1680&height=1050
  496. ```
  497. * @example
  498. ```js
  499. param({ foo: { one: 1, two: 2 }})
  500. // foo[one]=1&foo[two]=2
  501. ```
  502. * @example
  503. ```js
  504. param({ids: [1, 2, 3]})
  505. // ids[]=1&ids[]=2&ids[]=3
  506. ```
  507. * @example
  508. ```js
  509. param([
  510. {"name":"name","value":"mdui"},
  511. {"name":"password","value":"123456"}
  512. ])
  513. // name=mdui&password=123456
  514. ```
  515. */
  516. function param(obj) {
  517. if (!isObjectLike(obj) && !Array.isArray(obj)) {
  518. return '';
  519. }
  520. const args = [];
  521. function destructure(key, value) {
  522. let keyTmp;
  523. if (isObjectLike(value)) {
  524. each(value, (i, v) => {
  525. if (Array.isArray(value) && !isObjectLike(v)) {
  526. keyTmp = '';
  527. }
  528. else {
  529. keyTmp = i;
  530. }
  531. destructure(`${key}[${keyTmp}]`, v);
  532. });
  533. }
  534. else {
  535. if (value == null || value === '') {
  536. keyTmp = '=';
  537. }
  538. else {
  539. keyTmp = `=${encodeURIComponent(value)}`;
  540. }
  541. args.push(encodeURIComponent(key) + keyTmp);
  542. }
  543. }
  544. if (Array.isArray(obj)) {
  545. each(obj, function () {
  546. destructure(this.name, this.value);
  547. });
  548. }
  549. else {
  550. each(obj, destructure);
  551. }
  552. return args.join('&');
  553. }
  554. // 全局配置参数
  555. const globalOptions = {};
  556. // 全局事件名
  557. const ajaxEvents = {
  558. ajaxStart: 'start.mdui.ajax',
  559. ajaxSuccess: 'success.mdui.ajax',
  560. ajaxError: 'error.mdui.ajax',
  561. ajaxComplete: 'complete.mdui.ajax',
  562. };
  563. /**
  564. * 判断此请求方法是否通过查询字符串提交参数
  565. * @param method 请求方法,大写
  566. */
  567. function isQueryStringData(method) {
  568. return ['GET', 'HEAD'].indexOf(method) >= 0;
  569. }
  570. /**
  571. * 添加参数到 URL 上,且 URL 中不存在 ? 时,自动把第一个 & 替换为 ?
  572. * @param url
  573. * @param query
  574. */
  575. function appendQuery(url, query) {
  576. return `${url}&${query}`.replace(/[&?]{1,2}/, '?');
  577. }
  578. /**
  579. * 合并请求参数,参数优先级:options > globalOptions > defaults
  580. * @param options
  581. */
  582. function mergeOptions(options) {
  583. // 默认参数
  584. const defaults = {
  585. url: '',
  586. method: 'GET',
  587. data: '',
  588. processData: true,
  589. async: true,
  590. cache: true,
  591. username: '',
  592. password: '',
  593. headers: {},
  594. xhrFields: {},
  595. statusCode: {},
  596. dataType: 'text',
  597. contentType: 'application/x-www-form-urlencoded',
  598. timeout: 0,
  599. global: true,
  600. };
  601. // globalOptions 中的回调函数不合并
  602. each(globalOptions, (key, value) => {
  603. const callbacks = [
  604. 'beforeSend',
  605. 'success',
  606. 'error',
  607. 'complete',
  608. 'statusCode',
  609. ];
  610. // @ts-ignore
  611. if (callbacks.indexOf(key) < 0 && !isUndefined(value)) {
  612. defaults[key] = value;
  613. }
  614. });
  615. return extend({}, defaults, options);
  616. }
  617. /**
  618. * 发送 ajax 请求
  619. * @param options
  620. * @example
  621. ```js
  622. ajax({
  623. method: "POST",
  624. url: "some.php",
  625. data: { name: "John", location: "Boston" }
  626. }).then(function( msg ) {
  627. alert( "Data Saved: " + msg );
  628. });
  629. ```
  630. */
  631. function ajax(options) {
  632. // 是否已取消请求
  633. let isCanceled = false;
  634. // 事件参数
  635. const eventParams = {};
  636. // 参数合并
  637. const mergedOptions = mergeOptions(options);
  638. let url = mergedOptions.url || window.location.toString();
  639. const method = mergedOptions.method.toUpperCase();
  640. let data = mergedOptions.data;
  641. const processData = mergedOptions.processData;
  642. const async = mergedOptions.async;
  643. const cache = mergedOptions.cache;
  644. const username = mergedOptions.username;
  645. const password = mergedOptions.password;
  646. const headers = mergedOptions.headers;
  647. const xhrFields = mergedOptions.xhrFields;
  648. const statusCode = mergedOptions.statusCode;
  649. const dataType = mergedOptions.dataType;
  650. const contentType = mergedOptions.contentType;
  651. const timeout = mergedOptions.timeout;
  652. const global = mergedOptions.global;
  653. // 需要发送的数据
  654. // GET/HEAD 请求和 processData 为 true 时,转换为查询字符串格式,特殊格式不转换
  655. if (data &&
  656. (isQueryStringData(method) || processData) &&
  657. !isString(data) &&
  658. !(data instanceof ArrayBuffer) &&
  659. !(data instanceof Blob) &&
  660. !(data instanceof Document) &&
  661. !(data instanceof FormData)) {
  662. data = param(data);
  663. }
  664. // 对于 GET、HEAD 类型的请求,把 data 数据添加到 URL 中
  665. if (data && isQueryStringData(method)) {
  666. // 查询字符串拼接到 URL 中
  667. url = appendQuery(url, data);
  668. data = null;
  669. }
  670. /**
  671. * 触发事件和回调函数
  672. * @param event
  673. * @param params
  674. * @param callback
  675. * @param args
  676. */
  677. function trigger(event, params, callback, ...args) {
  678. // 触发全局事件
  679. if (global) {
  680. $(document).trigger(event, params);
  681. }
  682. // 触发 ajax 回调和事件
  683. let result1;
  684. let result2;
  685. if (callback) {
  686. // 全局回调
  687. if (callback in globalOptions) {
  688. // @ts-ignore
  689. result1 = globalOptions[callback](...args);
  690. }
  691. // 自定义回调
  692. if (mergedOptions[callback]) {
  693. // @ts-ignore
  694. result2 = mergedOptions[callback](...args);
  695. }
  696. // beforeSend 回调返回 false 时取消 ajax 请求
  697. if (callback === 'beforeSend' &&
  698. (result1 === false || result2 === false)) {
  699. isCanceled = true;
  700. }
  701. }
  702. }
  703. // XMLHttpRequest 请求
  704. function XHR() {
  705. let textStatus;
  706. return new Promise((resolve, reject) => {
  707. // GET/HEAD 请求的缓存处理
  708. if (isQueryStringData(method) && !cache) {
  709. url = appendQuery(url, `_=${Date.now()}`);
  710. }
  711. // 创建 XHR
  712. const xhr = new XMLHttpRequest();
  713. xhr.open(method, url, async, username, password);
  714. if (contentType ||
  715. (data && !isQueryStringData(method) && contentType !== false)) {
  716. xhr.setRequestHeader('Content-Type', contentType);
  717. }
  718. // 设置 Accept
  719. if (dataType === 'json') {
  720. xhr.setRequestHeader('Accept', 'application/json, text/javascript');
  721. }
  722. // 添加 headers
  723. if (headers) {
  724. each(headers, (key, value) => {
  725. // undefined 值不发送,string 和 null 需要发送
  726. if (!isUndefined(value)) {
  727. xhr.setRequestHeader(key, value + ''); // 把 null 转换成字符串
  728. }
  729. });
  730. }
  731. // 检查是否是跨域请求,跨域请求时不添加 X-Requested-With
  732. const crossDomain = /^([\w-]+:)?\/\/([^/]+)/.test(url) &&
  733. RegExp.$2 !== window.location.host;
  734. if (!crossDomain) {
  735. xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
  736. }
  737. if (xhrFields) {
  738. each(xhrFields, (key, value) => {
  739. // @ts-ignore
  740. xhr[key] = value;
  741. });
  742. }
  743. eventParams.xhr = xhr;
  744. eventParams.options = mergedOptions;
  745. let xhrTimeout;
  746. xhr.onload = function () {
  747. if (xhrTimeout) {
  748. clearTimeout(xhrTimeout);
  749. }
  750. // AJAX 返回的 HTTP 响应码是否表示成功
  751. const isHttpStatusSuccess = (xhr.status >= 200 && xhr.status < 300) ||
  752. xhr.status === 304 ||
  753. xhr.status === 0;
  754. let responseData;
  755. if (isHttpStatusSuccess) {
  756. if (xhr.status === 204 || method === 'HEAD') {
  757. textStatus = 'nocontent';
  758. }
  759. else if (xhr.status === 304) {
  760. textStatus = 'notmodified';
  761. }
  762. else {
  763. textStatus = 'success';
  764. }
  765. if (dataType === 'json') {
  766. try {
  767. responseData =
  768. method === 'HEAD' ? undefined : JSON.parse(xhr.responseText);
  769. eventParams.data = responseData;
  770. }
  771. catch (err) {
  772. textStatus = 'parsererror';
  773. trigger(ajaxEvents.ajaxError, eventParams, 'error', xhr, textStatus);
  774. reject(new Error(textStatus));
  775. }
  776. if (textStatus !== 'parsererror') {
  777. trigger(ajaxEvents.ajaxSuccess, eventParams, 'success', responseData, textStatus, xhr);
  778. resolve(responseData);
  779. }
  780. }
  781. else {
  782. responseData =
  783. method === 'HEAD'
  784. ? undefined
  785. : xhr.responseType === 'text' || xhr.responseType === ''
  786. ? xhr.responseText
  787. : xhr.response;
  788. eventParams.data = responseData;
  789. trigger(ajaxEvents.ajaxSuccess, eventParams, 'success', responseData, textStatus, xhr);
  790. resolve(responseData);
  791. }
  792. }
  793. else {
  794. textStatus = 'error';
  795. trigger(ajaxEvents.ajaxError, eventParams, 'error', xhr, textStatus);
  796. reject(new Error(textStatus));
  797. }
  798. // statusCode
  799. each([globalOptions.statusCode, statusCode], (_, func) => {
  800. if (func && func[xhr.status]) {
  801. if (isHttpStatusSuccess) {
  802. func[xhr.status](responseData, textStatus, xhr);
  803. }
  804. else {
  805. func[xhr.status](xhr, textStatus);
  806. }
  807. }
  808. });
  809. trigger(ajaxEvents.ajaxComplete, eventParams, 'complete', xhr, textStatus);
  810. };
  811. xhr.onerror = function () {
  812. if (xhrTimeout) {
  813. clearTimeout(xhrTimeout);
  814. }
  815. trigger(ajaxEvents.ajaxError, eventParams, 'error', xhr, xhr.statusText);
  816. trigger(ajaxEvents.ajaxComplete, eventParams, 'complete', xhr, 'error');
  817. reject(new Error(xhr.statusText));
  818. };
  819. xhr.onabort = function () {
  820. let statusText = 'abort';
  821. if (xhrTimeout) {
  822. statusText = 'timeout';
  823. clearTimeout(xhrTimeout);
  824. }
  825. trigger(ajaxEvents.ajaxError, eventParams, 'error', xhr, statusText);
  826. trigger(ajaxEvents.ajaxComplete, eventParams, 'complete', xhr, statusText);
  827. reject(new Error(statusText));
  828. };
  829. // ajax start 回调
  830. trigger(ajaxEvents.ajaxStart, eventParams, 'beforeSend', xhr);
  831. if (isCanceled) {
  832. reject(new Error('cancel'));
  833. return;
  834. }
  835. // Timeout
  836. if (timeout > 0) {
  837. xhrTimeout = setTimeout(() => {
  838. xhr.abort();
  839. }, timeout);
  840. }
  841. // 发送 XHR
  842. xhr.send(data);
  843. });
  844. }
  845. return XHR();
  846. }
  847. $.ajax = ajax;
  848. /**
  849. * 为 Ajax 请求设置全局配置参数
  850. * @param options 键值对参数
  851. * @example
  852. ```js
  853. ajaxSetup({
  854. dataType: 'json',
  855. method: 'POST',
  856. });
  857. ```
  858. */
  859. function ajaxSetup(options) {
  860. return extend(globalOptions, options);
  861. }
  862. $.ajaxSetup = ajaxSetup;
  863. $.contains = contains;
  864. const dataNS = '_mduiElementDataStorage';
  865. /**
  866. * 在元素上设置键值对数据
  867. * @param element
  868. * @param object
  869. */
  870. function setObjectToElement(element, object) {
  871. // @ts-ignore
  872. if (!element[dataNS]) {
  873. // @ts-ignore
  874. element[dataNS] = {};
  875. }
  876. each(object, (key, value) => {
  877. // @ts-ignore
  878. element[dataNS][toCamelCase(key)] = value;
  879. });
  880. }
  881. function data(element, key, value) {
  882. // 根据键值对设置值
  883. // data(element, { 'key' : 'value' })
  884. if (isObjectLike(key)) {
  885. setObjectToElement(element, key);
  886. return key;
  887. }
  888. // 根据 key、value 设置值
  889. // data(element, 'key', 'value')
  890. if (!isUndefined(value)) {
  891. setObjectToElement(element, { [key]: value });
  892. return value;
  893. }
  894. // 获取所有值
  895. // data(element)
  896. if (isUndefined(key)) {
  897. // @ts-ignore
  898. return element[dataNS] ? element[dataNS] : {};
  899. }
  900. // 从 dataNS 中获取指定值
  901. // data(element, 'key')
  902. key = toCamelCase(key);
  903. // @ts-ignore
  904. if (element[dataNS] && key in element[dataNS]) {
  905. // @ts-ignore
  906. return element[dataNS][key];
  907. }
  908. return undefined;
  909. }
  910. $.data = data;
  911. $.each = each;
  912. $.extend = function (...objectN) {
  913. if (objectN.length === 1) {
  914. each(objectN[0], (prop, value) => {
  915. this[prop] = value;
  916. });
  917. return this;
  918. }
  919. return extend(objectN.shift(), objectN.shift(), ...objectN);
  920. };
  921. function map(elements, callback) {
  922. let value;
  923. const ret = [];
  924. each(elements, (i, element) => {
  925. value = callback.call(window, element, i);
  926. if (value != null) {
  927. ret.push(value);
  928. }
  929. });
  930. return [].concat(...ret);
  931. }
  932. $.map = map;
  933. $.merge = merge;
  934. $.param = param;
  935. /**
  936. * 移除指定元素上存放的数据
  937. * @param element 存放数据的元素
  938. * @param name
  939. * 数据键名
  940. *
  941. * 若未指定键名,将移除元素上所有数据
  942. *
  943. * 多个键名可以用空格分隔,或者用数组表示多个键名
  944. @example
  945. ```js
  946. // 移除元素上键名为 name 的数据
  947. removeData(document.body, 'name');
  948. ```
  949. * @example
  950. ```js
  951. // 移除元素上键名为 name1 和 name2 的数据
  952. removeData(document.body, 'name1 name2');
  953. ```
  954. * @example
  955. ```js
  956. // 移除元素上键名为 name1 和 name2 的数据
  957. removeData(document.body, ['name1', 'name2']);
  958. ```
  959. * @example
  960. ```js
  961. // 移除元素上所有数据
  962. removeData(document.body);
  963. ```
  964. */
  965. function removeData(element, name) {
  966. // @ts-ignore
  967. if (!element[dataNS]) {
  968. return;
  969. }
  970. const remove = (nameItem) => {
  971. nameItem = toCamelCase(nameItem);
  972. // @ts-ignore
  973. if (element[dataNS][nameItem]) {
  974. // @ts-ignore
  975. element[dataNS][nameItem] = null;
  976. // @ts-ignore
  977. delete element[dataNS][nameItem];
  978. }
  979. };
  980. if (isUndefined(name)) {
  981. // @ts-ignore
  982. element[dataNS] = null;
  983. // @ts-ignore
  984. delete element[dataNS];
  985. // @ts-ignore
  986. }
  987. else if (isString(name)) {
  988. name
  989. .split(' ')
  990. .filter((nameItem) => nameItem)
  991. .forEach((nameItem) => remove(nameItem));
  992. }
  993. else {
  994. each(name, (_, nameItem) => remove(nameItem));
  995. }
  996. }
  997. $.removeData = removeData;
  998. /**
  999. * 过滤掉数组中的重复元素
  1000. * @param arr 数组
  1001. * @example
  1002. ```js
  1003. unique([1, 2, 12, 3, 2, 1, 2, 1, 1]);
  1004. // [1, 2, 12, 3]
  1005. ```
  1006. */
  1007. function unique(arr) {
  1008. const result = [];
  1009. each(arr, (_, val) => {
  1010. if (result.indexOf(val) === -1) {
  1011. result.push(val);
  1012. }
  1013. });
  1014. return result;
  1015. }
  1016. $.unique = unique;
  1017. $.fn.add = function (selector) {
  1018. return new JQ(unique(merge(this.get(), $(selector).get())));
  1019. };
  1020. each(['add', 'remove', 'toggle'], (_, name) => {
  1021. $.fn[`${name}Class`] = function (className) {
  1022. if (name === 'remove' && !arguments.length) {
  1023. return this.each((_, element) => {
  1024. element.setAttribute('class', '');
  1025. });
  1026. }
  1027. return this.each((i, element) => {
  1028. if (!isElement(element)) {
  1029. return;
  1030. }
  1031. const classes = (isFunction(className)
  1032. ? className.call(element, i, element.getAttribute('class') || '')
  1033. : className)
  1034. .split(' ')
  1035. .filter((name) => name);
  1036. each(classes, (_, cls) => {
  1037. element.classList[name](cls);
  1038. });
  1039. });
  1040. };
  1041. });
  1042. each(['insertBefore', 'insertAfter'], (nameIndex, name) => {
  1043. $.fn[name] = function (target) {
  1044. const $element = nameIndex ? $(this.get().reverse()) : this; // 顺序和 jQuery 保持一致
  1045. const $target = $(target);
  1046. const result = [];
  1047. $target.each((index, target) => {
  1048. if (!target.parentNode) {
  1049. return;
  1050. }
  1051. $element.each((_, element) => {
  1052. const newItem = index
  1053. ? element.cloneNode(true)
  1054. : element;
  1055. const existingItem = nameIndex ? target.nextSibling : target;
  1056. result.push(newItem);
  1057. target.parentNode.insertBefore(newItem, existingItem);
  1058. });
  1059. });
  1060. return $(nameIndex ? result.reverse() : result);
  1061. };
  1062. });
  1063. /**
  1064. * 是否不是 HTML 字符串(包裹在 <> 中)
  1065. * @param target
  1066. */
  1067. function isPlainText(target) {
  1068. return (isString(target) && (target[0] !== '<' || target[target.length - 1] !== '>'));
  1069. }
  1070. each(['before', 'after'], (nameIndex, name) => {
  1071. $.fn[name] = function (...args) {
  1072. // after 方法,多个参数需要按参数顺序添加到元素后面,所以需要将参数顺序反向处理
  1073. if (nameIndex === 1) {
  1074. args = args.reverse();
  1075. }
  1076. return this.each((index, element) => {
  1077. const targets = isFunction(args[0])
  1078. ? [args[0].call(element, index, element.innerHTML)]
  1079. : args;
  1080. each(targets, (_, target) => {
  1081. let $target;
  1082. if (isPlainText(target)) {
  1083. $target = $(getChildNodesArray(target, 'div'));
  1084. }
  1085. else if (index && isElement(target)) {
  1086. $target = $(target.cloneNode(true));
  1087. }
  1088. else {
  1089. $target = $(target);
  1090. }
  1091. $target[nameIndex ? 'insertAfter' : 'insertBefore'](element);
  1092. });
  1093. });
  1094. };
  1095. });
  1096. $.fn.off = function (types, selector, callback) {
  1097. // types 是对象
  1098. if (isObjectLike(types)) {
  1099. each(types, (type, fn) => {
  1100. // this.off('click', undefined, function () {})
  1101. // this.off('click', '.box', function () {})
  1102. this.off(type, selector, fn);
  1103. });
  1104. return this;
  1105. }
  1106. // selector 不存在
  1107. if (selector === false || isFunction(selector)) {
  1108. callback = selector;
  1109. selector = undefined;
  1110. // this.off('click', undefined, function () {})
  1111. }
  1112. // callback 传入 `false`,相当于 `return false`
  1113. if (callback === false) {
  1114. callback = returnFalse;
  1115. }
  1116. return this.each(function () {
  1117. remove(this, types, callback, selector);
  1118. });
  1119. };
  1120. $.fn.on = function (types, selector, data, callback, one) {
  1121. // types 可以是 type/func 对象
  1122. if (isObjectLike(types)) {
  1123. // (types-Object, selector, data)
  1124. if (!isString(selector)) {
  1125. // (types-Object, data)
  1126. data = data || selector;
  1127. selector = undefined;
  1128. }
  1129. each(types, (type, fn) => {
  1130. // selector 和 data 都可能是 undefined
  1131. // @ts-ignore
  1132. this.on(type, selector, data, fn, one);
  1133. });
  1134. return this;
  1135. }
  1136. if (data == null && callback == null) {
  1137. // (types, fn)
  1138. callback = selector;
  1139. data = selector = undefined;
  1140. }
  1141. else if (callback == null) {
  1142. if (isString(selector)) {
  1143. // (types, selector, fn)
  1144. callback = data;
  1145. data = undefined;
  1146. }
  1147. else {
  1148. // (types, data, fn)
  1149. callback = data;
  1150. data = selector;
  1151. selector = undefined;
  1152. }
  1153. }
  1154. if (callback === false) {
  1155. callback = returnFalse;
  1156. }
  1157. else if (!callback) {
  1158. return this;
  1159. }
  1160. // $().one()
  1161. if (one) {
  1162. // eslint-disable-next-line @typescript-eslint/no-this-alias
  1163. const _this = this;
  1164. const origCallback = callback;
  1165. callback = function (event) {
  1166. _this.off(event.type, selector, callback);
  1167. // eslint-disable-next-line prefer-rest-params
  1168. return origCallback.apply(this, arguments);
  1169. };
  1170. }
  1171. return this.each(function () {
  1172. add(this, types, callback, data, selector);
  1173. });
  1174. };
  1175. each(ajaxEvents, (name, eventName) => {
  1176. $.fn[name] = function (fn) {
  1177. return this.on(eventName, (e, params) => {
  1178. fn(e, params.xhr, params.options, params.data);
  1179. });
  1180. };
  1181. });
  1182. $.fn.map = function (callback) {
  1183. return new JQ(map(this, (element, i) => callback.call(element, i, element)));
  1184. };
  1185. $.fn.clone = function () {
  1186. return this.map(function () {
  1187. return this.cloneNode(true);
  1188. });
  1189. };
  1190. $.fn.is = function (selector) {
  1191. let isMatched = false;
  1192. if (isFunction(selector)) {
  1193. this.each((index, element) => {
  1194. if (selector.call(element, index, element)) {
  1195. isMatched = true;
  1196. }
  1197. });
  1198. return isMatched;
  1199. }
  1200. if (isString(selector)) {
  1201. this.each((_, element) => {
  1202. if (isDocument(element) || isWindow(element)) {
  1203. return;
  1204. }
  1205. // @ts-ignore
  1206. const matches = element.matches || element.msMatchesSelector;
  1207. if (matches.call(element, selector)) {
  1208. isMatched = true;
  1209. }
  1210. });
  1211. return isMatched;
  1212. }
  1213. const $compareWith = $(selector);
  1214. this.each((_, element) => {
  1215. $compareWith.each((_, compare) => {
  1216. if (element === compare) {
  1217. isMatched = true;
  1218. }
  1219. });
  1220. });
  1221. return isMatched;
  1222. };
  1223. $.fn.remove = function (selector) {
  1224. return this.each((_, element) => {
  1225. if (element.parentNode && (!selector || $(element).is(selector))) {
  1226. element.parentNode.removeChild(element);
  1227. }
  1228. });
  1229. };
  1230. each(['prepend', 'append'], (nameIndex, name) => {
  1231. $.fn[name] = function (...args) {
  1232. return this.each((index, element) => {
  1233. const childNodes = element.childNodes;
  1234. const childLength = childNodes.length;
  1235. const child = childLength
  1236. ? childNodes[nameIndex ? childLength - 1 : 0]
  1237. : document.createElement('div');
  1238. if (!childLength) {
  1239. element.appendChild(child);
  1240. }
  1241. let contents = isFunction(args[0])
  1242. ? [args[0].call(element, index, element.innerHTML)]
  1243. : args;
  1244. // 如果不是字符串,则仅第一个元素使用原始元素,其他的都克隆自第一个元素
  1245. if (index) {
  1246. contents = contents.map((content) => {
  1247. return isString(content) ? content : $(content).clone();
  1248. });
  1249. }
  1250. $(child)[nameIndex ? 'after' : 'before'](...contents);
  1251. if (!childLength) {
  1252. element.removeChild(child);
  1253. }
  1254. });
  1255. };
  1256. });
  1257. each(['appendTo', 'prependTo'], (nameIndex, name) => {
  1258. $.fn[name] = function (target) {
  1259. const extraChilds = [];
  1260. const $target = $(target).map((_, element) => {
  1261. const childNodes = element.childNodes;
  1262. const childLength = childNodes.length;
  1263. if (childLength) {
  1264. return childNodes[nameIndex ? 0 : childLength - 1];
  1265. }
  1266. const child = document.createElement('div');
  1267. element.appendChild(child);
  1268. extraChilds.push(child);
  1269. return child;
  1270. });
  1271. const $result = this[nameIndex ? 'insertBefore' : 'insertAfter']($target);
  1272. $(extraChilds).remove();
  1273. return $result;
  1274. };
  1275. });
  1276. each(['attr', 'prop', 'css'], (nameIndex, name) => {
  1277. function set(element, key, value) {
  1278. // 值为 undefined 时,不修改
  1279. if (isUndefined(value)) {
  1280. return;
  1281. }
  1282. switch (nameIndex) {
  1283. // attr
  1284. case 0:
  1285. if (isNull(value)) {
  1286. element.removeAttribute(key);
  1287. }
  1288. else {
  1289. element.setAttribute(key, value);
  1290. }
  1291. break;
  1292. // prop
  1293. case 1:
  1294. // @ts-ignore
  1295. element[key] = value;
  1296. break;
  1297. // css
  1298. default:
  1299. key = toCamelCase(key);
  1300. // @ts-ignore
  1301. element.style[key] = isNumber(value)
  1302. ? `${value}${cssNumber.indexOf(key) > -1 ? '' : 'px'}`
  1303. : value;
  1304. break;
  1305. }
  1306. }
  1307. function get(element, key) {
  1308. switch (nameIndex) {
  1309. // attr
  1310. case 0:
  1311. // 属性不存在时,原生 getAttribute 方法返回 null,而 jquery 返回 undefined。这里和 jquery 保持一致
  1312. const value = element.getAttribute(key);
  1313. return isNull(value) ? undefined : value;
  1314. // prop
  1315. case 1:
  1316. // @ts-ignore
  1317. return element[key];
  1318. // css
  1319. default:
  1320. return getStyle(element, key);
  1321. }
  1322. }
  1323. $.fn[name] = function (key, value) {
  1324. if (isObjectLike(key)) {
  1325. each(key, (k, v) => {
  1326. // @ts-ignore
  1327. this[name](k, v);
  1328. });
  1329. return this;
  1330. }
  1331. if (arguments.length === 1) {
  1332. const element = this[0];
  1333. return isElement(element) ? get(element, key) : undefined;
  1334. }
  1335. return this.each((i, element) => {
  1336. set(element, key, isFunction(value) ? value.call(element, i, get(element, key)) : value);
  1337. });
  1338. };
  1339. });
  1340. $.fn.children = function (selector) {
  1341. const children = [];
  1342. this.each((_, element) => {
  1343. each(element.childNodes, (__, childNode) => {
  1344. if (!isElement(childNode)) {
  1345. return;
  1346. }
  1347. if (!selector || $(childNode).is(selector)) {
  1348. children.push(childNode);
  1349. }
  1350. });
  1351. });
  1352. return new JQ(unique(children));
  1353. };
  1354. $.fn.slice = function (...args) {
  1355. return new JQ([].slice.apply(this, args));
  1356. };
  1357. $.fn.eq = function (index) {
  1358. const ret = index === -1 ? this.slice(index) : this.slice(index, +index + 1);
  1359. return new JQ(ret);
  1360. };
  1361. function dir($elements, nameIndex, node, selector, filter) {
  1362. const ret = [];
  1363. let target;
  1364. $elements.each((_, element) => {
  1365. target = element[node];
  1366. // 不能包含最顶层的 document 元素
  1367. while (target && isElement(target)) {
  1368. // prevUntil, nextUntil, parentsUntil
  1369. if (nameIndex === 2) {
  1370. if (selector && $(target).is(selector)) {
  1371. break;
  1372. }
  1373. if (!filter || $(target).is(filter)) {
  1374. ret.push(target);
  1375. }
  1376. }
  1377. // prev, next, parent
  1378. else if (nameIndex === 0) {
  1379. if (!selector || $(target).is(selector)) {
  1380. ret.push(target);
  1381. }
  1382. break;
  1383. }
  1384. // prevAll, nextAll, parents
  1385. else {
  1386. if (!selector || $(target).is(selector)) {
  1387. ret.push(target);
  1388. }
  1389. }
  1390. // @ts-ignore
  1391. target = target[node];
  1392. }
  1393. });
  1394. return new JQ(unique(ret));
  1395. }
  1396. each(['', 's', 'sUntil'], (nameIndex, name) => {
  1397. $.fn[`parent${name}`] = function (selector, filter) {
  1398. // parents、parentsUntil 需要把元素的顺序反向处理,以便和 jQuery 的结果一致
  1399. const $nodes = !nameIndex ? this : $(this.get().reverse());
  1400. return dir($nodes, nameIndex, 'parentNode', selector, filter);
  1401. };
  1402. });
  1403. $.fn.closest = function (selector) {
  1404. if (this.is(selector)) {
  1405. return this;
  1406. }
  1407. const matched = [];
  1408. this.parents().each((_, element) => {
  1409. if ($(element).is(selector)) {
  1410. matched.push(element);
  1411. return false;
  1412. }
  1413. });
  1414. return new JQ(matched);
  1415. };
  1416. const rbrace = /^(?:{[\w\W]*\}|\[[\w\W]*\])$/;
  1417. // 从 `data-*` 中获取的值,需要经过该函数转换
  1418. function getData(value) {
  1419. if (value === 'true') {
  1420. return true;
  1421. }
  1422. if (value === 'false') {
  1423. return false;
  1424. }
  1425. if (value === 'null') {
  1426. return null;
  1427. }
  1428. if (value === +value + '') {
  1429. return +value;
  1430. }
  1431. if (rbrace.test(value)) {
  1432. return JSON.parse(value);
  1433. }
  1434. return value;
  1435. }
  1436. // 若 value 不存在,则从 `data-*` 中获取值
  1437. function dataAttr(element, key, value) {
  1438. if (isUndefined(value) && element.nodeType === 1) {
  1439. const name = 'data-' + toKebabCase(key);
  1440. value = element.getAttribute(name);
  1441. if (isString(value)) {
  1442. try {
  1443. value = getData(value);
  1444. }
  1445. catch (e) { }
  1446. }
  1447. else {
  1448. value = undefined;
  1449. }
  1450. }
  1451. return value;
  1452. }
  1453. $.fn.data = function (key, value) {
  1454. // 获取所有值
  1455. if (isUndefined(key)) {
  1456. if (!this.length) {
  1457. return undefined;
  1458. }
  1459. const element = this[0];
  1460. const resultData = data(element);
  1461. // window, document 上不存在 `data-*` 属性
  1462. if (element.nodeType !== 1) {
  1463. return resultData;
  1464. }
  1465. // 从 `data-*` 中获取值
  1466. const attrs = element.attributes;
  1467. let i = attrs.length;
  1468. while (i--) {
  1469. if (attrs[i]) {
  1470. let name = attrs[i].name;
  1471. if (name.indexOf('data-') === 0) {
  1472. name = toCamelCase(name.slice(5));
  1473. resultData[name] = dataAttr(element, name, resultData[name]);
  1474. }
  1475. }
  1476. }
  1477. return resultData;
  1478. }
  1479. // 同时设置多个值
  1480. if (isObjectLike(key)) {
  1481. return this.each(function () {
  1482. data(this, key);
  1483. });
  1484. }
  1485. // value 传入了 undefined
  1486. if (arguments.length === 2 && isUndefined(value)) {
  1487. return this;
  1488. }
  1489. // 设置值
  1490. if (!isUndefined(value)) {
  1491. return this.each(function () {
  1492. data(this, key, value);
  1493. });
  1494. }
  1495. // 获取值
  1496. if (!this.length) {
  1497. return undefined;
  1498. }
  1499. return dataAttr(this[0], key, data(this[0], key));
  1500. };
  1501. $.fn.empty = function () {
  1502. return this.each(function () {
  1503. this.innerHTML = '';
  1504. });
  1505. };
  1506. $.fn.extend = function (obj) {
  1507. each(obj, (prop, value) => {
  1508. // 在 JQ 对象上扩展方法时,需要自己添加 typescript 的类型定义
  1509. $.fn[prop] = value;
  1510. });
  1511. return this;
  1512. };
  1513. $.fn.filter = function (selector) {
  1514. if (isFunction(selector)) {
  1515. return this.map((index, element) => selector.call(element, index, element) ? element : undefined);
  1516. }
  1517. if (isString(selector)) {
  1518. return this.map((_, element) => $(element).is(selector) ? element : undefined);
  1519. }
  1520. const $selector = $(selector);
  1521. return this.map((_, element) => $selector.get().indexOf(element) > -1 ? element : undefined);
  1522. };
  1523. $.fn.first = function () {
  1524. return this.eq(0);
  1525. };
  1526. $.fn.has = function (selector) {
  1527. const $targets = isString(selector) ? this.find(selector) : $(selector);
  1528. const { length } = $targets;
  1529. return this.map(function () {
  1530. for (let i = 0; i < length; i += 1) {
  1531. if (contains(this, $targets[i])) {
  1532. return this;
  1533. }
  1534. }
  1535. return;
  1536. });
  1537. };
  1538. $.fn.hasClass = function (className) {
  1539. return this[0].classList.contains(className);
  1540. };
  1541. /**
  1542. * 值上面的 padding、border、margin 处理
  1543. * @param element
  1544. * @param name
  1545. * @param value
  1546. * @param funcIndex
  1547. * @param includeMargin
  1548. * @param multiply
  1549. */
  1550. function handleExtraWidth(element, name, value, funcIndex, includeMargin, multiply) {
  1551. // 获取元素的 padding, border, margin 宽度(两侧宽度的和)
  1552. const getExtraWidthValue = (extra) => {
  1553. return (getExtraWidth(element, name.toLowerCase(), extra) *
  1554. multiply);
  1555. };
  1556. if (funcIndex === 2 && includeMargin) {
  1557. value += getExtraWidthValue('margin');
  1558. }
  1559. if (isBorderBox(element)) {
  1560. // IE 为 box-sizing: border-box 时,得到的值不含 border 和 padding,这里先修复
  1561. // 仅获取时需要处理,multiply === 1 为 get
  1562. if (isIE() && multiply === 1) {
  1563. value += getExtraWidthValue('border');
  1564. value += getExtraWidthValue('padding');
  1565. }
  1566. if (funcIndex === 0) {
  1567. value -= getExtraWidthValue('border');
  1568. }
  1569. if (funcIndex === 1) {
  1570. value -= getExtraWidthValue('border');
  1571. value -= getExtraWidthValue('padding');
  1572. }
  1573. }
  1574. else {
  1575. if (funcIndex === 0) {
  1576. value += getExtraWidthValue('padding');
  1577. }
  1578. if (funcIndex === 2) {
  1579. value += getExtraWidthValue('border');
  1580. value += getExtraWidthValue('padding');
  1581. }
  1582. }
  1583. return value;
  1584. }
  1585. /**
  1586. * 获取元素的样式值
  1587. * @param element
  1588. * @param name
  1589. * @param funcIndex 0: innerWidth, innerHeight; 1: width, height; 2: outerWidth, outerHeight
  1590. * @param includeMargin
  1591. */
  1592. function get(element, name, funcIndex, includeMargin) {
  1593. const clientProp = `client${name}`;
  1594. const scrollProp = `scroll${name}`;
  1595. const offsetProp = `offset${name}`;
  1596. const innerProp = `inner${name}`;
  1597. // $(window).width()
  1598. if (isWindow(element)) {
  1599. // outerWidth, outerHeight 需要包含滚动条的宽度
  1600. return funcIndex === 2
  1601. ? element[innerProp]
  1602. : toElement(document)[clientProp];
  1603. }
  1604. // $(document).width()
  1605. if (isDocument(element)) {
  1606. const doc = toElement(element);
  1607. return Math.max(
  1608. // @ts-ignore
  1609. element.body[scrollProp], doc[scrollProp],
  1610. // @ts-ignore
  1611. element.body[offsetProp], doc[offsetProp], doc[clientProp]);
  1612. }
  1613. const value = parseFloat(getComputedStyleValue(element, name.toLowerCase()) || '0');
  1614. return handleExtraWidth(element, name, value, funcIndex, includeMargin, 1);
  1615. }
  1616. /**
  1617. * 设置元素的样式值
  1618. * @param element
  1619. * @param elementIndex
  1620. * @param name
  1621. * @param funcIndex 0: innerWidth, innerHeight; 1: width, height; 2: outerWidth, outerHeight
  1622. * @param includeMargin
  1623. * @param value
  1624. */
  1625. function set(element, elementIndex, name, funcIndex, includeMargin, value) {
  1626. let computedValue = isFunction(value)
  1627. ? value.call(element, elementIndex, get(element, name, funcIndex, includeMargin))
  1628. : value;
  1629. if (computedValue == null) {
  1630. return;
  1631. }
  1632. const $element = $(element);
  1633. const dimension = name.toLowerCase();
  1634. // 特殊的值,不需要计算 padding、border、margin
  1635. if (['auto', 'inherit', ''].indexOf(computedValue) > -1) {
  1636. $element.css(dimension, computedValue);
  1637. return;
  1638. }
  1639. // 其他值保留原始单位。注意:如果不使用 px 作为单位,则算出的值一般是不准确的
  1640. const suffix = computedValue.toString().replace(/\b[0-9.]*/, '');
  1641. const numerical = parseFloat(computedValue);
  1642. computedValue =
  1643. handleExtraWidth(element, name, numerical, funcIndex, includeMargin, -1) +
  1644. (suffix || 'px');
  1645. $element.css(dimension, computedValue);
  1646. }
  1647. each(['Width', 'Height'], (_, name) => {
  1648. each([`inner${name}`, name.toLowerCase(), `outer${name}`], (funcIndex, funcName) => {
  1649. $.fn[funcName] = function (margin, value) {
  1650. // 是否是赋值操作
  1651. const isSet = arguments.length && (funcIndex < 2 || !isBoolean(margin));
  1652. const includeMargin = margin === true || value === true;
  1653. // 获取第一个元素的值
  1654. if (!isSet) {
  1655. return this.length
  1656. ? get(this[0], name, funcIndex, includeMargin)
  1657. : undefined;
  1658. }
  1659. // 设置每个元素的值
  1660. return this.each((index, element) => set(element, index, name, funcIndex, includeMargin, margin));
  1661. };
  1662. });
  1663. });
  1664. $.fn.hide = function () {
  1665. return this.each(function () {
  1666. this.style.display = 'none';
  1667. });
  1668. };
  1669. each(['val', 'html', 'text'], (nameIndex, name) => {
  1670. const props = {
  1671. 0: 'value',
  1672. 1: 'innerHTML',
  1673. 2: 'textContent',
  1674. };
  1675. const propName = props[nameIndex];
  1676. function get($elements) {
  1677. // text() 获取所有元素的文本
  1678. if (nameIndex === 2) {
  1679. // @ts-ignore
  1680. return map($elements, (element) => toElement(element)[propName]).join('');
  1681. }
  1682. // 空集合时,val() 和 html() 返回 undefined
  1683. if (!$elements.length) {
  1684. return undefined;
  1685. }
  1686. // val() 和 html() 仅获取第一个元素的内容
  1687. const firstElement = $elements[0];
  1688. // select multiple 返回数组
  1689. if (nameIndex === 0 && $(firstElement).is('select[multiple]')) {
  1690. return map($(firstElement).find('option:checked'), (element) => element.value);
  1691. }
  1692. // @ts-ignore
  1693. return firstElement[propName];
  1694. }
  1695. function set(element, value) {
  1696. // text() 和 html() 赋值为 undefined,则保持原内容不变
  1697. // val() 赋值为 undefined 则赋值为空
  1698. if (isUndefined(value)) {
  1699. if (nameIndex !== 0) {
  1700. return;
  1701. }
  1702. value = '';
  1703. }
  1704. if (nameIndex === 1 && isElement(value)) {
  1705. value = value.outerHTML;
  1706. }
  1707. // @ts-ignore
  1708. element[propName] = value;
  1709. }
  1710. $.fn[name] = function (value) {
  1711. // 获取值
  1712. if (!arguments.length) {
  1713. return get(this);
  1714. }
  1715. // 设置值
  1716. return this.each((i, element) => {
  1717. const computedValue = isFunction(value)
  1718. ? value.call(element, i, get($(element)))
  1719. : value;
  1720. // value 是数组,则选中数组中的元素,反选不在数组中的元素
  1721. if (nameIndex === 0 && Array.isArray(computedValue)) {
  1722. // select[multiple]
  1723. if ($(element).is('select[multiple]')) {
  1724. map($(element).find('option'), (option) => (option.selected =
  1725. computedValue.indexOf(option.value) >
  1726. -1));
  1727. }
  1728. // 其他 checkbox, radio 等元素
  1729. else {
  1730. element.checked =
  1731. computedValue.indexOf(element.value) > -1;
  1732. }
  1733. }
  1734. else {
  1735. set(element, computedValue);
  1736. }
  1737. });
  1738. };
  1739. });
  1740. $.fn.index = function (selector) {
  1741. if (!arguments.length) {
  1742. return this.eq(0).parent().children().get().indexOf(this[0]);
  1743. }
  1744. if (isString(selector)) {
  1745. return $(selector).get().indexOf(this[0]);
  1746. }
  1747. return this.get().indexOf($(selector)[0]);
  1748. };
  1749. $.fn.last = function () {
  1750. return this.eq(-1);
  1751. };
  1752. each(['', 'All', 'Until'], (nameIndex, name) => {
  1753. $.fn[`next${name}`] = function (selector, filter) {
  1754. return dir(this, nameIndex, 'nextElementSibling', selector, filter);
  1755. };
  1756. });
  1757. $.fn.not = function (selector) {
  1758. const $excludes = this.filter(selector);
  1759. return this.map((_, element) => $excludes.index(element) > -1 ? undefined : element);
  1760. };
  1761. /**
  1762. * 返回最近的用于定位的父元素
  1763. */
  1764. $.fn.offsetParent = function () {
  1765. return this.map(function () {
  1766. let offsetParent = this.offsetParent;
  1767. while (offsetParent && $(offsetParent).css('position') === 'static') {
  1768. offsetParent = offsetParent.offsetParent;
  1769. }
  1770. return offsetParent || document.documentElement;
  1771. });
  1772. };
  1773. function floatStyle($element, name) {
  1774. return parseFloat($element.css(name));
  1775. }
  1776. $.fn.position = function () {
  1777. if (!this.length) {
  1778. return undefined;
  1779. }
  1780. const $element = this.eq(0);
  1781. let currentOffset;
  1782. let parentOffset = {
  1783. left: 0,
  1784. top: 0,
  1785. };
  1786. if ($element.css('position') === 'fixed') {
  1787. currentOffset = $element[0].getBoundingClientRect();
  1788. }
  1789. else {
  1790. currentOffset = $element.offset();
  1791. const $offsetParent = $element.offsetParent();
  1792. parentOffset = $offsetParent.offset();
  1793. parentOffset.top += floatStyle($offsetParent, 'border-top-width');
  1794. parentOffset.left += floatStyle($offsetParent, 'border-left-width');
  1795. }
  1796. return {
  1797. top: currentOffset.top - parentOffset.top - floatStyle($element, 'margin-top'),
  1798. left: currentOffset.left -
  1799. parentOffset.left -
  1800. floatStyle($element, 'margin-left'),
  1801. };
  1802. };
  1803. function get$1(element) {
  1804. if (!element.getClientRects().length) {
  1805. return { top: 0, left: 0 };
  1806. }
  1807. const rect = element.getBoundingClientRect();
  1808. const win = element.ownerDocument.defaultView;
  1809. return {
  1810. top: rect.top + win.pageYOffset,
  1811. left: rect.left + win.pageXOffset,
  1812. };
  1813. }
  1814. function set$1(element, value, index) {
  1815. const $element = $(element);
  1816. const position = $element.css('position');
  1817. if (position === 'static') {
  1818. $element.css('position', 'relative');
  1819. }
  1820. const currentOffset = get$1(element);
  1821. const currentTopString = $element.css('top');
  1822. const currentLeftString = $element.css('left');
  1823. let currentTop;
  1824. let currentLeft;
  1825. const calculatePosition = (position === 'absolute' || position === 'fixed') &&
  1826. (currentTopString + currentLeftString).indexOf('auto') > -1;
  1827. if (calculatePosition) {
  1828. const currentPosition = $element.position();
  1829. currentTop = currentPosition.top;
  1830. currentLeft = currentPosition.left;
  1831. }
  1832. else {
  1833. currentTop = parseFloat(currentTopString);
  1834. currentLeft = parseFloat(currentLeftString);
  1835. }
  1836. const computedValue = isFunction(value)
  1837. ? value.call(element, index, extend({}, currentOffset))
  1838. : value;
  1839. $element.css({
  1840. top: computedValue.top != null
  1841. ? computedValue.top - currentOffset.top + currentTop
  1842. : undefined,
  1843. left: computedValue.left != null
  1844. ? computedValue.left - currentOffset.left + currentLeft
  1845. : undefined,
  1846. });
  1847. }
  1848. $.fn.offset = function (value) {
  1849. // 获取坐标
  1850. if (!arguments.length) {
  1851. if (!this.length) {
  1852. return undefined;
  1853. }
  1854. return get$1(this[0]);
  1855. }
  1856. // 设置坐标
  1857. return this.each(function (index) {
  1858. set$1(this, value, index);
  1859. });
  1860. };
  1861. $.fn.one = function (types, selector, data, callback) {
  1862. // @ts-ignore
  1863. return this.on(types, selector, data, callback, true);
  1864. };
  1865. each(['', 'All', 'Until'], (nameIndex, name) => {
  1866. $.fn[`prev${name}`] = function (selector, filter) {
  1867. // prevAll、prevUntil 需要把元素的顺序倒序处理,以便和 jQuery 的结果一致
  1868. const $nodes = !nameIndex ? this : $(this.get().reverse());
  1869. return dir($nodes, nameIndex, 'previousElementSibling', selector, filter);
  1870. };
  1871. });
  1872. $.fn.removeAttr = function (attributeName) {
  1873. const names = attributeName.split(' ').filter((name) => name);
  1874. return this.each(function () {
  1875. each(names, (_, name) => {
  1876. this.removeAttribute(name);
  1877. });
  1878. });
  1879. };
  1880. $.fn.removeData = function (name) {
  1881. return this.each(function () {
  1882. removeData(this, name);
  1883. });
  1884. };
  1885. $.fn.removeProp = function (name) {
  1886. return this.each(function () {
  1887. try {
  1888. // @ts-ignore
  1889. delete this[name];
  1890. }
  1891. catch (e) { }
  1892. });
  1893. };
  1894. $.fn.replaceWith = function (newContent) {
  1895. this.each((index, element) => {
  1896. let content = newContent;
  1897. if (isFunction(content)) {
  1898. content = content.call(element, index, element.innerHTML);
  1899. }
  1900. else if (index && !isString(content)) {
  1901. content = $(content).clone();
  1902. }
  1903. $(element).before(content);
  1904. });
  1905. return this.remove();
  1906. };
  1907. $.fn.replaceAll = function (target) {
  1908. return $(target).map((index, element) => {
  1909. $(element).replaceWith(index ? this.clone() : this);
  1910. return this.get();
  1911. });
  1912. };
  1913. /**
  1914. * 将表单元素的值组合成键值对数组
  1915. * @returns {Array}
  1916. */
  1917. $.fn.serializeArray = function () {
  1918. const result = [];
  1919. this.each((_, element) => {
  1920. const elements = element instanceof HTMLFormElement ? element.elements : [element];
  1921. $(elements).each((_, element) => {
  1922. const $element = $(element);
  1923. const type = element.type;
  1924. const nodeName = element.nodeName.toLowerCase();
  1925. if (nodeName !== 'fieldset' &&
  1926. element.name &&
  1927. !element.disabled &&
  1928. ['input', 'select', 'textarea', 'keygen'].indexOf(nodeName) > -1 &&
  1929. ['submit', 'button', 'image', 'reset', 'file'].indexOf(type) === -1 &&
  1930. (['radio', 'checkbox'].indexOf(type) === -1 ||
  1931. element.checked)) {
  1932. const value = $element.val();
  1933. const valueArr = Array.isArray(value) ? value : [value];
  1934. valueArr.forEach((value) => {
  1935. result.push({
  1936. name: element.name,
  1937. value,
  1938. });
  1939. });
  1940. }
  1941. });
  1942. });
  1943. return result;
  1944. };
  1945. $.fn.serialize = function () {
  1946. return param(this.serializeArray());
  1947. };
  1948. const elementDisplay = {};
  1949. /**
  1950. * 获取元素的初始 display 值,用于 .show() 方法
  1951. * @param nodeName
  1952. */
  1953. function defaultDisplay(nodeName) {
  1954. let element;
  1955. let display;
  1956. if (!elementDisplay[nodeName]) {
  1957. element = document.createElement(nodeName);
  1958. document.body.appendChild(element);
  1959. display = getStyle(element, 'display');
  1960. element.parentNode.removeChild(element);
  1961. if (display === 'none') {
  1962. display = 'block';
  1963. }
  1964. elementDisplay[nodeName] = display;
  1965. }
  1966. return elementDisplay[nodeName];
  1967. }
  1968. /**
  1969. * 显示指定元素
  1970. * @returns {JQ}
  1971. */
  1972. $.fn.show = function () {
  1973. return this.each(function () {
  1974. if (this.style.display === 'none') {
  1975. this.style.display = '';
  1976. }
  1977. if (getStyle(this, 'display') === 'none') {
  1978. this.style.display = defaultDisplay(this.nodeName);
  1979. }
  1980. });
  1981. };
  1982. /**
  1983. * 取得同辈元素的集合
  1984. * @param selector {String=}
  1985. * @returns {JQ}
  1986. */
  1987. $.fn.siblings = function (selector) {
  1988. return this.prevAll(selector).add(this.nextAll(selector));
  1989. };
  1990. /**
  1991. * 切换元素的显示状态
  1992. */
  1993. $.fn.toggle = function () {
  1994. return this.each(function () {
  1995. getStyle(this, 'display') === 'none' ? $(this).show() : $(this).hide();
  1996. });
  1997. };
  1998. $.fn.reflow = function () {
  1999. return this.each(function () {
  2000. return this.clientLeft;
  2001. });
  2002. };
  2003. $.fn.transition = function (duration) {
  2004. if (isNumber(duration)) {
  2005. duration = `${duration}ms`;
  2006. }
  2007. return this.each(function () {
  2008. this.style.webkitTransitionDuration = duration;
  2009. this.style.transitionDuration = duration;
  2010. });
  2011. };
  2012. $.fn.transitionEnd = function (callback) {
  2013. // eslint-disable-next-line @typescript-eslint/no-this-alias
  2014. const that = this;
  2015. const events = ['webkitTransitionEnd', 'transitionend'];
  2016. function fireCallback(e) {
  2017. if (e.target !== this) {
  2018. return;
  2019. }
  2020. // @ts-ignore
  2021. callback.call(this, e);
  2022. each(events, (_, event) => {
  2023. that.off(event, fireCallback);
  2024. });
  2025. }
  2026. each(events, (_, event) => {
  2027. that.on(event, fireCallback);
  2028. });
  2029. return this;
  2030. };
  2031. $.fn.transformOrigin = function (transformOrigin) {
  2032. return this.each(function () {
  2033. this.style.webkitTransformOrigin = transformOrigin;
  2034. this.style.transformOrigin = transformOrigin;
  2035. });
  2036. };
  2037. $.fn.transform = function (transform) {
  2038. return this.each(function () {
  2039. this.style.webkitTransform = transform;
  2040. this.style.transform = transform;
  2041. });
  2042. };
  2043. /**
  2044. * CSS 选择器和初始化函数组成的对象
  2045. */
  2046. const entries = {};
  2047. /**
  2048. * 注册并执行初始化函数
  2049. * @param selector CSS 选择器
  2050. * @param apiInit 初始化函数
  2051. * @param i 元素索引
  2052. * @param element 元素
  2053. */
  2054. function mutation(selector, apiInit, i, element) {
  2055. let selectors = data(element, '_mdui_mutation');
  2056. if (!selectors) {
  2057. selectors = [];
  2058. data(element, '_mdui_mutation', selectors);
  2059. }
  2060. if (selectors.indexOf(selector) === -1) {
  2061. selectors.push(selector);
  2062. apiInit.call(element, i, element);
  2063. }
  2064. }
  2065. $.fn.mutation = function () {
  2066. return this.each((i, element) => {
  2067. const $this = $(element);
  2068. each(entries, (selector, apiInit) => {
  2069. if ($this.is(selector)) {
  2070. mutation(selector, apiInit, i, element);
  2071. }
  2072. $this.find(selector).each((i, element) => {
  2073. mutation(selector, apiInit, i, element);
  2074. });
  2075. });
  2076. });
  2077. };
  2078. $.showOverlay = function (zIndex) {
  2079. let $overlay = $('.mdui-overlay');
  2080. if ($overlay.length) {
  2081. $overlay.data('_overlay_is_deleted', false);
  2082. if (!isUndefined(zIndex)) {
  2083. $overlay.css('z-index', zIndex);
  2084. }
  2085. }
  2086. else {
  2087. if (isUndefined(zIndex)) {
  2088. zIndex = 2000;
  2089. }
  2090. $overlay = $('<div class="mdui-overlay">')
  2091. .appendTo(document.body)
  2092. .reflow()
  2093. .css('z-index', zIndex);
  2094. }
  2095. let level = $overlay.data('_overlay_level') || 0;
  2096. return $overlay.data('_overlay_level', ++level).addClass('mdui-overlay-show');
  2097. };
  2098. $.hideOverlay = function (force = false) {
  2099. const $overlay = $('.mdui-overlay');
  2100. if (!$overlay.length) {
  2101. return;
  2102. }
  2103. let level = force ? 1 : $overlay.data('_overlay_level');
  2104. if (level > 1) {
  2105. $overlay.data('_overlay_level', --level);
  2106. return;
  2107. }
  2108. $overlay
  2109. .data('_overlay_level', 0)
  2110. .removeClass('mdui-overlay-show')
  2111. .data('_overlay_is_deleted', true)
  2112. .transitionEnd(() => {
  2113. if ($overlay.data('_overlay_is_deleted')) {
  2114. $overlay.remove();
  2115. }
  2116. });
  2117. };
  2118. $.lockScreen = function () {
  2119. const $body = $('body');
  2120. // 不直接把 body 设为 box-sizing: border-box,避免污染全局样式
  2121. const newBodyWidth = $body.width();
  2122. let level = $body.data('_lockscreen_level') || 0;
  2123. $body
  2124. .addClass('mdui-locked')
  2125. .width(newBodyWidth)
  2126. .data('_lockscreen_level', ++level);
  2127. };
  2128. $.unlockScreen = function (force = false) {
  2129. const $body = $('body');
  2130. let level = force ? 1 : $body.data('_lockscreen_level');
  2131. if (level > 1) {
  2132. $body.data('_lockscreen_level', --level);
  2133. return;
  2134. }
  2135. $body.data('_lockscreen_level', 0).removeClass('mdui-locked').width('');
  2136. };
  2137. $.throttle = function (fn, delay = 16) {
  2138. let timer = null;
  2139. return function (...args) {
  2140. if (isNull(timer)) {
  2141. timer = setTimeout(() => {
  2142. fn.apply(this, args);
  2143. timer = null;
  2144. }, delay);
  2145. }
  2146. };
  2147. };
  2148. const GUID = {};
  2149. $.guid = function (name) {
  2150. if (!isUndefined(name) && !isUndefined(GUID[name])) {
  2151. return GUID[name];
  2152. }
  2153. function s4() {
  2154. return Math.floor((1 + Math.random()) * 0x10000)
  2155. .toString(16)
  2156. .substring(1);
  2157. }
  2158. const guid = '_' +
  2159. s4() +
  2160. s4() +
  2161. '-' +
  2162. s4() +
  2163. '-' +
  2164. s4() +
  2165. '-' +
  2166. s4() +
  2167. '-' +
  2168. s4() +
  2169. s4() +
  2170. s4();
  2171. if (!isUndefined(name)) {
  2172. GUID[name] = guid;
  2173. }
  2174. return guid;
  2175. };
  2176. mdui.mutation = function (selector, apiInit) {
  2177. if (isUndefined(selector) || isUndefined(apiInit)) {
  2178. $(document).mutation();
  2179. return;
  2180. }
  2181. entries[selector] = apiInit;
  2182. $(selector).each((i, element) => mutation(selector, apiInit, i, element));
  2183. };
  2184. /**
  2185. * 触发组件上的事件
  2186. * @param eventName 事件名
  2187. * @param componentName 组件名
  2188. * @param target 在该元素上触发事件
  2189. * @param instance 组件实例
  2190. * @param parameters 事件参数
  2191. */
  2192. function componentEvent(eventName, componentName, target, instance, parameters) {
  2193. if (!parameters) {
  2194. parameters = {};
  2195. }
  2196. // @ts-ignore
  2197. parameters.inst = instance;
  2198. const fullEventName = `${eventName}.mdui.${componentName}`;
  2199. // jQuery 事件
  2200. // @ts-ignore
  2201. if (typeof jQuery !== 'undefined') {
  2202. // @ts-ignore
  2203. jQuery(target).trigger(fullEventName, parameters);
  2204. }
  2205. const $target = $(target);
  2206. // mdui.jq 事件
  2207. $target.trigger(fullEventName, parameters);
  2208. const eventParams = {
  2209. bubbles: true,
  2210. cancelable: true,
  2211. detail: parameters,
  2212. };
  2213. const eventObject = new CustomEvent(fullEventName, eventParams);
  2214. // @ts-ignore
  2215. eventObject._detail = parameters;
  2216. $target[0].dispatchEvent(eventObject);
  2217. }
  2218. const $document = $(document);
  2219. const $window = $(window);
  2220. $('body');
  2221. const DEFAULT_OPTIONS = {
  2222. tolerance: 5,
  2223. offset: 0,
  2224. initialClass: 'mdui-headroom',
  2225. pinnedClass: 'mdui-headroom-pinned-top',
  2226. unpinnedClass: 'mdui-headroom-unpinned-top',
  2227. };
  2228. class Headroom {
  2229. constructor(selector, options = {}) {
  2230. /**
  2231. * 配置参数
  2232. */
  2233. this.options = extend({}, DEFAULT_OPTIONS);
  2234. /**
  2235. * 当前 headroom 的状态
  2236. */
  2237. this.state = 'pinned';
  2238. /**
  2239. * 当前是否启用
  2240. */
  2241. this.isEnable = false;
  2242. /**
  2243. * 上次滚动后,垂直方向的距离
  2244. */
  2245. this.lastScrollY = 0;
  2246. /**
  2247. * AnimationFrame ID
  2248. */
  2249. this.rafId = 0;
  2250. this.$element = $(selector).first();
  2251. extend(this.options, options);
  2252. // tolerance 参数若为数值,转换为对象
  2253. const tolerance = this.options.tolerance;
  2254. if (isNumber(tolerance)) {
  2255. this.options.tolerance = {
  2256. down: tolerance,
  2257. up: tolerance,
  2258. };
  2259. }
  2260. this.enable();
  2261. }
  2262. /**
  2263. * 滚动时的处理
  2264. */
  2265. onScroll() {
  2266. this.rafId = window.requestAnimationFrame(() => {
  2267. const currentScrollY = window.pageYOffset;
  2268. const direction = currentScrollY > this.lastScrollY ? 'down' : 'up';
  2269. const tolerance = this.options.tolerance[direction];
  2270. const scrolled = Math.abs(currentScrollY - this.lastScrollY);
  2271. const toleranceExceeded = scrolled >= tolerance;
  2272. if (currentScrollY > this.lastScrollY &&
  2273. currentScrollY >= this.options.offset &&
  2274. toleranceExceeded) {
  2275. this.unpin();
  2276. }
  2277. else if ((currentScrollY < this.lastScrollY && toleranceExceeded) ||
  2278. currentScrollY <= this.options.offset) {
  2279. this.pin();
  2280. }
  2281. this.lastScrollY = currentScrollY;
  2282. });
  2283. }
  2284. /**
  2285. * 触发组件事件
  2286. * @param name
  2287. */
  2288. triggerEvent(name) {
  2289. componentEvent(name, 'headroom', this.$element, this);
  2290. }
  2291. /**
  2292. * 动画结束的回调
  2293. */
  2294. transitionEnd() {
  2295. if (this.state === 'pinning') {
  2296. this.state = 'pinned';
  2297. this.triggerEvent('pinned');
  2298. }
  2299. if (this.state === 'unpinning') {
  2300. this.state = 'unpinned';
  2301. this.triggerEvent('unpinned');
  2302. }
  2303. }
  2304. /**
  2305. * 使元素固定住
  2306. */
  2307. pin() {
  2308. if (this.state === 'pinning' ||
  2309. this.state === 'pinned' ||
  2310. !this.$element.hasClass(this.options.initialClass)) {
  2311. return;
  2312. }
  2313. this.triggerEvent('pin');
  2314. this.state = 'pinning';
  2315. this.$element
  2316. .removeClass(this.options.unpinnedClass)
  2317. .addClass(this.options.pinnedClass)
  2318. .transitionEnd(() => this.transitionEnd());
  2319. }
  2320. /**
  2321. * 使元素隐藏
  2322. */
  2323. unpin() {
  2324. if (this.state === 'unpinning' ||
  2325. this.state === 'unpinned' ||
  2326. !this.$element.hasClass(this.options.initialClass)) {
  2327. return;
  2328. }
  2329. this.triggerEvent('unpin');
  2330. this.state = 'unpinning';
  2331. this.$element
  2332. .removeClass(this.options.pinnedClass)
  2333. .addClass(this.options.unpinnedClass)
  2334. .transitionEnd(() => this.transitionEnd());
  2335. }
  2336. /**
  2337. * 启用 headroom 插件
  2338. */
  2339. enable() {
  2340. if (this.isEnable) {
  2341. return;
  2342. }
  2343. this.isEnable = true;
  2344. this.state = 'pinned';
  2345. this.$element
  2346. .addClass(this.options.initialClass)
  2347. .removeClass(this.options.pinnedClass)
  2348. .removeClass(this.options.unpinnedClass);
  2349. this.lastScrollY = window.pageYOffset;
  2350. $window.on('scroll', () => this.onScroll());
  2351. }
  2352. /**
  2353. * 禁用 headroom 插件
  2354. */
  2355. disable() {
  2356. if (!this.isEnable) {
  2357. return;
  2358. }
  2359. this.isEnable = false;
  2360. this.$element
  2361. .removeClass(this.options.initialClass)
  2362. .removeClass(this.options.pinnedClass)
  2363. .removeClass(this.options.unpinnedClass);
  2364. $window.off('scroll', () => this.onScroll());
  2365. window.cancelAnimationFrame(this.rafId);
  2366. }
  2367. /**
  2368. * 获取当前状态。共包含四种状态:`pinning`、`pinned`、`unpinning`、`unpinned`
  2369. */
  2370. getState() {
  2371. return this.state;
  2372. }
  2373. }
  2374. mdui.Headroom = Headroom;
  2375. /**
  2376. * 解析 DATA API 参数
  2377. * @param element 元素
  2378. * @param name 属性名
  2379. */
  2380. function parseOptions(element, name) {
  2381. const attr = $(element).attr(name);
  2382. if (!attr) {
  2383. return {};
  2384. }
  2385. return new Function('', `var json = ${attr}; return JSON.parse(JSON.stringify(json));`)();
  2386. }
  2387. const customAttr = 'mdui-headroom';
  2388. $(() => {
  2389. mdui.mutation(`[${customAttr}]`, function () {
  2390. new mdui.Headroom(this, parseOptions(this, customAttr));
  2391. });
  2392. });
  2393. const DEFAULT_OPTIONS$1 = {
  2394. accordion: false,
  2395. };
  2396. class CollapseAbstract {
  2397. constructor(selector, options = {}) {
  2398. /**
  2399. * 配置参数
  2400. */
  2401. this.options = extend({}, DEFAULT_OPTIONS$1);
  2402. // CSS 类名
  2403. const classPrefix = `mdui-${this.getNamespace()}-item`;
  2404. this.classItem = classPrefix;
  2405. this.classItemOpen = `${classPrefix}-open`;
  2406. this.classHeader = `${classPrefix}-header`;
  2407. this.classBody = `${classPrefix}-body`;
  2408. this.$element = $(selector).first();
  2409. extend(this.options, options);
  2410. this.bindEvent();
  2411. }
  2412. /**
  2413. * 绑定事件
  2414. */
  2415. bindEvent() {
  2416. // eslint-disable-next-line @typescript-eslint/no-this-alias
  2417. const that = this;
  2418. // 点击 header 时,打开/关闭 item
  2419. this.$element.on('click', `.${this.classHeader}`, function () {
  2420. const $header = $(this);
  2421. const $item = $header.parent();
  2422. const $items = that.getItems();
  2423. $items.each((_, item) => {
  2424. if ($item.is(item)) {
  2425. that.toggle(item);
  2426. }
  2427. });
  2428. });
  2429. // 点击关闭按钮时,关闭 item
  2430. this.$element.on('click', `[mdui-${this.getNamespace()}-item-close]`, function () {
  2431. const $target = $(this);
  2432. const $item = $target.parents(`.${that.classItem}`).first();
  2433. that.close($item);
  2434. });
  2435. }
  2436. /**
  2437. * 指定 item 是否处于打开状态
  2438. * @param $item
  2439. */
  2440. isOpen($item) {
  2441. return $item.hasClass(this.classItemOpen);
  2442. }
  2443. /**
  2444. * 获取所有 item
  2445. */
  2446. getItems() {
  2447. return this.$element.children(`.${this.classItem}`);
  2448. }
  2449. /**
  2450. * 获取指定 item
  2451. * @param item
  2452. */
  2453. getItem(item) {
  2454. if (isNumber(item)) {
  2455. return this.getItems().eq(item);
  2456. }
  2457. return $(item).first();
  2458. }
  2459. /**
  2460. * 触发组件事件
  2461. * @param name 事件名
  2462. * @param $item 事件触发的目标 item
  2463. */
  2464. triggerEvent(name, $item) {
  2465. componentEvent(name, this.getNamespace(), $item, this);
  2466. }
  2467. /**
  2468. * 动画结束回调
  2469. * @param $content body 元素
  2470. * @param $item item 元素
  2471. */
  2472. transitionEnd($content, $item) {
  2473. if (this.isOpen($item)) {
  2474. $content.transition(0).height('auto').reflow().transition('');
  2475. this.triggerEvent('opened', $item);
  2476. }
  2477. else {
  2478. $content.height('');
  2479. this.triggerEvent('closed', $item);
  2480. }
  2481. }
  2482. /**
  2483. * 打开指定面板项
  2484. * @param item 面板项的索引号、或 CSS 选择器、或 DOM 元素、或 JQ 对象
  2485. */
  2486. open(item) {
  2487. const $item = this.getItem(item);
  2488. if (this.isOpen($item)) {
  2489. return;
  2490. }
  2491. // 关闭其他项
  2492. if (this.options.accordion) {
  2493. this.$element.children(`.${this.classItemOpen}`).each((_, element) => {
  2494. const $element = $(element);
  2495. if (!$element.is($item)) {
  2496. this.close($element);
  2497. }
  2498. });
  2499. }
  2500. const $content = $item.children(`.${this.classBody}`);
  2501. $content
  2502. .height($content[0].scrollHeight)
  2503. .transitionEnd(() => this.transitionEnd($content, $item));
  2504. this.triggerEvent('open', $item);
  2505. $item.addClass(this.classItemOpen);
  2506. }
  2507. /**
  2508. * 关闭指定面板项
  2509. * @param item 面板项的索引号、或 CSS 选择器、或 DOM 元素、或 JQ 对象
  2510. */
  2511. close(item) {
  2512. const $item = this.getItem(item);
  2513. if (!this.isOpen($item)) {
  2514. return;
  2515. }
  2516. const $content = $item.children(`.${this.classBody}`);
  2517. this.triggerEvent('close', $item);
  2518. $item.removeClass(this.classItemOpen);
  2519. $content
  2520. .transition(0)
  2521. .height($content[0].scrollHeight)
  2522. .reflow()
  2523. .transition('')
  2524. .height('')
  2525. .transitionEnd(() => this.transitionEnd($content, $item));
  2526. }
  2527. /**
  2528. * 切换指定面板项的打开状态
  2529. * @param item 面板项的索引号、或 CSS 选择器、或 DOM 元素、或 JQ 对象
  2530. */
  2531. toggle(item) {
  2532. const $item = this.getItem(item);
  2533. this.isOpen($item) ? this.close($item) : this.open($item);
  2534. }
  2535. /**
  2536. * 打开所有面板项
  2537. */
  2538. openAll() {
  2539. this.getItems().each((_, element) => this.open(element));
  2540. }
  2541. /**
  2542. * 关闭所有面板项
  2543. */
  2544. closeAll() {
  2545. this.getItems().each((_, element) => this.close(element));
  2546. }
  2547. }
  2548. class Collapse extends CollapseAbstract {
  2549. getNamespace() {
  2550. return 'collapse';
  2551. }
  2552. }
  2553. mdui.Collapse = Collapse;
  2554. const customAttr$1 = 'mdui-collapse';
  2555. $(() => {
  2556. mdui.mutation(`[${customAttr$1}]`, function () {
  2557. new mdui.Collapse(this, parseOptions(this, customAttr$1));
  2558. });
  2559. });
  2560. class Panel extends CollapseAbstract {
  2561. getNamespace() {
  2562. return 'panel';
  2563. }
  2564. }
  2565. mdui.Panel = Panel;
  2566. const customAttr$2 = 'mdui-panel';
  2567. $(() => {
  2568. mdui.mutation(`[${customAttr$2}]`, function () {
  2569. new mdui.Panel(this, parseOptions(this, customAttr$2));
  2570. });
  2571. });
  2572. class Table {
  2573. constructor(selector) {
  2574. /**
  2575. * 表头 tr 元素
  2576. */
  2577. this.$thRow = $();
  2578. /**
  2579. * 表格 body 中的 tr 元素
  2580. */
  2581. this.$tdRows = $();
  2582. /**
  2583. * 表头的 checkbox 元素
  2584. */
  2585. this.$thCheckbox = $();
  2586. /**
  2587. * 表格 body 中的 checkbox 元素
  2588. */
  2589. this.$tdCheckboxs = $();
  2590. /**
  2591. * 表格行是否可选择
  2592. */
  2593. this.selectable = false;
  2594. /**
  2595. * 已选中的行数
  2596. */
  2597. this.selectedRow = 0;
  2598. this.$element = $(selector).first();
  2599. this.init();
  2600. }
  2601. /**
  2602. * 初始化表格
  2603. */
  2604. init() {
  2605. this.$thRow = this.$element.find('thead tr');
  2606. this.$tdRows = this.$element.find('tbody tr');
  2607. this.selectable = this.$element.hasClass('mdui-table-selectable');
  2608. this.updateThCheckbox();
  2609. this.updateTdCheckbox();
  2610. this.updateNumericCol();
  2611. }
  2612. /**
  2613. * 生成 checkbox 的 HTML 结构
  2614. * @param tag 标签名
  2615. */
  2616. createCheckboxHTML(tag) {
  2617. return (`<${tag} class="mdui-table-cell-checkbox">` +
  2618. '<label class="mdui-checkbox">' +
  2619. '<input type="checkbox"/>' +
  2620. '<i class="mdui-checkbox-icon"></i>' +
  2621. '</label>' +
  2622. `</${tag}>`);
  2623. }
  2624. /**
  2625. * 更新表头 checkbox 的状态
  2626. */
  2627. updateThCheckboxStatus() {
  2628. const checkbox = this.$thCheckbox[0];
  2629. const selectedRow = this.selectedRow;
  2630. const tdRowsLength = this.$tdRows.length;
  2631. checkbox.checked = selectedRow === tdRowsLength;
  2632. checkbox.indeterminate = !!selectedRow && selectedRow !== tdRowsLength;
  2633. }
  2634. /**
  2635. * 更新表格行的 checkbox
  2636. */
  2637. updateTdCheckbox() {
  2638. const rowSelectedClass = 'mdui-table-row-selected';
  2639. this.$tdRows.each((_, row) => {
  2640. const $row = $(row);
  2641. // 移除旧的 checkbox
  2642. $row.find('.mdui-table-cell-checkbox').remove();
  2643. if (!this.selectable) {
  2644. return;
  2645. }
  2646. // 创建 DOM
  2647. const $checkbox = $(this.createCheckboxHTML('td'))
  2648. .prependTo($row)
  2649. .find('input[type="checkbox"]');
  2650. // 默认选中的行
  2651. if ($row.hasClass(rowSelectedClass)) {
  2652. $checkbox[0].checked = true;
  2653. this.selectedRow++;
  2654. }
  2655. this.updateThCheckboxStatus();
  2656. // 绑定事件
  2657. $checkbox.on('change', () => {
  2658. if ($checkbox[0].checked) {
  2659. $row.addClass(rowSelectedClass);
  2660. this.selectedRow++;
  2661. }
  2662. else {
  2663. $row.removeClass(rowSelectedClass);
  2664. this.selectedRow--;
  2665. }
  2666. this.updateThCheckboxStatus();
  2667. });
  2668. this.$tdCheckboxs = this.$tdCheckboxs.add($checkbox);
  2669. });
  2670. }
  2671. /**
  2672. * 更新表头的 checkbox
  2673. */
  2674. updateThCheckbox() {
  2675. // 移除旧的 checkbox
  2676. this.$thRow.find('.mdui-table-cell-checkbox').remove();
  2677. if (!this.selectable) {
  2678. return;
  2679. }
  2680. this.$thCheckbox = $(this.createCheckboxHTML('th'))
  2681. .prependTo(this.$thRow)
  2682. .find('input[type="checkbox"]')
  2683. .on('change', () => {
  2684. const isCheckedAll = this.$thCheckbox[0].checked;
  2685. this.selectedRow = isCheckedAll ? this.$tdRows.length : 0;
  2686. this.$tdCheckboxs.each((_, checkbox) => {
  2687. checkbox.checked = isCheckedAll;
  2688. });
  2689. this.$tdRows.each((_, row) => {
  2690. isCheckedAll
  2691. ? $(row).addClass('mdui-table-row-selected')
  2692. : $(row).removeClass('mdui-table-row-selected');
  2693. });
  2694. });
  2695. }
  2696. /**
  2697. * 更新数值列
  2698. */
  2699. updateNumericCol() {
  2700. const numericClass = 'mdui-table-col-numeric';
  2701. this.$thRow.find('th').each((i, th) => {
  2702. const isNumericCol = $(th).hasClass(numericClass);
  2703. this.$tdRows.each((_, row) => {
  2704. const $td = $(row).find('td').eq(i);
  2705. isNumericCol
  2706. ? $td.addClass(numericClass)
  2707. : $td.removeClass(numericClass);
  2708. });
  2709. });
  2710. }
  2711. }
  2712. const dataName = '_mdui_table';
  2713. $(() => {
  2714. mdui.mutation('.mdui-table', function () {
  2715. const $element = $(this);
  2716. if (!$element.data(dataName)) {
  2717. $element.data(dataName, new Table($element));
  2718. }
  2719. });
  2720. });
  2721. mdui.updateTables = function (selector) {
  2722. const $elements = isUndefined(selector) ? $('.mdui-table') : $(selector);
  2723. $elements.each((_, element) => {
  2724. const $element = $(element);
  2725. const instance = $element.data(dataName);
  2726. if (instance) {
  2727. instance.init();
  2728. }
  2729. else {
  2730. $element.data(dataName, new Table($element));
  2731. }
  2732. });
  2733. };
  2734. /**
  2735. * touch 事件后的 500ms 内禁用 mousedown 事件
  2736. *
  2737. * 不支持触控的屏幕上事件顺序为 mousedown -> mouseup -> click
  2738. * 支持触控的屏幕上事件顺序为 touchstart -> touchend -> mousedown -> mouseup -> click
  2739. *
  2740. * 在每一个事件中都使用 TouchHandler.isAllow(event) 判断事件是否可执行
  2741. * 在 touchstart 和 touchmove、touchend、touchcancel
  2742. *
  2743. * (function () {
  2744. * $document
  2745. * .on(start, function (e) {
  2746. * if (!isAllow(e)) {
  2747. * return;
  2748. * }
  2749. * register(e);
  2750. * console.log(e.type);
  2751. * })
  2752. * .on(move, function (e) {
  2753. * if (!isAllow(e)) {
  2754. * return;
  2755. * }
  2756. * console.log(e.type);
  2757. * })
  2758. * .on(end, function (e) {
  2759. * if (!isAllow(e)) {
  2760. * return;
  2761. * }
  2762. * console.log(e.type);
  2763. * })
  2764. * .on(unlock, register);
  2765. * })();
  2766. */
  2767. const startEvent = 'touchstart mousedown';
  2768. const moveEvent = 'touchmove mousemove';
  2769. const endEvent = 'touchend mouseup';
  2770. const cancelEvent = 'touchcancel mouseleave';
  2771. const unlockEvent = 'touchend touchmove touchcancel';
  2772. let touches = 0;
  2773. /**
  2774. * 该事件是否被允许,在执行事件前调用该方法判断事件是否可以执行
  2775. * 若已触发 touch 事件,则阻止之后的鼠标事件
  2776. * @param event
  2777. */
  2778. function isAllow(event) {
  2779. return !(touches &&
  2780. [
  2781. 'mousedown',
  2782. 'mouseup',
  2783. 'mousemove',
  2784. 'click',
  2785. 'mouseover',
  2786. 'mouseout',
  2787. 'mouseenter',
  2788. 'mouseleave',
  2789. ].indexOf(event.type) > -1);
  2790. }
  2791. /**
  2792. * 在 touchstart 和 touchmove、touchend、touchcancel 事件中调用该方法注册事件
  2793. * @param event
  2794. */
  2795. function register(event) {
  2796. if (event.type === 'touchstart') {
  2797. // 触发了 touch 事件
  2798. touches += 1;
  2799. }
  2800. else if (['touchmove', 'touchend', 'touchcancel'].indexOf(event.type) > -1) {
  2801. // touch 事件结束 500ms 后解除对鼠标事件的阻止
  2802. setTimeout(function () {
  2803. if (touches) {
  2804. touches -= 1;
  2805. }
  2806. }, 500);
  2807. }
  2808. }
  2809. /**
  2810. * Inspired by https://github.com/nolimits4web/Framework7/blob/master/src/js/fast-clicks.js
  2811. * https://github.com/nolimits4web/Framework7/blob/master/LICENSE
  2812. *
  2813. * Inspired by https://github.com/fians/Waves
  2814. */
  2815. /**
  2816. * 显示涟漪动画
  2817. * @param event
  2818. * @param $ripple
  2819. */
  2820. function show(event, $ripple) {
  2821. // 鼠标右键不产生涟漪
  2822. if (event instanceof MouseEvent && event.button === 2) {
  2823. return;
  2824. }
  2825. // 点击位置坐标
  2826. const touchPosition = typeof TouchEvent !== 'undefined' &&
  2827. event instanceof TouchEvent &&
  2828. event.touches.length
  2829. ? event.touches[0]
  2830. : event;
  2831. const touchStartX = touchPosition.pageX;
  2832. const touchStartY = touchPosition.pageY;
  2833. // 涟漪位置
  2834. const offset = $ripple.offset();
  2835. const height = $ripple.innerHeight();
  2836. const width = $ripple.innerWidth();
  2837. const center = {
  2838. x: touchStartX - offset.left,
  2839. y: touchStartY - offset.top,
  2840. };
  2841. const diameter = Math.max(Math.pow(Math.pow(height, 2) + Math.pow(width, 2), 0.5), 48);
  2842. // 涟漪扩散动画
  2843. const translate = `translate3d(${-center.x + width / 2}px,` +
  2844. `${-center.y + height / 2}px, 0) scale(1)`;
  2845. // 涟漪的 DOM 结构,并缓存动画效果
  2846. $(`<div class="mdui-ripple-wave" ` +
  2847. `style="width:${diameter}px;height:${diameter}px;` +
  2848. `margin-top:-${diameter / 2}px;margin-left:-${diameter / 2}px;` +
  2849. `left:${center.x}px;top:${center.y}px;"></div>`)
  2850. .data('_ripple_wave_translate', translate)
  2851. .prependTo($ripple)
  2852. .reflow()
  2853. .transform(translate);
  2854. }
  2855. /**
  2856. * 隐藏并移除涟漪
  2857. * @param $wave
  2858. */
  2859. function removeRipple($wave) {
  2860. if (!$wave.length || $wave.data('_ripple_wave_removed')) {
  2861. return;
  2862. }
  2863. $wave.data('_ripple_wave_removed', true);
  2864. let removeTimer = setTimeout(() => $wave.remove(), 400);
  2865. const translate = $wave.data('_ripple_wave_translate');
  2866. $wave
  2867. .addClass('mdui-ripple-wave-fill')
  2868. .transform(translate.replace('scale(1)', 'scale(1.01)'))
  2869. .transitionEnd(() => {
  2870. clearTimeout(removeTimer);
  2871. $wave
  2872. .addClass('mdui-ripple-wave-out')
  2873. .transform(translate.replace('scale(1)', 'scale(1.01)'));
  2874. removeTimer = setTimeout(() => $wave.remove(), 700);
  2875. setTimeout(() => {
  2876. $wave.transitionEnd(() => {
  2877. clearTimeout(removeTimer);
  2878. $wave.remove();
  2879. });
  2880. }, 0);
  2881. });
  2882. }
  2883. /**
  2884. * 隐藏涟漪动画
  2885. * @param this
  2886. */
  2887. function hide() {
  2888. const $ripple = $(this);
  2889. $ripple.children('.mdui-ripple-wave').each((_, wave) => {
  2890. removeRipple($(wave));
  2891. });
  2892. $ripple.off(`${moveEvent} ${endEvent} ${cancelEvent}`, hide);
  2893. }
  2894. /**
  2895. * 显示涟漪,并绑定 touchend 等事件
  2896. * @param event
  2897. */
  2898. function showRipple(event) {
  2899. if (!isAllow(event)) {
  2900. return;
  2901. }
  2902. register(event);
  2903. // Chrome 59 点击滚动条时,会在 document 上触发事件
  2904. if (event.target === document) {
  2905. return;
  2906. }
  2907. const $target = $(event.target);
  2908. // 获取含 .mdui-ripple 类的元素
  2909. const $ripple = $target.hasClass('mdui-ripple')
  2910. ? $target
  2911. : $target.parents('.mdui-ripple').first();
  2912. if (!$ripple.length) {
  2913. return;
  2914. }
  2915. // 禁用状态的元素上不产生涟漪效果
  2916. if ($ripple.prop('disabled') || !isUndefined($ripple.attr('disabled'))) {
  2917. return;
  2918. }
  2919. if (event.type === 'touchstart') {
  2920. let hidden = false;
  2921. // touchstart 触发指定时间后开始涟漪动画,避免手指滑动时也触发涟漪
  2922. let timer = setTimeout(() => {
  2923. timer = 0;
  2924. show(event, $ripple);
  2925. }, 200);
  2926. const hideRipple = () => {
  2927. // 如果手指没有移动,且涟漪动画还没有开始,则开始涟漪动画
  2928. if (timer) {
  2929. clearTimeout(timer);
  2930. timer = 0;
  2931. show(event, $ripple);
  2932. }
  2933. if (!hidden) {
  2934. hidden = true;
  2935. hide.call($ripple);
  2936. }
  2937. };
  2938. // 手指移动后,移除涟漪动画
  2939. const touchMove = () => {
  2940. if (timer) {
  2941. clearTimeout(timer);
  2942. timer = 0;
  2943. }
  2944. hideRipple();
  2945. };
  2946. $ripple.on('touchmove', touchMove).on('touchend touchcancel', hideRipple);
  2947. }
  2948. else {
  2949. show(event, $ripple);
  2950. $ripple.on(`${moveEvent} ${endEvent} ${cancelEvent}`, hide);
  2951. }
  2952. }
  2953. $(() => {
  2954. $document.on(startEvent, showRipple).on(unlockEvent, register);
  2955. });
  2956. const defaultData = {
  2957. reInit: false,
  2958. domLoadedEvent: false,
  2959. };
  2960. /**
  2961. * 输入框事件
  2962. * @param event
  2963. * @param data
  2964. */
  2965. function inputEvent(event, data = {}) {
  2966. data = extend({}, defaultData, data);
  2967. const input = event.target;
  2968. const $input = $(input);
  2969. const eventType = event.type;
  2970. const value = $input.val();
  2971. // 文本框类型
  2972. const inputType = $input.attr('type') || '';
  2973. if (['checkbox', 'button', 'submit', 'range', 'radio', 'image'].indexOf(inputType) > -1) {
  2974. return;
  2975. }
  2976. const $textfield = $input.parent('.mdui-textfield');
  2977. // 输入框是否聚焦
  2978. if (eventType === 'focus') {
  2979. $textfield.addClass('mdui-textfield-focus');
  2980. }
  2981. if (eventType === 'blur') {
  2982. $textfield.removeClass('mdui-textfield-focus');
  2983. }
  2984. // 输入框是否为空
  2985. if (eventType === 'blur' || eventType === 'input') {
  2986. value
  2987. ? $textfield.addClass('mdui-textfield-not-empty')
  2988. : $textfield.removeClass('mdui-textfield-not-empty');
  2989. }
  2990. // 输入框是否禁用
  2991. input.disabled
  2992. ? $textfield.addClass('mdui-textfield-disabled')
  2993. : $textfield.removeClass('mdui-textfield-disabled');
  2994. // 表单验证
  2995. if ((eventType === 'input' || eventType === 'blur') &&
  2996. !data.domLoadedEvent &&
  2997. input.validity) {
  2998. input.validity.valid
  2999. ? $textfield.removeClass('mdui-textfield-invalid-html5')
  3000. : $textfield.addClass('mdui-textfield-invalid-html5');
  3001. }
  3002. // textarea 高度自动调整
  3003. if ($input.is('textarea')) {
  3004. // IE bug:textarea 的值仅为多个换行,不含其他内容时,textarea 的高度不准确
  3005. // 此时,在计算高度前,在值的开头加入一个空格,计算完后,移除空格
  3006. const inputValue = value;
  3007. let hasExtraSpace = false;
  3008. if (inputValue.replace(/[\r\n]/g, '') === '') {
  3009. $input.val(' ' + inputValue);
  3010. hasExtraSpace = true;
  3011. }
  3012. // 设置 textarea 高度
  3013. $input.outerHeight('');
  3014. const height = $input.outerHeight();
  3015. const scrollHeight = input.scrollHeight;
  3016. if (scrollHeight > height) {
  3017. $input.outerHeight(scrollHeight);
  3018. }
  3019. // 计算完,还原 textarea 的值
  3020. if (hasExtraSpace) {
  3021. $input.val(inputValue);
  3022. }
  3023. }
  3024. // 实时字数统计
  3025. if (data.reInit) {
  3026. $textfield.find('.mdui-textfield-counter').remove();
  3027. }
  3028. const maxLength = $input.attr('maxlength');
  3029. if (maxLength) {
  3030. if (data.reInit || data.domLoadedEvent) {
  3031. $('<div class="mdui-textfield-counter">' +
  3032. `<span class="mdui-textfield-counter-inputed"></span> / ${maxLength}` +
  3033. '</div>').appendTo($textfield);
  3034. }
  3035. $textfield
  3036. .find('.mdui-textfield-counter-inputed')
  3037. .text(value.length.toString());
  3038. }
  3039. // 含 帮助文本、错误提示、字数统计 时,增加文本框底部内边距
  3040. if ($textfield.find('.mdui-textfield-helper').length ||
  3041. $textfield.find('.mdui-textfield-error').length ||
  3042. maxLength) {
  3043. $textfield.addClass('mdui-textfield-has-bottom');
  3044. }
  3045. }
  3046. $(() => {
  3047. // 绑定事件
  3048. $document.on('input focus blur', '.mdui-textfield-input', { useCapture: true }, inputEvent);
  3049. // 可展开文本框展开
  3050. $document.on('click', '.mdui-textfield-expandable .mdui-textfield-icon', function () {
  3051. $(this)
  3052. .parents('.mdui-textfield')
  3053. .addClass('mdui-textfield-expanded')
  3054. .find('.mdui-textfield-input')[0]
  3055. .focus();
  3056. });
  3057. // 可展开文本框关闭
  3058. $document.on('click', '.mdui-textfield-expanded .mdui-textfield-close', function () {
  3059. $(this)
  3060. .parents('.mdui-textfield')
  3061. .removeClass('mdui-textfield-expanded')
  3062. .find('.mdui-textfield-input')
  3063. .val('');
  3064. });
  3065. /**
  3066. * 初始化文本框
  3067. */
  3068. mdui.mutation('.mdui-textfield', function () {
  3069. $(this).find('.mdui-textfield-input').trigger('input', {
  3070. domLoadedEvent: true,
  3071. });
  3072. });
  3073. });
  3074. mdui.updateTextFields = function (selector) {
  3075. const $elements = isUndefined(selector) ? $('.mdui-textfield') : $(selector);
  3076. $elements.each((_, element) => {
  3077. $(element).find('.mdui-textfield-input').trigger('input', {
  3078. reInit: true,
  3079. });
  3080. });
  3081. };
  3082. /**
  3083. * 滑块的值改变后修改滑块样式
  3084. * @param $slider
  3085. */
  3086. function updateValueStyle($slider) {
  3087. const data = $slider.data();
  3088. const $track = data._slider_$track;
  3089. const $fill = data._slider_$fill;
  3090. const $thumb = data._slider_$thumb;
  3091. const $input = data._slider_$input;
  3092. const min = data._slider_min;
  3093. const max = data._slider_max;
  3094. const isDisabled = data._slider_disabled;
  3095. const isDiscrete = data._slider_discrete;
  3096. const $thumbText = data._slider_$thumbText;
  3097. const value = $input.val();
  3098. const percent = ((value - min) / (max - min)) * 100;
  3099. $fill.width(`${percent}%`);
  3100. $track.width(`${100 - percent}%`);
  3101. if (isDisabled) {
  3102. $fill.css('padding-right', '6px');
  3103. $track.css('padding-left', '6px');
  3104. }
  3105. $thumb.css('left', `${percent}%`);
  3106. if (isDiscrete) {
  3107. $thumbText.text(value);
  3108. }
  3109. percent === 0
  3110. ? $slider.addClass('mdui-slider-zero')
  3111. : $slider.removeClass('mdui-slider-zero');
  3112. }
  3113. /**
  3114. * 重新初始化滑块
  3115. * @param $slider
  3116. */
  3117. function reInit($slider) {
  3118. const $track = $('<div class="mdui-slider-track"></div>');
  3119. const $fill = $('<div class="mdui-slider-fill"></div>');
  3120. const $thumb = $('<div class="mdui-slider-thumb"></div>');
  3121. const $input = $slider.find('input[type="range"]');
  3122. const isDisabled = $input[0].disabled;
  3123. const isDiscrete = $slider.hasClass('mdui-slider-discrete');
  3124. // 禁用状态
  3125. isDisabled
  3126. ? $slider.addClass('mdui-slider-disabled')
  3127. : $slider.removeClass('mdui-slider-disabled');
  3128. // 重新填充 HTML
  3129. $slider.find('.mdui-slider-track').remove();
  3130. $slider.find('.mdui-slider-fill').remove();
  3131. $slider.find('.mdui-slider-thumb').remove();
  3132. $slider.append($track).append($fill).append($thumb);
  3133. // 间续型滑块
  3134. let $thumbText = $();
  3135. if (isDiscrete) {
  3136. $thumbText = $('<span></span>');
  3137. $thumb.empty().append($thumbText);
  3138. }
  3139. $slider.data('_slider_$track', $track);
  3140. $slider.data('_slider_$fill', $fill);
  3141. $slider.data('_slider_$thumb', $thumb);
  3142. $slider.data('_slider_$input', $input);
  3143. $slider.data('_slider_min', $input.attr('min'));
  3144. $slider.data('_slider_max', $input.attr('max'));
  3145. $slider.data('_slider_disabled', isDisabled);
  3146. $slider.data('_slider_discrete', isDiscrete);
  3147. $slider.data('_slider_$thumbText', $thumbText);
  3148. // 设置默认值
  3149. updateValueStyle($slider);
  3150. }
  3151. const rangeSelector = '.mdui-slider input[type="range"]';
  3152. $(() => {
  3153. // 滑块滑动事件
  3154. $document.on('input change', rangeSelector, function () {
  3155. const $slider = $(this).parent();
  3156. updateValueStyle($slider);
  3157. });
  3158. // 开始触摸滑块事件
  3159. $document.on(startEvent, rangeSelector, function (event) {
  3160. if (!isAllow(event)) {
  3161. return;
  3162. }
  3163. register(event);
  3164. if (this.disabled) {
  3165. return;
  3166. }
  3167. const $slider = $(this).parent();
  3168. $slider.addClass('mdui-slider-focus');
  3169. });
  3170. // 结束触摸滑块事件
  3171. $document.on(endEvent, rangeSelector, function (event) {
  3172. if (!isAllow(event)) {
  3173. return;
  3174. }
  3175. if (this.disabled) {
  3176. return;
  3177. }
  3178. const $slider = $(this).parent();
  3179. $slider.removeClass('mdui-slider-focus');
  3180. });
  3181. $document.on(unlockEvent, rangeSelector, register);
  3182. /**
  3183. * 初始化滑块
  3184. */
  3185. mdui.mutation('.mdui-slider', function () {
  3186. reInit($(this));
  3187. });
  3188. });
  3189. mdui.updateSliders = function (selector) {
  3190. const $elements = isUndefined(selector) ? $('.mdui-slider') : $(selector);
  3191. $elements.each((_, element) => {
  3192. reInit($(element));
  3193. });
  3194. };
  3195. const DEFAULT_OPTIONS$2 = {
  3196. trigger: 'hover',
  3197. };
  3198. class Fab {
  3199. constructor(selector, options = {}) {
  3200. /**
  3201. * 配置参数
  3202. */
  3203. this.options = extend({}, DEFAULT_OPTIONS$2);
  3204. /**
  3205. * 当前 fab 的状态
  3206. */
  3207. this.state = 'closed';
  3208. this.$element = $(selector).first();
  3209. extend(this.options, options);
  3210. this.$btn = this.$element.find('.mdui-fab');
  3211. this.$dial = this.$element.find('.mdui-fab-dial');
  3212. this.$dialBtns = this.$dial.find('.mdui-fab');
  3213. if (this.options.trigger === 'hover') {
  3214. this.$btn.on('touchstart mouseenter', () => this.open());
  3215. this.$element.on('mouseleave', () => this.close());
  3216. }
  3217. if (this.options.trigger === 'click') {
  3218. this.$btn.on(startEvent, () => this.open());
  3219. }
  3220. // 触摸屏幕其他地方关闭快速拨号
  3221. $document.on(startEvent, (event) => {
  3222. if ($(event.target).parents('.mdui-fab-wrapper').length) {
  3223. return;
  3224. }
  3225. this.close();
  3226. });
  3227. }
  3228. /**
  3229. * 触发组件事件
  3230. * @param name
  3231. */
  3232. triggerEvent(name) {
  3233. componentEvent(name, 'fab', this.$element, this);
  3234. }
  3235. /**
  3236. * 当前是否为打开状态
  3237. */
  3238. isOpen() {
  3239. return this.state === 'opening' || this.state === 'opened';
  3240. }
  3241. /**
  3242. * 打开快速拨号菜单
  3243. */
  3244. open() {
  3245. if (this.isOpen()) {
  3246. return;
  3247. }
  3248. // 为菜单中的按钮添加不同的 transition-delay
  3249. this.$dialBtns.each((index, btn) => {
  3250. const delay = `${15 * (this.$dialBtns.length - index)}ms`;
  3251. btn.style.transitionDelay = delay;
  3252. btn.style.webkitTransitionDelay = delay;
  3253. });
  3254. this.$dial.css('height', 'auto').addClass('mdui-fab-dial-show');
  3255. // 如果按钮中存在 .mdui-fab-opened 的图标,则进行图标切换
  3256. if (this.$btn.find('.mdui-fab-opened').length) {
  3257. this.$btn.addClass('mdui-fab-opened');
  3258. }
  3259. this.state = 'opening';
  3260. this.triggerEvent('open');
  3261. // 打开顺序为从下到上逐个打开,最上面的打开后才表示动画完成
  3262. this.$dialBtns.first().transitionEnd(() => {
  3263. if (this.$btn.hasClass('mdui-fab-opened')) {
  3264. this.state = 'opened';
  3265. this.triggerEvent('opened');
  3266. }
  3267. });
  3268. }
  3269. /**
  3270. * 关闭快速拨号菜单
  3271. */
  3272. close() {
  3273. if (!this.isOpen()) {
  3274. return;
  3275. }
  3276. // 为菜单中的按钮添加不同的 transition-delay
  3277. this.$dialBtns.each((index, btn) => {
  3278. const delay = `${15 * index}ms`;
  3279. btn.style.transitionDelay = delay;
  3280. btn.style.webkitTransitionDelay = delay;
  3281. });
  3282. this.$dial.removeClass('mdui-fab-dial-show');
  3283. this.$btn.removeClass('mdui-fab-opened');
  3284. this.state = 'closing';
  3285. this.triggerEvent('close');
  3286. // 从上往下依次关闭,最后一个关闭后才表示动画完成
  3287. this.$dialBtns.last().transitionEnd(() => {
  3288. if (this.$btn.hasClass('mdui-fab-opened')) {
  3289. return;
  3290. }
  3291. this.state = 'closed';
  3292. this.triggerEvent('closed');
  3293. this.$dial.css('height', 0);
  3294. });
  3295. }
  3296. /**
  3297. * 切换快速拨号菜单的打开状态
  3298. */
  3299. toggle() {
  3300. this.isOpen() ? this.close() : this.open();
  3301. }
  3302. /**
  3303. * 以动画的形式显示整个浮动操作按钮
  3304. */
  3305. show() {
  3306. this.$element.removeClass('mdui-fab-hide');
  3307. }
  3308. /**
  3309. * 以动画的形式隐藏整个浮动操作按钮
  3310. */
  3311. hide() {
  3312. this.$element.addClass('mdui-fab-hide');
  3313. }
  3314. /**
  3315. * 返回当前快速拨号菜单的打开状态。共包含四种状态:`opening`、`opened`、`closing`、`closed`
  3316. */
  3317. getState() {
  3318. return this.state;
  3319. }
  3320. }
  3321. mdui.Fab = Fab;
  3322. const customAttr$3 = 'mdui-fab';
  3323. $(() => {
  3324. // mouseenter 不冒泡,无法进行事件委托,这里用 mouseover 代替。
  3325. // 不管是 click 、 mouseover 还是 touchstart ,都先初始化。
  3326. $document.on('touchstart mousedown mouseover', `[${customAttr$3}]`, function () {
  3327. new mdui.Fab(this, parseOptions(this, customAttr$3));
  3328. });
  3329. });
  3330. /**
  3331. * 最终生成的元素结构为:
  3332. * <select class="mdui-select" mdui-select="{position: 'top'}" style="display: none;"> // $native
  3333. * <option value="1">State 1</option>
  3334. * <option value="2">State 2</option>
  3335. * <option value="3" disabled="">State 3</option>
  3336. * </select>
  3337. * <div class="mdui-select mdui-select-position-top" style="" id="88dec0e4-d4a2-c6d0-0e7f-1ba4501e0553"> // $element
  3338. * <span class="mdui-select-selected">State 1</span> // $selected
  3339. * <div class="mdui-select-menu" style="transform-origin: center 100% 0px;"> // $menu
  3340. * <div class="mdui-select-menu-item mdui-ripple" selected="">State 1</div> // $items
  3341. * <div class="mdui-select-menu-item mdui-ripple">State 2</div>
  3342. * <div class="mdui-select-menu-item mdui-ripple" disabled="">State 3</div>
  3343. * </div>
  3344. * </div>
  3345. */
  3346. const DEFAULT_OPTIONS$3 = {
  3347. position: 'auto',
  3348. gutter: 16,
  3349. };
  3350. class Select {
  3351. constructor(selector, options = {}) {
  3352. /**
  3353. * 生成的 `<div class="mdui-select">` 元素的 JQ 对象
  3354. */
  3355. this.$element = $();
  3356. /**
  3357. * 配置参数
  3358. */
  3359. this.options = extend({}, DEFAULT_OPTIONS$3);
  3360. /**
  3361. * select 的 size 属性的值,根据该值设置 select 的高度
  3362. */
  3363. this.size = 0;
  3364. /**
  3365. * 占位元素,显示已选中菜单项的文本
  3366. */
  3367. this.$selected = $();
  3368. /**
  3369. * 菜单项的外层元素的 JQ 对象
  3370. */
  3371. this.$menu = $();
  3372. /**
  3373. * 菜单项数组的 JQ 对象
  3374. */
  3375. this.$items = $();
  3376. /**
  3377. * 当前选中的菜单项的索引号
  3378. */
  3379. this.selectedIndex = 0;
  3380. /**
  3381. * 当前选中菜单项的文本
  3382. */
  3383. this.selectedText = '';
  3384. /**
  3385. * 当前选中菜单项的值
  3386. */
  3387. this.selectedValue = '';
  3388. /**
  3389. * 当前 select 的状态
  3390. */
  3391. this.state = 'closed';
  3392. this.$native = $(selector).first();
  3393. this.$native.hide();
  3394. extend(this.options, options);
  3395. // 为当前 select 生成唯一 ID
  3396. this.uniqueID = $.guid();
  3397. // 生成 select
  3398. this.handleUpdate();
  3399. // 点击 select 外面区域关闭
  3400. $document.on('click touchstart', (event) => {
  3401. const $target = $(event.target);
  3402. if (this.isOpen() &&
  3403. !$target.is(this.$element) &&
  3404. !contains(this.$element[0], $target[0])) {
  3405. this.close();
  3406. }
  3407. });
  3408. }
  3409. /**
  3410. * 调整菜单位置
  3411. */
  3412. readjustMenu() {
  3413. const windowHeight = $window.height();
  3414. // mdui-select 高度
  3415. const elementHeight = this.$element.height();
  3416. // 菜单项高度
  3417. const $itemFirst = this.$items.first();
  3418. const itemHeight = $itemFirst.height();
  3419. const itemMargin = parseInt($itemFirst.css('margin-top'));
  3420. // 菜单高度
  3421. const menuWidth = this.$element.innerWidth() + 0.01; // 必须比真实宽度多一点,不然会出现省略号
  3422. let menuHeight = itemHeight * this.size + itemMargin * 2;
  3423. // mdui-select 在窗口中的位置
  3424. const elementTop = this.$element[0].getBoundingClientRect().top;
  3425. let transformOriginY;
  3426. let menuMarginTop;
  3427. if (this.options.position === 'bottom') {
  3428. menuMarginTop = elementHeight;
  3429. transformOriginY = '0px';
  3430. }
  3431. else if (this.options.position === 'top') {
  3432. menuMarginTop = -menuHeight - 1;
  3433. transformOriginY = '100%';
  3434. }
  3435. else {
  3436. // 菜单高度不能超过窗口高度
  3437. const menuMaxHeight = windowHeight - this.options.gutter * 2;
  3438. if (menuHeight > menuMaxHeight) {
  3439. menuHeight = menuMaxHeight;
  3440. }
  3441. // 菜单的 margin-top
  3442. menuMarginTop = -(itemMargin +
  3443. this.selectedIndex * itemHeight +
  3444. (itemHeight - elementHeight) / 2);
  3445. const menuMaxMarginTop = -(itemMargin +
  3446. (this.size - 1) * itemHeight +
  3447. (itemHeight - elementHeight) / 2);
  3448. if (menuMarginTop < menuMaxMarginTop) {
  3449. menuMarginTop = menuMaxMarginTop;
  3450. }
  3451. // 菜单不能超出窗口
  3452. const menuTop = elementTop + menuMarginTop;
  3453. if (menuTop < this.options.gutter) {
  3454. // 不能超出窗口上方
  3455. menuMarginTop = -(elementTop - this.options.gutter);
  3456. }
  3457. else if (menuTop + menuHeight + this.options.gutter > windowHeight) {
  3458. // 不能超出窗口下方
  3459. menuMarginTop = -(elementTop +
  3460. menuHeight +
  3461. this.options.gutter -
  3462. windowHeight);
  3463. }
  3464. // transform 的 Y 轴坐标
  3465. transformOriginY = `${this.selectedIndex * itemHeight + itemHeight / 2 + itemMargin}px`;
  3466. }
  3467. // 设置样式
  3468. this.$element.innerWidth(menuWidth);
  3469. this.$menu
  3470. .innerWidth(menuWidth)
  3471. .height(menuHeight)
  3472. .css({
  3473. 'margin-top': menuMarginTop + 'px',
  3474. 'transform-origin': 'center ' + transformOriginY + ' 0',
  3475. });
  3476. }
  3477. /**
  3478. * select 是否为打开状态
  3479. */
  3480. isOpen() {
  3481. return this.state === 'opening' || this.state === 'opened';
  3482. }
  3483. /**
  3484. * 对原生 select 组件进行了修改后,需要调用该方法
  3485. */
  3486. handleUpdate() {
  3487. if (this.isOpen()) {
  3488. this.close();
  3489. }
  3490. this.selectedValue = this.$native.val();
  3491. const itemsData = [];
  3492. this.$items = $();
  3493. // 生成 HTML
  3494. this.$native.find('option').each((index, option) => {
  3495. const text = option.textContent || '';
  3496. const value = option.value;
  3497. const disabled = option.disabled;
  3498. const selected = this.selectedValue === value;
  3499. itemsData.push({
  3500. value,
  3501. text,
  3502. disabled,
  3503. selected,
  3504. index,
  3505. });
  3506. if (selected) {
  3507. this.selectedText = text;
  3508. this.selectedIndex = index;
  3509. }
  3510. this.$items = this.$items.add('<div class="mdui-select-menu-item mdui-ripple"' +
  3511. (disabled ? ' disabled' : '') +
  3512. (selected ? ' selected' : '') +
  3513. `>${text}</div>`);
  3514. });
  3515. this.$selected = $(`<span class="mdui-select-selected">${this.selectedText}</span>`);
  3516. this.$element = $(`<div class="mdui-select mdui-select-position-${this.options.position}" ` +
  3517. `style="${this.$native.attr('style')}" ` +
  3518. `id="${this.uniqueID}"></div>`)
  3519. .show()
  3520. .append(this.$selected);
  3521. this.$menu = $('<div class="mdui-select-menu"></div>')
  3522. .appendTo(this.$element)
  3523. .append(this.$items);
  3524. $(`#${this.uniqueID}`).remove();
  3525. this.$native.after(this.$element);
  3526. // 根据 select 的 size 属性设置高度
  3527. this.size = parseInt(this.$native.attr('size') || '0');
  3528. if (this.size <= 0) {
  3529. this.size = this.$items.length;
  3530. if (this.size > 8) {
  3531. this.size = 8;
  3532. }
  3533. }
  3534. // 点击选项时关闭下拉菜单
  3535. // eslint-disable-next-line @typescript-eslint/no-this-alias
  3536. const that = this;
  3537. this.$items.on('click', function () {
  3538. if (that.state === 'closing') {
  3539. return;
  3540. }
  3541. const $item = $(this);
  3542. const index = $item.index();
  3543. const data = itemsData[index];
  3544. if (data.disabled) {
  3545. return;
  3546. }
  3547. that.$selected.text(data.text);
  3548. that.$native.val(data.value);
  3549. that.$items.removeAttr('selected');
  3550. $item.attr('selected', '');
  3551. that.selectedIndex = data.index;
  3552. that.selectedValue = data.value;
  3553. that.selectedText = data.text;
  3554. that.$native.trigger('change');
  3555. that.close();
  3556. });
  3557. // 点击 $element 时打开下拉菜单
  3558. this.$element.on('click', (event) => {
  3559. const $target = $(event.target);
  3560. // 在菜单上点击时不打开
  3561. if ($target.is('.mdui-select-menu') ||
  3562. $target.is('.mdui-select-menu-item')) {
  3563. return;
  3564. }
  3565. this.toggle();
  3566. });
  3567. }
  3568. /**
  3569. * 动画结束的回调
  3570. */
  3571. transitionEnd() {
  3572. this.$element.removeClass('mdui-select-closing');
  3573. if (this.state === 'opening') {
  3574. this.state = 'opened';
  3575. this.triggerEvent('opened');
  3576. this.$menu.css('overflow-y', 'auto');
  3577. }
  3578. if (this.state === 'closing') {
  3579. this.state = 'closed';
  3580. this.triggerEvent('closed');
  3581. // 恢复样式
  3582. this.$element.innerWidth('');
  3583. this.$menu.css({
  3584. 'margin-top': '',
  3585. height: '',
  3586. width: '',
  3587. });
  3588. }
  3589. }
  3590. /**
  3591. * 触发组件事件
  3592. * @param name
  3593. */
  3594. triggerEvent(name) {
  3595. componentEvent(name, 'select', this.$native, this);
  3596. }
  3597. /**
  3598. * 切换下拉菜单的打开状态
  3599. */
  3600. toggle() {
  3601. this.isOpen() ? this.close() : this.open();
  3602. }
  3603. /**
  3604. * 打开下拉菜单
  3605. */
  3606. open() {
  3607. if (this.isOpen()) {
  3608. return;
  3609. }
  3610. this.state = 'opening';
  3611. this.triggerEvent('open');
  3612. this.readjustMenu();
  3613. this.$element.addClass('mdui-select-open');
  3614. this.$menu.transitionEnd(() => this.transitionEnd());
  3615. }
  3616. /**
  3617. * 关闭下拉菜单
  3618. */
  3619. close() {
  3620. if (!this.isOpen()) {
  3621. return;
  3622. }
  3623. this.state = 'closing';
  3624. this.triggerEvent('close');
  3625. this.$menu.css('overflow-y', '');
  3626. this.$element
  3627. .removeClass('mdui-select-open')
  3628. .addClass('mdui-select-closing');
  3629. this.$menu.transitionEnd(() => this.transitionEnd());
  3630. }
  3631. /**
  3632. * 获取当前菜单的状态。共包含四种状态:`opening`、`opened`、`closing`、`closed`
  3633. */
  3634. getState() {
  3635. return this.state;
  3636. }
  3637. }
  3638. mdui.Select = Select;
  3639. const customAttr$4 = 'mdui-select';
  3640. $(() => {
  3641. mdui.mutation(`[${customAttr$4}]`, function () {
  3642. new mdui.Select(this, parseOptions(this, customAttr$4));
  3643. });
  3644. });
  3645. $(() => {
  3646. // 滚动时隐藏应用栏
  3647. mdui.mutation('.mdui-appbar-scroll-hide', function () {
  3648. new mdui.Headroom(this);
  3649. });
  3650. // 滚动时只隐藏应用栏中的工具栏
  3651. mdui.mutation('.mdui-appbar-scroll-toolbar-hide', function () {
  3652. new mdui.Headroom(this, {
  3653. pinnedClass: 'mdui-headroom-pinned-toolbar',
  3654. unpinnedClass: 'mdui-headroom-unpinned-toolbar',
  3655. });
  3656. });
  3657. });
  3658. const DEFAULT_OPTIONS$4 = {
  3659. trigger: 'click',
  3660. loop: false,
  3661. };
  3662. class Tab {
  3663. constructor(selector, options = {}) {
  3664. /**
  3665. * 配置参数
  3666. */
  3667. this.options = extend({}, DEFAULT_OPTIONS$4);
  3668. /**
  3669. * 当前激活的 tab 的索引号。为 -1 时表示没有激活的选项卡,或不存在选项卡
  3670. */
  3671. this.activeIndex = -1;
  3672. this.$element = $(selector).first();
  3673. extend(this.options, options);
  3674. this.$tabs = this.$element.children('a');
  3675. this.$indicator = $('<div class="mdui-tab-indicator"></div>').appendTo(this.$element);
  3676. // 根据 url hash 获取默认激活的选项卡
  3677. const hash = window.location.hash;
  3678. if (hash) {
  3679. this.$tabs.each((index, tab) => {
  3680. if ($(tab).attr('href') === hash) {
  3681. this.activeIndex = index;
  3682. return false;
  3683. }
  3684. return true;
  3685. });
  3686. }
  3687. // 含 .mdui-tab-active 的元素默认激活
  3688. if (this.activeIndex === -1) {
  3689. this.$tabs.each((index, tab) => {
  3690. if ($(tab).hasClass('mdui-tab-active')) {
  3691. this.activeIndex = index;
  3692. return false;
  3693. }
  3694. return true;
  3695. });
  3696. }
  3697. // 存在选项卡时,默认激活第一个选项卡
  3698. if (this.$tabs.length && this.activeIndex === -1) {
  3699. this.activeIndex = 0;
  3700. }
  3701. // 设置激活状态选项卡
  3702. this.setActive();
  3703. // 监听窗口大小变化事件,调整指示器位置
  3704. $window.on('resize', $.throttle(() => this.setIndicatorPosition(), 100));
  3705. // 监听点击选项卡事件
  3706. this.$tabs.each((_, tab) => {
  3707. this.bindTabEvent(tab);
  3708. });
  3709. }
  3710. /**
  3711. * 指定选项卡是否已禁用
  3712. * @param $tab
  3713. */
  3714. isDisabled($tab) {
  3715. return $tab.attr('disabled') !== undefined;
  3716. }
  3717. /**
  3718. * 绑定在 Tab 上点击或悬浮的事件
  3719. * @param tab
  3720. */
  3721. bindTabEvent(tab) {
  3722. const $tab = $(tab);
  3723. // 点击或鼠标移入触发的事件
  3724. const clickEvent = () => {
  3725. // 禁用状态的选项卡无法选中
  3726. if (this.isDisabled($tab)) {
  3727. return false;
  3728. }
  3729. this.activeIndex = this.$tabs.index(tab);
  3730. this.setActive();
  3731. };
  3732. // 无论 trigger 是 click 还是 hover,都会响应 click 事件
  3733. $tab.on('click', clickEvent);
  3734. // trigger 为 hover 时,额外响应 mouseenter 事件
  3735. if (this.options.trigger === 'hover') {
  3736. $tab.on('mouseenter', clickEvent);
  3737. }
  3738. // 阻止链接的默认点击动作
  3739. $tab.on('click', () => {
  3740. if (($tab.attr('href') || '').indexOf('#') === 0) {
  3741. return false;
  3742. }
  3743. });
  3744. }
  3745. /**
  3746. * 触发组件事件
  3747. * @param name
  3748. * @param $element
  3749. * @param parameters
  3750. */
  3751. triggerEvent(name, $element, parameters = {}) {
  3752. componentEvent(name, 'tab', $element, this, parameters);
  3753. }
  3754. /**
  3755. * 设置激活状态的选项卡
  3756. */
  3757. setActive() {
  3758. this.$tabs.each((index, tab) => {
  3759. const $tab = $(tab);
  3760. const targetId = $tab.attr('href') || '';
  3761. // 设置选项卡激活状态
  3762. if (index === this.activeIndex && !this.isDisabled($tab)) {
  3763. if (!$tab.hasClass('mdui-tab-active')) {
  3764. this.triggerEvent('change', this.$element, {
  3765. index: this.activeIndex,
  3766. id: targetId.substr(1),
  3767. });
  3768. this.triggerEvent('show', $tab);
  3769. $tab.addClass('mdui-tab-active');
  3770. }
  3771. $(targetId).show();
  3772. this.setIndicatorPosition();
  3773. }
  3774. else {
  3775. $tab.removeClass('mdui-tab-active');
  3776. $(targetId).hide();
  3777. }
  3778. });
  3779. }
  3780. /**
  3781. * 设置选项卡指示器的位置
  3782. */
  3783. setIndicatorPosition() {
  3784. // 选项卡数量为 0 时,不显示指示器
  3785. if (this.activeIndex === -1) {
  3786. this.$indicator.css({
  3787. left: 0,
  3788. width: 0,
  3789. });
  3790. return;
  3791. }
  3792. const $activeTab = this.$tabs.eq(this.activeIndex);
  3793. if (this.isDisabled($activeTab)) {
  3794. return;
  3795. }
  3796. const activeTabOffset = $activeTab.offset();
  3797. this.$indicator.css({
  3798. left: `${activeTabOffset.left +
  3799. this.$element[0].scrollLeft -
  3800. this.$element[0].getBoundingClientRect().left}px`,
  3801. width: `${$activeTab.innerWidth()}px`,
  3802. });
  3803. }
  3804. /**
  3805. * 切换到下一个选项卡
  3806. */
  3807. next() {
  3808. if (this.activeIndex === -1) {
  3809. return;
  3810. }
  3811. if (this.$tabs.length > this.activeIndex + 1) {
  3812. this.activeIndex++;
  3813. }
  3814. else if (this.options.loop) {
  3815. this.activeIndex = 0;
  3816. }
  3817. this.setActive();
  3818. }
  3819. /**
  3820. * 切换到上一个选项卡
  3821. */
  3822. prev() {
  3823. if (this.activeIndex === -1) {
  3824. return;
  3825. }
  3826. if (this.activeIndex > 0) {
  3827. this.activeIndex--;
  3828. }
  3829. else if (this.options.loop) {
  3830. this.activeIndex = this.$tabs.length - 1;
  3831. }
  3832. this.setActive();
  3833. }
  3834. /**
  3835. * 显示指定索引号、或指定id的选项卡
  3836. * @param index 索引号、或id
  3837. */
  3838. show(index) {
  3839. if (this.activeIndex === -1) {
  3840. return;
  3841. }
  3842. if (isNumber(index)) {
  3843. this.activeIndex = index;
  3844. }
  3845. else {
  3846. this.$tabs.each((i, tab) => {
  3847. if (tab.id === index) {
  3848. this.activeIndex = i;
  3849. return false;
  3850. }
  3851. });
  3852. }
  3853. this.setActive();
  3854. }
  3855. /**
  3856. * 在父元素的宽度变化时,需要调用该方法重新调整指示器位置
  3857. * 在添加或删除选项卡时,需要调用该方法
  3858. */
  3859. handleUpdate() {
  3860. const $oldTabs = this.$tabs; // 旧的 tabs JQ对象
  3861. const $newTabs = this.$element.children('a'); // 新的 tabs JQ对象
  3862. const oldTabsElement = $oldTabs.get(); // 旧的 tabs 元素数组
  3863. const newTabsElement = $newTabs.get(); // 新的 tabs 元素数组
  3864. if (!$newTabs.length) {
  3865. this.activeIndex = -1;
  3866. this.$tabs = $newTabs;
  3867. this.setIndicatorPosition();
  3868. return;
  3869. }
  3870. // 重新遍历选项卡,找出新增的选项卡
  3871. $newTabs.each((index, tab) => {
  3872. // 有新增的选项卡
  3873. if (oldTabsElement.indexOf(tab) < 0) {
  3874. this.bindTabEvent(tab);
  3875. if (this.activeIndex === -1) {
  3876. this.activeIndex = 0;
  3877. }
  3878. else if (index <= this.activeIndex) {
  3879. this.activeIndex++;
  3880. }
  3881. }
  3882. });
  3883. // 找出被移除的选项卡
  3884. $oldTabs.each((index, tab) => {
  3885. // 有被移除的选项卡
  3886. if (newTabsElement.indexOf(tab) < 0) {
  3887. if (index < this.activeIndex) {
  3888. this.activeIndex--;
  3889. }
  3890. else if (index === this.activeIndex) {
  3891. this.activeIndex = 0;
  3892. }
  3893. }
  3894. });
  3895. this.$tabs = $newTabs;
  3896. this.setActive();
  3897. }
  3898. }
  3899. mdui.Tab = Tab;
  3900. const customAttr$5 = 'mdui-tab';
  3901. $(() => {
  3902. mdui.mutation(`[${customAttr$5}]`, function () {
  3903. new mdui.Tab(this, parseOptions(this, customAttr$5));
  3904. });
  3905. });
  3906. /**
  3907. * 在桌面设备上默认显示抽屉栏,不显示遮罩层
  3908. * 在手机和平板设备上默认不显示抽屉栏,始终显示遮罩层,且覆盖导航栏
  3909. */
  3910. const DEFAULT_OPTIONS$5 = {
  3911. overlay: false,
  3912. swipe: false,
  3913. };
  3914. class Drawer {
  3915. constructor(selector, options = {}) {
  3916. /**
  3917. * 配置参数
  3918. */
  3919. this.options = extend({}, DEFAULT_OPTIONS$5);
  3920. /**
  3921. * 当前是否显示着遮罩层
  3922. */
  3923. this.overlay = false;
  3924. this.$element = $(selector).first();
  3925. extend(this.options, options);
  3926. this.position = this.$element.hasClass('mdui-drawer-right')
  3927. ? 'right'
  3928. : 'left';
  3929. if (this.$element.hasClass('mdui-drawer-close')) {
  3930. this.state = 'closed';
  3931. }
  3932. else if (this.$element.hasClass('mdui-drawer-open')) {
  3933. this.state = 'opened';
  3934. }
  3935. else if (this.isDesktop()) {
  3936. this.state = 'opened';
  3937. }
  3938. else {
  3939. this.state = 'closed';
  3940. }
  3941. // 浏览器窗口大小调整时
  3942. $window.on('resize', $.throttle(() => {
  3943. if (this.isDesktop()) {
  3944. // 由手机平板切换到桌面时
  3945. // 如果显示着遮罩,则隐藏遮罩
  3946. if (this.overlay && !this.options.overlay) {
  3947. $.hideOverlay();
  3948. this.overlay = false;
  3949. $.unlockScreen();
  3950. }
  3951. // 没有强制关闭,则状态为打开状态
  3952. if (!this.$element.hasClass('mdui-drawer-close')) {
  3953. this.state = 'opened';
  3954. }
  3955. }
  3956. else if (!this.overlay && this.state === 'opened') {
  3957. // 由桌面切换到手机平板时。如果抽屉栏是打开着的且没有遮罩层,则关闭抽屉栏
  3958. if (this.$element.hasClass('mdui-drawer-open')) {
  3959. $.showOverlay();
  3960. this.overlay = true;
  3961. $.lockScreen();
  3962. $('.mdui-overlay').one('click', () => this.close());
  3963. }
  3964. else {
  3965. this.state = 'closed';
  3966. }
  3967. }
  3968. }, 100));
  3969. // 绑定关闭按钮事件
  3970. this.$element.find('[mdui-drawer-close]').each((_, close) => {
  3971. $(close).on('click', () => this.close());
  3972. });
  3973. this.swipeSupport();
  3974. }
  3975. /**
  3976. * 是否是桌面设备
  3977. */
  3978. isDesktop() {
  3979. return $window.width() >= 1024;
  3980. }
  3981. /**
  3982. * 滑动手势支持
  3983. */
  3984. swipeSupport() {
  3985. // eslint-disable-next-line @typescript-eslint/no-this-alias
  3986. const that = this;
  3987. // 抽屉栏滑动手势控制
  3988. let openNavEventHandler;
  3989. let touchStartX;
  3990. let touchStartY;
  3991. let swipeStartX;
  3992. let swiping = null;
  3993. let maybeSwiping = false;
  3994. const $body = $('body');
  3995. // 手势触发的范围
  3996. const swipeAreaWidth = 24;
  3997. function setPosition(translateX) {
  3998. const rtlTranslateMultiplier = that.position === 'right' ? -1 : 1;
  3999. const transformCSS = `translate(${-1 * rtlTranslateMultiplier * translateX}px, 0) !important;`;
  4000. const transitionCSS = 'initial !important;';
  4001. that.$element.css('cssText', `transform: ${transformCSS}; transition: ${transitionCSS};`);
  4002. }
  4003. function cleanPosition() {
  4004. that.$element[0].style.transform = '';
  4005. that.$element[0].style.webkitTransform = '';
  4006. that.$element[0].style.transition = '';
  4007. that.$element[0].style.webkitTransition = '';
  4008. }
  4009. function getMaxTranslateX() {
  4010. return that.$element.width() + 10;
  4011. }
  4012. function getTranslateX(currentX) {
  4013. return Math.min(Math.max(swiping === 'closing'
  4014. ? swipeStartX - currentX
  4015. : getMaxTranslateX() + swipeStartX - currentX, 0), getMaxTranslateX());
  4016. }
  4017. function onBodyTouchEnd(event) {
  4018. if (swiping) {
  4019. let touchX = event.changedTouches[0].pageX;
  4020. if (that.position === 'right') {
  4021. touchX = $body.width() - touchX;
  4022. }
  4023. const translateRatio = getTranslateX(touchX) / getMaxTranslateX();
  4024. maybeSwiping = false;
  4025. const swipingState = swiping;
  4026. swiping = null;
  4027. if (swipingState === 'opening') {
  4028. if (translateRatio < 0.92) {
  4029. cleanPosition();
  4030. that.open();
  4031. }
  4032. else {
  4033. cleanPosition();
  4034. }
  4035. }
  4036. else {
  4037. if (translateRatio > 0.08) {
  4038. cleanPosition();
  4039. that.close();
  4040. }
  4041. else {
  4042. cleanPosition();
  4043. }
  4044. }
  4045. $.unlockScreen();
  4046. }
  4047. else {
  4048. maybeSwiping = false;
  4049. }
  4050. $body.off({
  4051. // eslint-disable-next-line @typescript-eslint/no-use-before-define
  4052. touchmove: onBodyTouchMove,
  4053. touchend: onBodyTouchEnd,
  4054. // eslint-disable-next-line @typescript-eslint/no-use-before-define
  4055. touchcancel: onBodyTouchMove,
  4056. });
  4057. }
  4058. function onBodyTouchMove(event) {
  4059. let touchX = event.touches[0].pageX;
  4060. if (that.position === 'right') {
  4061. touchX = $body.width() - touchX;
  4062. }
  4063. const touchY = event.touches[0].pageY;
  4064. if (swiping) {
  4065. setPosition(getTranslateX(touchX));
  4066. }
  4067. else if (maybeSwiping) {
  4068. const dXAbs = Math.abs(touchX - touchStartX);
  4069. const dYAbs = Math.abs(touchY - touchStartY);
  4070. const threshold = 8;
  4071. if (dXAbs > threshold && dYAbs <= threshold) {
  4072. swipeStartX = touchX;
  4073. swiping = that.state === 'opened' ? 'closing' : 'opening';
  4074. $.lockScreen();
  4075. setPosition(getTranslateX(touchX));
  4076. }
  4077. else if (dXAbs <= threshold && dYAbs > threshold) {
  4078. onBodyTouchEnd();
  4079. }
  4080. }
  4081. }
  4082. function onBodyTouchStart(event) {
  4083. touchStartX = event.touches[0].pageX;
  4084. if (that.position === 'right') {
  4085. touchStartX = $body.width() - touchStartX;
  4086. }
  4087. touchStartY = event.touches[0].pageY;
  4088. if (that.state !== 'opened') {
  4089. if (touchStartX > swipeAreaWidth ||
  4090. openNavEventHandler !== onBodyTouchStart) {
  4091. return;
  4092. }
  4093. }
  4094. maybeSwiping = true;
  4095. $body.on({
  4096. touchmove: onBodyTouchMove,
  4097. touchend: onBodyTouchEnd,
  4098. touchcancel: onBodyTouchMove,
  4099. });
  4100. }
  4101. function enableSwipeHandling() {
  4102. if (!openNavEventHandler) {
  4103. $body.on('touchstart', onBodyTouchStart);
  4104. openNavEventHandler = onBodyTouchStart;
  4105. }
  4106. }
  4107. if (this.options.swipe) {
  4108. enableSwipeHandling();
  4109. }
  4110. }
  4111. /**
  4112. * 触发组件事件
  4113. * @param name
  4114. */
  4115. triggerEvent(name) {
  4116. componentEvent(name, 'drawer', this.$element, this);
  4117. }
  4118. /**
  4119. * 动画结束回调
  4120. */
  4121. transitionEnd() {
  4122. if (this.$element.hasClass('mdui-drawer-open')) {
  4123. this.state = 'opened';
  4124. this.triggerEvent('opened');
  4125. }
  4126. else {
  4127. this.state = 'closed';
  4128. this.triggerEvent('closed');
  4129. }
  4130. }
  4131. /**
  4132. * 是否处于打开状态
  4133. */
  4134. isOpen() {
  4135. return this.state === 'opening' || this.state === 'opened';
  4136. }
  4137. /**
  4138. * 打开抽屉栏
  4139. */
  4140. open() {
  4141. if (this.isOpen()) {
  4142. return;
  4143. }
  4144. this.state = 'opening';
  4145. this.triggerEvent('open');
  4146. if (!this.options.overlay) {
  4147. $('body').addClass(`mdui-drawer-body-${this.position}`);
  4148. }
  4149. this.$element
  4150. .removeClass('mdui-drawer-close')
  4151. .addClass('mdui-drawer-open')
  4152. .transitionEnd(() => this.transitionEnd());
  4153. if (!this.isDesktop() || this.options.overlay) {
  4154. this.overlay = true;
  4155. $.showOverlay().one('click', () => this.close());
  4156. $.lockScreen();
  4157. }
  4158. }
  4159. /**
  4160. * 关闭抽屉栏
  4161. */
  4162. close() {
  4163. if (!this.isOpen()) {
  4164. return;
  4165. }
  4166. this.state = 'closing';
  4167. this.triggerEvent('close');
  4168. if (!this.options.overlay) {
  4169. $('body').removeClass(`mdui-drawer-body-${this.position}`);
  4170. }
  4171. this.$element
  4172. .addClass('mdui-drawer-close')
  4173. .removeClass('mdui-drawer-open')
  4174. .transitionEnd(() => this.transitionEnd());
  4175. if (this.overlay) {
  4176. $.hideOverlay();
  4177. this.overlay = false;
  4178. $.unlockScreen();
  4179. }
  4180. }
  4181. /**
  4182. * 切换抽屉栏打开/关闭状态
  4183. */
  4184. toggle() {
  4185. this.isOpen() ? this.close() : this.open();
  4186. }
  4187. /**
  4188. * 返回当前抽屉栏的状态。共包含四种状态:`opening`、`opened`、`closing`、`closed`
  4189. */
  4190. getState() {
  4191. return this.state;
  4192. }
  4193. }
  4194. mdui.Drawer = Drawer;
  4195. const customAttr$6 = 'mdui-drawer';
  4196. $(() => {
  4197. mdui.mutation(`[${customAttr$6}]`, function () {
  4198. const $element = $(this);
  4199. const options = parseOptions(this, customAttr$6);
  4200. const selector = options.target;
  4201. // @ts-ignore
  4202. delete options.target;
  4203. const $drawer = $(selector).first();
  4204. const instance = new mdui.Drawer($drawer, options);
  4205. $element.on('click', () => instance.toggle());
  4206. });
  4207. });
  4208. const container = {};
  4209. function queue(name, func) {
  4210. if (isUndefined(container[name])) {
  4211. container[name] = [];
  4212. }
  4213. if (isUndefined(func)) {
  4214. return container[name];
  4215. }
  4216. container[name].push(func);
  4217. }
  4218. /**
  4219. * 从队列中移除第一个函数,并执行该函数
  4220. * @param name 队列满
  4221. */
  4222. function dequeue(name) {
  4223. if (isUndefined(container[name])) {
  4224. return;
  4225. }
  4226. if (!container[name].length) {
  4227. return;
  4228. }
  4229. const func = container[name].shift();
  4230. func();
  4231. }
  4232. const DEFAULT_OPTIONS$6 = {
  4233. history: true,
  4234. overlay: true,
  4235. modal: false,
  4236. closeOnEsc: true,
  4237. closeOnCancel: true,
  4238. closeOnConfirm: true,
  4239. destroyOnClosed: false,
  4240. };
  4241. /**
  4242. * 当前显示的对话框实例
  4243. */
  4244. let currentInst = null;
  4245. /**
  4246. * 队列名
  4247. */
  4248. const queueName = '_mdui_dialog';
  4249. /**
  4250. * 窗口是否已锁定
  4251. */
  4252. let isLockScreen = false;
  4253. /**
  4254. * 遮罩层元素
  4255. */
  4256. let $overlay;
  4257. class Dialog {
  4258. constructor(selector, options = {}) {
  4259. /**
  4260. * 配置参数
  4261. */
  4262. this.options = extend({}, DEFAULT_OPTIONS$6);
  4263. /**
  4264. * 当前 dialog 的状态
  4265. */
  4266. this.state = 'closed';
  4267. /**
  4268. * dialog 元素是否是动态添加的
  4269. */
  4270. this.append = false;
  4271. this.$element = $(selector).first();
  4272. // 如果对话框元素没有在当前文档中,则需要添加
  4273. if (!contains(document.body, this.$element[0])) {
  4274. this.append = true;
  4275. $('body').append(this.$element);
  4276. }
  4277. extend(this.options, options);
  4278. // 绑定取消按钮事件
  4279. this.$element.find('[mdui-dialog-cancel]').each((_, cancel) => {
  4280. $(cancel).on('click', () => {
  4281. this.triggerEvent('cancel');
  4282. if (this.options.closeOnCancel) {
  4283. this.close();
  4284. }
  4285. });
  4286. });
  4287. // 绑定确认按钮事件
  4288. this.$element.find('[mdui-dialog-confirm]').each((_, confirm) => {
  4289. $(confirm).on('click', () => {
  4290. this.triggerEvent('confirm');
  4291. if (this.options.closeOnConfirm) {
  4292. this.close();
  4293. }
  4294. });
  4295. });
  4296. // 绑定关闭按钮事件
  4297. this.$element.find('[mdui-dialog-close]').each((_, close) => {
  4298. $(close).on('click', () => this.close());
  4299. });
  4300. }
  4301. /**
  4302. * 触发组件事件
  4303. * @param name
  4304. */
  4305. triggerEvent(name) {
  4306. componentEvent(name, 'dialog', this.$element, this);
  4307. }
  4308. /**
  4309. * 窗口宽度变化,或对话框内容变化时,调整对话框位置和对话框内的滚动条
  4310. */
  4311. readjust() {
  4312. if (!currentInst) {
  4313. return;
  4314. }
  4315. const $element = currentInst.$element;
  4316. const $title = $element.children('.mdui-dialog-title');
  4317. const $content = $element.children('.mdui-dialog-content');
  4318. const $actions = $element.children('.mdui-dialog-actions');
  4319. // 调整 dialog 的 top 和 height 值
  4320. $element.height('');
  4321. $content.height('');
  4322. const elementHeight = $element.height();
  4323. $element.css({
  4324. top: `${($window.height() - elementHeight) / 2}px`,
  4325. height: `${elementHeight}px`,
  4326. });
  4327. // 调整 mdui-dialog-content 的高度
  4328. $content.innerHeight(elementHeight -
  4329. ($title.innerHeight() || 0) -
  4330. ($actions.innerHeight() || 0));
  4331. }
  4332. /**
  4333. * hashchange 事件触发时关闭对话框
  4334. */
  4335. hashchangeEvent() {
  4336. if (window.location.hash.substring(1).indexOf('mdui-dialog') < 0) {
  4337. currentInst.close(true);
  4338. }
  4339. }
  4340. /**
  4341. * 点击遮罩层关闭对话框
  4342. * @param event
  4343. */
  4344. overlayClick(event) {
  4345. if ($(event.target).hasClass('mdui-overlay') &&
  4346. currentInst) {
  4347. currentInst.close();
  4348. }
  4349. }
  4350. /**
  4351. * 动画结束回调
  4352. */
  4353. transitionEnd() {
  4354. if (this.$element.hasClass('mdui-dialog-open')) {
  4355. this.state = 'opened';
  4356. this.triggerEvent('opened');
  4357. }
  4358. else {
  4359. this.state = 'closed';
  4360. this.triggerEvent('closed');
  4361. this.$element.hide();
  4362. // 所有对话框都关闭,且当前没有打开的对话框时,解锁屏幕
  4363. if (!queue(queueName).length && !currentInst && isLockScreen) {
  4364. $.unlockScreen();
  4365. isLockScreen = false;
  4366. }
  4367. $window.off('resize', $.throttle(this.readjust, 100));
  4368. if (this.options.destroyOnClosed) {
  4369. this.destroy();
  4370. }
  4371. }
  4372. }
  4373. /**
  4374. * 打开指定对话框
  4375. */
  4376. doOpen() {
  4377. currentInst = this;
  4378. if (!isLockScreen) {
  4379. $.lockScreen();
  4380. isLockScreen = true;
  4381. }
  4382. this.$element.show();
  4383. this.readjust();
  4384. $window.on('resize', $.throttle(this.readjust, 100));
  4385. // 打开消息框
  4386. this.state = 'opening';
  4387. this.triggerEvent('open');
  4388. this.$element
  4389. .addClass('mdui-dialog-open')
  4390. .transitionEnd(() => this.transitionEnd());
  4391. // 不存在遮罩层元素时,添加遮罩层
  4392. if (!$overlay) {
  4393. $overlay = $.showOverlay(5100);
  4394. }
  4395. // 点击遮罩层时是否关闭对话框
  4396. if (this.options.modal) {
  4397. $overlay.off('click', this.overlayClick);
  4398. }
  4399. else {
  4400. $overlay.on('click', this.overlayClick);
  4401. }
  4402. // 是否显示遮罩层,不显示时,把遮罩层背景透明
  4403. $overlay.css('opacity', this.options.overlay ? '' : 0);
  4404. if (this.options.history) {
  4405. // 如果 hash 中原来就有 mdui-dialog,先删除,避免后退历史纪录后仍然有 mdui-dialog 导致无法关闭
  4406. // 包括 mdui-dialog 和 &mdui-dialog 和 ?mdui-dialog
  4407. let hash = window.location.hash.substring(1);
  4408. if (hash.indexOf('mdui-dialog') > -1) {
  4409. hash = hash.replace(/[&?]?mdui-dialog/g, '');
  4410. }
  4411. // 后退按钮关闭对话框
  4412. if (hash) {
  4413. window.location.hash = `${hash}${hash.indexOf('?') > -1 ? '&' : '?'}mdui-dialog`;
  4414. }
  4415. else {
  4416. window.location.hash = 'mdui-dialog';
  4417. }
  4418. $window.on('hashchange', this.hashchangeEvent);
  4419. }
  4420. }
  4421. /**
  4422. * 当前对话框是否为打开状态
  4423. */
  4424. isOpen() {
  4425. return this.state === 'opening' || this.state === 'opened';
  4426. }
  4427. /**
  4428. * 打开对话框
  4429. */
  4430. open() {
  4431. if (this.isOpen()) {
  4432. return;
  4433. }
  4434. // 如果当前有正在打开或已经打开的对话框,或队列不为空,则先加入队列,等旧对话框开始关闭时再打开
  4435. if ((currentInst &&
  4436. (currentInst.state === 'opening' || currentInst.state === 'opened')) ||
  4437. queue(queueName).length) {
  4438. queue(queueName, () => this.doOpen());
  4439. return;
  4440. }
  4441. this.doOpen();
  4442. }
  4443. /**
  4444. * 关闭对话框
  4445. */
  4446. close(historyBack = false) {
  4447. // historyBack 是否需要后退历史纪录,默认为 `false`。该参数仅内部使用
  4448. // 为 `false` 时是通过 js 关闭,需要后退一个历史记录
  4449. // 为 `true` 时是通过后退按钮关闭,不需要后退历史记录
  4450. // setTimeout 的作用是:
  4451. // 当同时关闭一个对话框,并打开另一个对话框时,使打开对话框的操作先执行,以使需要打开的对话框先加入队列
  4452. setTimeout(() => {
  4453. if (!this.isOpen()) {
  4454. return;
  4455. }
  4456. currentInst = null;
  4457. this.state = 'closing';
  4458. this.triggerEvent('close');
  4459. // 所有对话框都关闭,且当前没有打开的对话框时,隐藏遮罩
  4460. if (!queue(queueName).length && $overlay) {
  4461. $.hideOverlay();
  4462. $overlay = null;
  4463. // 若仍存在遮罩,恢复遮罩的 z-index
  4464. $('.mdui-overlay').css('z-index', 2000);
  4465. }
  4466. this.$element
  4467. .removeClass('mdui-dialog-open')
  4468. .transitionEnd(() => this.transitionEnd());
  4469. if (this.options.history && !queue(queueName).length) {
  4470. if (!historyBack) {
  4471. window.history.back();
  4472. }
  4473. $window.off('hashchange', this.hashchangeEvent);
  4474. }
  4475. // 关闭旧对话框,打开新对话框。
  4476. // 加一点延迟,仅仅为了视觉效果更好。不加延时也不影响功能
  4477. setTimeout(() => {
  4478. dequeue(queueName);
  4479. }, 100);
  4480. });
  4481. }
  4482. /**
  4483. * 切换对话框打开/关闭状态
  4484. */
  4485. toggle() {
  4486. this.isOpen() ? this.close() : this.open();
  4487. }
  4488. /**
  4489. * 获取对话框状态。共包含四种状态:`opening`、`opened`、`closing`、`closed`
  4490. */
  4491. getState() {
  4492. return this.state;
  4493. }
  4494. /**
  4495. * 销毁对话框
  4496. */
  4497. destroy() {
  4498. if (this.append) {
  4499. this.$element.remove();
  4500. }
  4501. if (!queue(queueName).length && !currentInst) {
  4502. if ($overlay) {
  4503. $.hideOverlay();
  4504. $overlay = null;
  4505. }
  4506. if (isLockScreen) {
  4507. $.unlockScreen();
  4508. isLockScreen = false;
  4509. }
  4510. }
  4511. }
  4512. /**
  4513. * 对话框内容变化时,需要调用该方法来调整对话框位置和滚动条高度
  4514. */
  4515. handleUpdate() {
  4516. this.readjust();
  4517. }
  4518. }
  4519. // esc 按下时关闭对话框
  4520. $document.on('keydown', (event) => {
  4521. if (currentInst &&
  4522. currentInst.options.closeOnEsc &&
  4523. currentInst.state === 'opened' &&
  4524. event.keyCode === 27) {
  4525. currentInst.close();
  4526. }
  4527. });
  4528. mdui.Dialog = Dialog;
  4529. const customAttr$7 = 'mdui-dialog';
  4530. const dataName$1 = '_mdui_dialog';
  4531. $(() => {
  4532. $document.on('click', `[${customAttr$7}]`, function () {
  4533. const options = parseOptions(this, customAttr$7);
  4534. const selector = options.target;
  4535. // @ts-ignore
  4536. delete options.target;
  4537. const $dialog = $(selector).first();
  4538. let instance = $dialog.data(dataName$1);
  4539. if (!instance) {
  4540. instance = new mdui.Dialog($dialog, options);
  4541. $dialog.data(dataName$1, instance);
  4542. }
  4543. instance.open();
  4544. });
  4545. });
  4546. const DEFAULT_BUTTON = {
  4547. text: '',
  4548. bold: false,
  4549. close: true,
  4550. // eslint-disable-next-line @typescript-eslint/no-empty-function
  4551. onClick: () => { },
  4552. };
  4553. const DEFAULT_OPTIONS$7 = {
  4554. title: '',
  4555. content: '',
  4556. buttons: [],
  4557. stackedButtons: false,
  4558. cssClass: '',
  4559. history: true,
  4560. overlay: true,
  4561. modal: false,
  4562. closeOnEsc: true,
  4563. destroyOnClosed: true,
  4564. // eslint-disable-next-line @typescript-eslint/no-empty-function
  4565. onOpen: () => { },
  4566. // eslint-disable-next-line @typescript-eslint/no-empty-function
  4567. onOpened: () => { },
  4568. // eslint-disable-next-line @typescript-eslint/no-empty-function
  4569. onClose: () => { },
  4570. // eslint-disable-next-line @typescript-eslint/no-empty-function
  4571. onClosed: () => { },
  4572. };
  4573. mdui.dialog = function (options) {
  4574. var _a, _b;
  4575. // 合并配置参数
  4576. options = extend({}, DEFAULT_OPTIONS$7, options);
  4577. each(options.buttons, (i, button) => {
  4578. options.buttons[i] = extend({}, DEFAULT_BUTTON, button);
  4579. });
  4580. // 按钮的 HTML
  4581. let buttonsHTML = '';
  4582. if ((_a = options.buttons) === null || _a === void 0 ? void 0 : _a.length) {
  4583. buttonsHTML = `<div class="mdui-dialog-actions${options.stackedButtons ? ' mdui-dialog-actions-stacked' : ''}">`;
  4584. each(options.buttons, (_, button) => {
  4585. buttonsHTML +=
  4586. '<a href="javascript:void(0)" ' +
  4587. `class="mdui-btn mdui-ripple mdui-text-color-primary ${button.bold ? 'mdui-btn-bold' : ''}">${button.text}</a>`;
  4588. });
  4589. buttonsHTML += '</div>';
  4590. }
  4591. // Dialog 的 HTML
  4592. const HTML = `<div class="mdui-dialog ${options.cssClass}">` +
  4593. (options.title
  4594. ? `<div class="mdui-dialog-title">${options.title}</div>`
  4595. : '') +
  4596. (options.content
  4597. ? `<div class="mdui-dialog-content">${options.content}</div>`
  4598. : '') +
  4599. buttonsHTML +
  4600. '</div>';
  4601. // 实例化 Dialog
  4602. const instance = new mdui.Dialog(HTML, {
  4603. history: options.history,
  4604. overlay: options.overlay,
  4605. modal: options.modal,
  4606. closeOnEsc: options.closeOnEsc,
  4607. destroyOnClosed: options.destroyOnClosed,
  4608. });
  4609. // 绑定按钮事件
  4610. if ((_b = options.buttons) === null || _b === void 0 ? void 0 : _b.length) {
  4611. instance.$element
  4612. .find('.mdui-dialog-actions .mdui-btn')
  4613. .each((index, button) => {
  4614. $(button).on('click', () => {
  4615. options.buttons[index].onClick(instance);
  4616. if (options.buttons[index].close) {
  4617. instance.close();
  4618. }
  4619. });
  4620. });
  4621. }
  4622. // 绑定打开关闭事件
  4623. instance.$element
  4624. .on('open.mdui.dialog', () => {
  4625. options.onOpen(instance);
  4626. })
  4627. .on('opened.mdui.dialog', () => {
  4628. options.onOpened(instance);
  4629. })
  4630. .on('close.mdui.dialog', () => {
  4631. options.onClose(instance);
  4632. })
  4633. .on('closed.mdui.dialog', () => {
  4634. options.onClosed(instance);
  4635. });
  4636. instance.open();
  4637. return instance;
  4638. };
  4639. const DEFAULT_OPTIONS$8 = {
  4640. confirmText: 'ok',
  4641. history: true,
  4642. modal: false,
  4643. closeOnEsc: true,
  4644. closeOnConfirm: true,
  4645. };
  4646. mdui.alert = function (text, title, onConfirm, options) {
  4647. if (isFunction(title)) {
  4648. options = onConfirm;
  4649. onConfirm = title;
  4650. title = '';
  4651. }
  4652. if (isUndefined(onConfirm)) {
  4653. // eslint-disable-next-line @typescript-eslint/no-empty-function
  4654. onConfirm = () => { };
  4655. }
  4656. if (isUndefined(options)) {
  4657. options = {};
  4658. }
  4659. options = extend({}, DEFAULT_OPTIONS$8, options);
  4660. return mdui.dialog({
  4661. title: title,
  4662. content: text,
  4663. buttons: [
  4664. {
  4665. text: options.confirmText,
  4666. bold: false,
  4667. close: options.closeOnConfirm,
  4668. onClick: onConfirm,
  4669. },
  4670. ],
  4671. cssClass: 'mdui-dialog-alert',
  4672. history: options.history,
  4673. modal: options.modal,
  4674. closeOnEsc: options.closeOnEsc,
  4675. });
  4676. };
  4677. const DEFAULT_OPTIONS$9 = {
  4678. confirmText: 'ok',
  4679. cancelText: 'cancel',
  4680. history: true,
  4681. modal: false,
  4682. closeOnEsc: true,
  4683. closeOnCancel: true,
  4684. closeOnConfirm: true,
  4685. };
  4686. mdui.confirm = function (text, title, onConfirm, onCancel, options) {
  4687. if (isFunction(title)) {
  4688. options = onCancel;
  4689. onCancel = onConfirm;
  4690. onConfirm = title;
  4691. title = '';
  4692. }
  4693. if (isUndefined(onConfirm)) {
  4694. // eslint-disable-next-line @typescript-eslint/no-empty-function
  4695. onConfirm = () => { };
  4696. }
  4697. if (isUndefined(onCancel)) {
  4698. // eslint-disable-next-line @typescript-eslint/no-empty-function
  4699. onCancel = () => { };
  4700. }
  4701. if (isUndefined(options)) {
  4702. options = {};
  4703. }
  4704. options = extend({}, DEFAULT_OPTIONS$9, options);
  4705. return mdui.dialog({
  4706. title: title,
  4707. content: text,
  4708. buttons: [
  4709. {
  4710. text: options.cancelText,
  4711. bold: false,
  4712. close: options.closeOnCancel,
  4713. onClick: onCancel,
  4714. },
  4715. {
  4716. text: options.confirmText,
  4717. bold: false,
  4718. close: options.closeOnConfirm,
  4719. onClick: onConfirm,
  4720. },
  4721. ],
  4722. cssClass: 'mdui-dialog-confirm',
  4723. history: options.history,
  4724. modal: options.modal,
  4725. closeOnEsc: options.closeOnEsc,
  4726. });
  4727. };
  4728. const DEFAULT_OPTIONS$a = {
  4729. confirmText: 'ok',
  4730. cancelText: 'cancel',
  4731. history: true,
  4732. modal: false,
  4733. closeOnEsc: true,
  4734. closeOnCancel: true,
  4735. closeOnConfirm: true,
  4736. type: 'text',
  4737. maxlength: 0,
  4738. defaultValue: '',
  4739. confirmOnEnter: false,
  4740. };
  4741. mdui.prompt = function (label, title, onConfirm, onCancel, options) {
  4742. if (isFunction(title)) {
  4743. options = onCancel;
  4744. onCancel = onConfirm;
  4745. onConfirm = title;
  4746. title = '';
  4747. }
  4748. if (isUndefined(onConfirm)) {
  4749. // eslint-disable-next-line @typescript-eslint/no-empty-function
  4750. onConfirm = () => { };
  4751. }
  4752. if (isUndefined(onCancel)) {
  4753. // eslint-disable-next-line @typescript-eslint/no-empty-function
  4754. onCancel = () => { };
  4755. }
  4756. if (isUndefined(options)) {
  4757. options = {};
  4758. }
  4759. options = extend({}, DEFAULT_OPTIONS$a, options);
  4760. const content = '<div class="mdui-textfield">' +
  4761. (label ? `<label class="mdui-textfield-label">${label}</label>` : '') +
  4762. (options.type === 'text'
  4763. ? `<input class="mdui-textfield-input" type="text" value="${options.defaultValue}" ${options.maxlength ? 'maxlength="' + options.maxlength + '"' : ''}/>`
  4764. : '') +
  4765. (options.type === 'textarea'
  4766. ? `<textarea class="mdui-textfield-input" ${options.maxlength ? 'maxlength="' + options.maxlength + '"' : ''}>${options.defaultValue}</textarea>`
  4767. : '') +
  4768. '</div>';
  4769. const onCancelClick = (dialog) => {
  4770. const value = dialog.$element.find('.mdui-textfield-input').val();
  4771. onCancel(value, dialog);
  4772. };
  4773. const onConfirmClick = (dialog) => {
  4774. const value = dialog.$element.find('.mdui-textfield-input').val();
  4775. onConfirm(value, dialog);
  4776. };
  4777. return mdui.dialog({
  4778. title,
  4779. content,
  4780. buttons: [
  4781. {
  4782. text: options.cancelText,
  4783. bold: false,
  4784. close: options.closeOnCancel,
  4785. onClick: onCancelClick,
  4786. },
  4787. {
  4788. text: options.confirmText,
  4789. bold: false,
  4790. close: options.closeOnConfirm,
  4791. onClick: onConfirmClick,
  4792. },
  4793. ],
  4794. cssClass: 'mdui-dialog-prompt',
  4795. history: options.history,
  4796. modal: options.modal,
  4797. closeOnEsc: options.closeOnEsc,
  4798. onOpen: (dialog) => {
  4799. // 初始化输入框
  4800. const $input = dialog.$element.find('.mdui-textfield-input');
  4801. mdui.updateTextFields($input);
  4802. // 聚焦到输入框
  4803. $input[0].focus();
  4804. // 捕捉文本框回车键,在单行文本框的情况下触发回调
  4805. if (options.type !== 'textarea' && options.confirmOnEnter === true) {
  4806. $input.on('keydown', (event) => {
  4807. if (event.keyCode === 13) {
  4808. const value = dialog.$element.find('.mdui-textfield-input').val();
  4809. onConfirm(value, dialog);
  4810. if (options.closeOnConfirm) {
  4811. dialog.close();
  4812. }
  4813. return false;
  4814. }
  4815. return;
  4816. });
  4817. }
  4818. // 如果是多行输入框,监听输入框的 input 事件,更新对话框高度
  4819. if (options.type === 'textarea') {
  4820. $input.on('input', () => dialog.handleUpdate());
  4821. }
  4822. // 有字符数限制时,加载完文本框后 DOM 会变化,需要更新对话框高度
  4823. if (options.maxlength) {
  4824. dialog.handleUpdate();
  4825. }
  4826. },
  4827. });
  4828. };
  4829. const DEFAULT_OPTIONS$b = {
  4830. position: 'auto',
  4831. delay: 0,
  4832. content: '',
  4833. };
  4834. class Tooltip {
  4835. constructor(selector, options = {}) {
  4836. /**
  4837. * 配置参数
  4838. */
  4839. this.options = extend({}, DEFAULT_OPTIONS$b);
  4840. /**
  4841. * 当前 tooltip 的状态
  4842. */
  4843. this.state = 'closed';
  4844. /**
  4845. * setTimeout 的返回值
  4846. */
  4847. this.timeoutId = null;
  4848. this.$target = $(selector).first();
  4849. extend(this.options, options);
  4850. // 创建 Tooltip HTML
  4851. this.$element = $(`<div class="mdui-tooltip" id="${$.guid()}">${this.options.content}</div>`).appendTo(document.body);
  4852. // 绑定事件。元素处于 disabled 状态时无法触发鼠标事件,为了统一,把 touch 事件也禁用
  4853. // eslint-disable-next-line @typescript-eslint/no-this-alias
  4854. const that = this;
  4855. this.$target
  4856. .on('touchstart mouseenter', function (event) {
  4857. if (that.isDisabled(this)) {
  4858. return;
  4859. }
  4860. if (!isAllow(event)) {
  4861. return;
  4862. }
  4863. register(event);
  4864. that.open();
  4865. })
  4866. .on('touchend mouseleave', function (event) {
  4867. if (that.isDisabled(this)) {
  4868. return;
  4869. }
  4870. if (!isAllow(event)) {
  4871. return;
  4872. }
  4873. that.close();
  4874. })
  4875. .on(unlockEvent, function (event) {
  4876. if (that.isDisabled(this)) {
  4877. return;
  4878. }
  4879. register(event);
  4880. });
  4881. }
  4882. /**
  4883. * 元素是否已禁用
  4884. * @param element
  4885. */
  4886. isDisabled(element) {
  4887. return (element.disabled ||
  4888. $(element).attr('disabled') !== undefined);
  4889. }
  4890. /**
  4891. * 是否是桌面设备
  4892. */
  4893. isDesktop() {
  4894. return $window.width() > 1024;
  4895. }
  4896. /**
  4897. * 设置 Tooltip 的位置
  4898. */
  4899. setPosition() {
  4900. let marginLeft;
  4901. let marginTop;
  4902. // 触发的元素
  4903. const targetProps = this.$target[0].getBoundingClientRect();
  4904. // 触发的元素和 Tooltip 之间的距离
  4905. const targetMargin = this.isDesktop() ? 14 : 24;
  4906. // Tooltip 的宽度和高度
  4907. const tooltipWidth = this.$element[0].offsetWidth;
  4908. const tooltipHeight = this.$element[0].offsetHeight;
  4909. // Tooltip 的方向
  4910. let position = this.options.position;
  4911. // 自动判断位置,加 2px,使 Tooltip 距离窗口边框至少有 2px 的间距
  4912. if (position === 'auto') {
  4913. if (targetProps.top +
  4914. targetProps.height +
  4915. targetMargin +
  4916. tooltipHeight +
  4917. 2 <
  4918. $window.height()) {
  4919. position = 'bottom';
  4920. }
  4921. else if (targetMargin + tooltipHeight + 2 < targetProps.top) {
  4922. position = 'top';
  4923. }
  4924. else if (targetMargin + tooltipWidth + 2 < targetProps.left) {
  4925. position = 'left';
  4926. }
  4927. else if (targetProps.width + targetMargin + tooltipWidth + 2 <
  4928. $window.width() - targetProps.left) {
  4929. position = 'right';
  4930. }
  4931. else {
  4932. position = 'bottom';
  4933. }
  4934. }
  4935. // 设置位置
  4936. switch (position) {
  4937. case 'bottom':
  4938. marginLeft = -1 * (tooltipWidth / 2);
  4939. marginTop = targetProps.height / 2 + targetMargin;
  4940. this.$element.transformOrigin('top center');
  4941. break;
  4942. case 'top':
  4943. marginLeft = -1 * (tooltipWidth / 2);
  4944. marginTop =
  4945. -1 * (tooltipHeight + targetProps.height / 2 + targetMargin);
  4946. this.$element.transformOrigin('bottom center');
  4947. break;
  4948. case 'left':
  4949. marginLeft = -1 * (tooltipWidth + targetProps.width / 2 + targetMargin);
  4950. marginTop = -1 * (tooltipHeight / 2);
  4951. this.$element.transformOrigin('center right');
  4952. break;
  4953. case 'right':
  4954. marginLeft = targetProps.width / 2 + targetMargin;
  4955. marginTop = -1 * (tooltipHeight / 2);
  4956. this.$element.transformOrigin('center left');
  4957. break;
  4958. }
  4959. const targetOffset = this.$target.offset();
  4960. this.$element.css({
  4961. top: `${targetOffset.top + targetProps.height / 2}px`,
  4962. left: `${targetOffset.left + targetProps.width / 2}px`,
  4963. 'margin-left': `${marginLeft}px`,
  4964. 'margin-top': `${marginTop}px`,
  4965. });
  4966. }
  4967. /**
  4968. * 触发组件事件
  4969. * @param name
  4970. */
  4971. triggerEvent(name) {
  4972. componentEvent(name, 'tooltip', this.$target, this);
  4973. }
  4974. /**
  4975. * 动画结束回调
  4976. */
  4977. transitionEnd() {
  4978. if (this.$element.hasClass('mdui-tooltip-open')) {
  4979. this.state = 'opened';
  4980. this.triggerEvent('opened');
  4981. }
  4982. else {
  4983. this.state = 'closed';
  4984. this.triggerEvent('closed');
  4985. }
  4986. }
  4987. /**
  4988. * 当前 tooltip 是否为打开状态
  4989. */
  4990. isOpen() {
  4991. return this.state === 'opening' || this.state === 'opened';
  4992. }
  4993. /**
  4994. * 执行打开 tooltip
  4995. */
  4996. doOpen() {
  4997. this.state = 'opening';
  4998. this.triggerEvent('open');
  4999. this.$element
  5000. .addClass('mdui-tooltip-open')
  5001. .transitionEnd(() => this.transitionEnd());
  5002. }
  5003. /**
  5004. * 打开 Tooltip
  5005. * @param options 允许每次打开时设置不同的参数
  5006. */
  5007. open(options) {
  5008. if (this.isOpen()) {
  5009. return;
  5010. }
  5011. const oldOptions = extend({}, this.options);
  5012. if (options) {
  5013. extend(this.options, options);
  5014. }
  5015. // tooltip 的内容有更新
  5016. if (oldOptions.content !== this.options.content) {
  5017. this.$element.html(this.options.content);
  5018. }
  5019. this.setPosition();
  5020. if (this.options.delay) {
  5021. this.timeoutId = setTimeout(() => this.doOpen(), this.options.delay);
  5022. }
  5023. else {
  5024. this.timeoutId = null;
  5025. this.doOpen();
  5026. }
  5027. }
  5028. /**
  5029. * 关闭 Tooltip
  5030. */
  5031. close() {
  5032. if (this.timeoutId) {
  5033. clearTimeout(this.timeoutId);
  5034. this.timeoutId = null;
  5035. }
  5036. if (!this.isOpen()) {
  5037. return;
  5038. }
  5039. this.state = 'closing';
  5040. this.triggerEvent('close');
  5041. this.$element
  5042. .removeClass('mdui-tooltip-open')
  5043. .transitionEnd(() => this.transitionEnd());
  5044. }
  5045. /**
  5046. * 切换 Tooltip 的打开状态
  5047. */
  5048. toggle() {
  5049. this.isOpen() ? this.close() : this.open();
  5050. }
  5051. /**
  5052. * 获取 Tooltip 状态。共包含四种状态:`opening`、`opened`、`closing`、`closed`
  5053. */
  5054. getState() {
  5055. return this.state;
  5056. }
  5057. }
  5058. mdui.Tooltip = Tooltip;
  5059. const customAttr$8 = 'mdui-tooltip';
  5060. const dataName$2 = '_mdui_tooltip';
  5061. $(() => {
  5062. // mouseenter 不能冒泡,所以这里用 mouseover 代替
  5063. $document.on('touchstart mouseover', `[${customAttr$8}]`, function () {
  5064. const $target = $(this);
  5065. let instance = $target.data(dataName$2);
  5066. if (!instance) {
  5067. instance = new mdui.Tooltip(this, parseOptions(this, customAttr$8));
  5068. $target.data(dataName$2, instance);
  5069. }
  5070. });
  5071. });
  5072. const DEFAULT_OPTIONS$c = {
  5073. message: '',
  5074. timeout: 4000,
  5075. position: 'bottom',
  5076. buttonText: '',
  5077. buttonColor: '',
  5078. closeOnButtonClick: true,
  5079. closeOnOutsideClick: true,
  5080. // eslint-disable-next-line @typescript-eslint/no-empty-function
  5081. onClick: () => { },
  5082. // eslint-disable-next-line @typescript-eslint/no-empty-function
  5083. onButtonClick: () => { },
  5084. // eslint-disable-next-line @typescript-eslint/no-empty-function
  5085. onOpen: () => { },
  5086. // eslint-disable-next-line @typescript-eslint/no-empty-function
  5087. onOpened: () => { },
  5088. // eslint-disable-next-line @typescript-eslint/no-empty-function
  5089. onClose: () => { },
  5090. // eslint-disable-next-line @typescript-eslint/no-empty-function
  5091. onClosed: () => { },
  5092. };
  5093. /**
  5094. * 当前打开着的 Snackbar
  5095. */
  5096. let currentInst$1 = null;
  5097. /**
  5098. * 队列名
  5099. */
  5100. const queueName$1 = '_mdui_snackbar';
  5101. class Snackbar {
  5102. constructor(options) {
  5103. /**
  5104. * 配置参数
  5105. */
  5106. this.options = extend({}, DEFAULT_OPTIONS$c);
  5107. /**
  5108. * 当前 Snackbar 的状态
  5109. */
  5110. this.state = 'closed';
  5111. /**
  5112. * setTimeout 的 ID
  5113. */
  5114. this.timeoutId = null;
  5115. extend(this.options, options);
  5116. // 按钮颜色
  5117. let buttonColorStyle = '';
  5118. let buttonColorClass = '';
  5119. if (this.options.buttonColor.indexOf('#') === 0 ||
  5120. this.options.buttonColor.indexOf('rgb') === 0) {
  5121. buttonColorStyle = `style="color:${this.options.buttonColor}"`;
  5122. }
  5123. else if (this.options.buttonColor !== '') {
  5124. buttonColorClass = `mdui-text-color-${this.options.buttonColor}`;
  5125. }
  5126. // 添加 HTML
  5127. this.$element = $('<div class="mdui-snackbar">' +
  5128. `<div class="mdui-snackbar-text">${this.options.message}</div>` +
  5129. (this.options.buttonText
  5130. ? `<a href="javascript:void(0)" class="mdui-snackbar-action mdui-btn mdui-ripple mdui-ripple-white ${buttonColorClass}" ${buttonColorStyle}>${this.options.buttonText}</a>`
  5131. : '') +
  5132. '</div>').appendTo(document.body);
  5133. // 设置位置
  5134. this.setPosition('close');
  5135. this.$element.reflow().addClass(`mdui-snackbar-${this.options.position}`);
  5136. }
  5137. /**
  5138. * 点击 Snackbar 外面的区域关闭
  5139. * @param event
  5140. */
  5141. closeOnOutsideClick(event) {
  5142. const $target = $(event.target);
  5143. if (!$target.hasClass('mdui-snackbar') &&
  5144. !$target.parents('.mdui-snackbar').length) {
  5145. currentInst$1.close();
  5146. }
  5147. }
  5148. /**
  5149. * 设置 Snackbar 的位置
  5150. * @param state
  5151. */
  5152. setPosition(state) {
  5153. const snackbarHeight = this.$element[0].clientHeight;
  5154. const position = this.options.position;
  5155. let translateX;
  5156. let translateY;
  5157. // translateX
  5158. if (position === 'bottom' || position === 'top') {
  5159. translateX = '-50%';
  5160. }
  5161. else {
  5162. translateX = '0';
  5163. }
  5164. // translateY
  5165. if (state === 'open') {
  5166. translateY = '0';
  5167. }
  5168. else {
  5169. if (position === 'bottom') {
  5170. translateY = snackbarHeight;
  5171. }
  5172. if (position === 'top') {
  5173. translateY = -snackbarHeight;
  5174. }
  5175. if (position === 'left-top' || position === 'right-top') {
  5176. translateY = -snackbarHeight - 24;
  5177. }
  5178. if (position === 'left-bottom' || position === 'right-bottom') {
  5179. translateY = snackbarHeight + 24;
  5180. }
  5181. }
  5182. this.$element.transform(`translate(${translateX},${translateY}px`);
  5183. }
  5184. /**
  5185. * 打开 Snackbar
  5186. */
  5187. open() {
  5188. if (this.state === 'opening' || this.state === 'opened') {
  5189. return;
  5190. }
  5191. // 如果当前有正在显示的 Snackbar,则先加入队列,等旧 Snackbar 关闭后再打开
  5192. if (currentInst$1) {
  5193. queue(queueName$1, () => this.open());
  5194. return;
  5195. }
  5196. currentInst$1 = this;
  5197. // 开始打开
  5198. this.state = 'opening';
  5199. this.options.onOpen(this);
  5200. this.setPosition('open');
  5201. this.$element.transitionEnd(() => {
  5202. if (this.state !== 'opening') {
  5203. return;
  5204. }
  5205. this.state = 'opened';
  5206. this.options.onOpened(this);
  5207. // 有按钮时绑定事件
  5208. if (this.options.buttonText) {
  5209. this.$element.find('.mdui-snackbar-action').on('click', () => {
  5210. this.options.onButtonClick(this);
  5211. if (this.options.closeOnButtonClick) {
  5212. this.close();
  5213. }
  5214. });
  5215. }
  5216. // 点击 snackbar 的事件
  5217. this.$element.on('click', (event) => {
  5218. if (!$(event.target).hasClass('mdui-snackbar-action')) {
  5219. this.options.onClick(this);
  5220. }
  5221. });
  5222. // 点击 Snackbar 外面的区域关闭
  5223. if (this.options.closeOnOutsideClick) {
  5224. $document.on(startEvent, this.closeOnOutsideClick);
  5225. }
  5226. // 超时后自动关闭
  5227. if (this.options.timeout) {
  5228. this.timeoutId = setTimeout(() => this.close(), this.options.timeout);
  5229. }
  5230. });
  5231. }
  5232. /**
  5233. * 关闭 Snackbar
  5234. */
  5235. close() {
  5236. if (this.state === 'closing' || this.state === 'closed') {
  5237. return;
  5238. }
  5239. if (this.timeoutId) {
  5240. clearTimeout(this.timeoutId);
  5241. }
  5242. if (this.options.closeOnOutsideClick) {
  5243. $document.off(startEvent, this.closeOnOutsideClick);
  5244. }
  5245. this.state = 'closing';
  5246. this.options.onClose(this);
  5247. this.setPosition('close');
  5248. this.$element.transitionEnd(() => {
  5249. if (this.state !== 'closing') {
  5250. return;
  5251. }
  5252. currentInst$1 = null;
  5253. this.state = 'closed';
  5254. this.options.onClosed(this);
  5255. this.$element.remove();
  5256. dequeue(queueName$1);
  5257. });
  5258. }
  5259. }
  5260. mdui.snackbar = function (message, options = {}) {
  5261. if (isString(message)) {
  5262. options.message = message;
  5263. }
  5264. else {
  5265. options = message;
  5266. }
  5267. const instance = new Snackbar(options);
  5268. instance.open();
  5269. return instance;
  5270. };
  5271. $(() => {
  5272. // 切换导航项
  5273. $document.on('click', '.mdui-bottom-nav>a', function () {
  5274. const $item = $(this);
  5275. const $bottomNav = $item.parent();
  5276. $bottomNav.children('a').each((index, item) => {
  5277. const isThis = $item.is(item);
  5278. if (isThis) {
  5279. componentEvent('change', 'bottomNav', $bottomNav[0], undefined, {
  5280. index,
  5281. });
  5282. }
  5283. isThis
  5284. ? $(item).addClass('mdui-bottom-nav-active')
  5285. : $(item).removeClass('mdui-bottom-nav-active');
  5286. });
  5287. });
  5288. // 滚动时隐藏 mdui-bottom-nav-scroll-hide
  5289. mdui.mutation('.mdui-bottom-nav-scroll-hide', function () {
  5290. new mdui.Headroom(this, {
  5291. pinnedClass: 'mdui-headroom-pinned-down',
  5292. unpinnedClass: 'mdui-headroom-unpinned-down',
  5293. });
  5294. });
  5295. });
  5296. /**
  5297. * layer 的 HTML 结构
  5298. * @param index
  5299. */
  5300. function layerHTML(index = false) {
  5301. return (`<div class="mdui-spinner-layer ${index ? `mdui-spinner-layer-${index}` : ''}">` +
  5302. '<div class="mdui-spinner-circle-clipper mdui-spinner-left">' +
  5303. '<div class="mdui-spinner-circle"></div>' +
  5304. '</div>' +
  5305. '<div class="mdui-spinner-gap-patch">' +
  5306. '<div class="mdui-spinner-circle"></div>' +
  5307. '</div>' +
  5308. '<div class="mdui-spinner-circle-clipper mdui-spinner-right">' +
  5309. '<div class="mdui-spinner-circle"></div>' +
  5310. '</div>' +
  5311. '</div>');
  5312. }
  5313. /**
  5314. * 填充 HTML
  5315. * @param spinner
  5316. */
  5317. function fillHTML(spinner) {
  5318. const $spinner = $(spinner);
  5319. const layer = $spinner.hasClass('mdui-spinner-colorful')
  5320. ? layerHTML(1) + layerHTML(2) + layerHTML(3) + layerHTML(4)
  5321. : layerHTML();
  5322. $spinner.html(layer);
  5323. }
  5324. $(() => {
  5325. // 页面加载完后自动填充 HTML 结构
  5326. mdui.mutation('.mdui-spinner', function () {
  5327. fillHTML(this);
  5328. });
  5329. });
  5330. mdui.updateSpinners = function (selector) {
  5331. const $elements = isUndefined(selector) ? $('.mdui-spinner') : $(selector);
  5332. $elements.each(function () {
  5333. fillHTML(this);
  5334. });
  5335. };
  5336. const DEFAULT_OPTIONS$d = {
  5337. position: 'auto',
  5338. align: 'auto',
  5339. gutter: 16,
  5340. fixed: false,
  5341. covered: 'auto',
  5342. subMenuTrigger: 'hover',
  5343. subMenuDelay: 200,
  5344. };
  5345. class Menu {
  5346. constructor(anchorSelector, menuSelector, options = {}) {
  5347. /**
  5348. * 配置参数
  5349. */
  5350. this.options = extend({}, DEFAULT_OPTIONS$d);
  5351. /**
  5352. * 当前菜单状态
  5353. */
  5354. this.state = 'closed';
  5355. this.$anchor = $(anchorSelector).first();
  5356. this.$element = $(menuSelector).first();
  5357. // 触发菜单的元素 和 菜单必须是同级的元素,否则菜单可能不能定位
  5358. if (!this.$anchor.parent().is(this.$element.parent())) {
  5359. throw new Error('anchorSelector and menuSelector must be siblings');
  5360. }
  5361. extend(this.options, options);
  5362. // 是否是级联菜单
  5363. this.isCascade = this.$element.hasClass('mdui-menu-cascade');
  5364. // covered 参数处理
  5365. this.isCovered =
  5366. this.options.covered === 'auto' ? !this.isCascade : this.options.covered;
  5367. // 点击触发菜单切换
  5368. this.$anchor.on('click', () => this.toggle());
  5369. // 点击菜单外面区域关闭菜单
  5370. $document.on('click touchstart', (event) => {
  5371. const $target = $(event.target);
  5372. if (this.isOpen() &&
  5373. !$target.is(this.$element) &&
  5374. !contains(this.$element[0], $target[0]) &&
  5375. !$target.is(this.$anchor) &&
  5376. !contains(this.$anchor[0], $target[0])) {
  5377. this.close();
  5378. }
  5379. });
  5380. // 点击不含子菜单的菜单条目关闭菜单
  5381. // eslint-disable-next-line @typescript-eslint/no-this-alias
  5382. const that = this;
  5383. $document.on('click', '.mdui-menu-item', function () {
  5384. const $item = $(this);
  5385. if (!$item.find('.mdui-menu').length &&
  5386. $item.attr('disabled') === undefined) {
  5387. that.close();
  5388. }
  5389. });
  5390. // 绑定点击或鼠标移入含子菜单的条目的事件
  5391. this.bindSubMenuEvent();
  5392. // 窗口大小变化时,重新调整菜单位置
  5393. $window.on('resize', $.throttle(() => this.readjust(), 100));
  5394. }
  5395. /**
  5396. * 是否为打开状态
  5397. */
  5398. isOpen() {
  5399. return this.state === 'opening' || this.state === 'opened';
  5400. }
  5401. /**
  5402. * 触发组件事件
  5403. * @param name
  5404. */
  5405. triggerEvent(name) {
  5406. componentEvent(name, 'menu', this.$element, this);
  5407. }
  5408. /**
  5409. * 调整主菜单位置
  5410. */
  5411. readjust() {
  5412. let menuLeft;
  5413. let menuTop;
  5414. // 菜单位置和方向
  5415. let position;
  5416. let align;
  5417. // window 窗口的宽度和高度
  5418. const windowHeight = $window.height();
  5419. const windowWidth = $window.width();
  5420. // 配置参数
  5421. const gutter = this.options.gutter;
  5422. const isCovered = this.isCovered;
  5423. const isFixed = this.options.fixed;
  5424. // 动画方向参数
  5425. let transformOriginX;
  5426. let transformOriginY;
  5427. // 菜单的原始宽度和高度
  5428. const menuWidth = this.$element.width();
  5429. const menuHeight = this.$element.height();
  5430. // 触发菜单的元素在窗口中的位置
  5431. const anchorRect = this.$anchor[0].getBoundingClientRect();
  5432. const anchorTop = anchorRect.top;
  5433. const anchorLeft = anchorRect.left;
  5434. const anchorHeight = anchorRect.height;
  5435. const anchorWidth = anchorRect.width;
  5436. const anchorBottom = windowHeight - anchorTop - anchorHeight;
  5437. const anchorRight = windowWidth - anchorLeft - anchorWidth;
  5438. // 触发元素相对其拥有定位属性的父元素的位置
  5439. const anchorOffsetTop = this.$anchor[0].offsetTop;
  5440. const anchorOffsetLeft = this.$anchor[0].offsetLeft;
  5441. // 自动判断菜单位置
  5442. if (this.options.position === 'auto') {
  5443. if (anchorBottom + (isCovered ? anchorHeight : 0) > menuHeight + gutter) {
  5444. // 判断下方是否放得下菜单
  5445. position = 'bottom';
  5446. }
  5447. else if (anchorTop + (isCovered ? anchorHeight : 0) >
  5448. menuHeight + gutter) {
  5449. // 判断上方是否放得下菜单
  5450. position = 'top';
  5451. }
  5452. else {
  5453. // 上下都放不下,居中显示
  5454. position = 'center';
  5455. }
  5456. }
  5457. else {
  5458. position = this.options.position;
  5459. }
  5460. // 自动判断菜单对齐方式
  5461. if (this.options.align === 'auto') {
  5462. if (anchorRight + anchorWidth > menuWidth + gutter) {
  5463. // 判断右侧是否放得下菜单
  5464. align = 'left';
  5465. }
  5466. else if (anchorLeft + anchorWidth > menuWidth + gutter) {
  5467. // 判断左侧是否放得下菜单
  5468. align = 'right';
  5469. }
  5470. else {
  5471. // 左右都放不下,居中显示
  5472. align = 'center';
  5473. }
  5474. }
  5475. else {
  5476. align = this.options.align;
  5477. }
  5478. // 设置菜单位置
  5479. if (position === 'bottom') {
  5480. transformOriginY = '0';
  5481. menuTop =
  5482. (isCovered ? 0 : anchorHeight) +
  5483. (isFixed ? anchorTop : anchorOffsetTop);
  5484. }
  5485. else if (position === 'top') {
  5486. transformOriginY = '100%';
  5487. menuTop =
  5488. (isCovered ? anchorHeight : 0) +
  5489. (isFixed ? anchorTop - menuHeight : anchorOffsetTop - menuHeight);
  5490. }
  5491. else {
  5492. transformOriginY = '50%';
  5493. // =====================在窗口中居中
  5494. // 显示的菜单的高度,简单菜单高度不超过窗口高度,若超过了则在菜单内部显示滚动条
  5495. // 级联菜单内部不允许出现滚动条
  5496. let menuHeightTemp = menuHeight;
  5497. // 简单菜单比窗口高时,限制菜单高度
  5498. if (!this.isCascade) {
  5499. if (menuHeight + gutter * 2 > windowHeight) {
  5500. menuHeightTemp = windowHeight - gutter * 2;
  5501. this.$element.height(menuHeightTemp);
  5502. }
  5503. }
  5504. menuTop =
  5505. (windowHeight - menuHeightTemp) / 2 +
  5506. (isFixed ? 0 : anchorOffsetTop - anchorTop);
  5507. }
  5508. this.$element.css('top', `${menuTop}px`);
  5509. // 设置菜单对齐方式
  5510. if (align === 'left') {
  5511. transformOriginX = '0';
  5512. menuLeft = isFixed ? anchorLeft : anchorOffsetLeft;
  5513. }
  5514. else if (align === 'right') {
  5515. transformOriginX = '100%';
  5516. menuLeft = isFixed
  5517. ? anchorLeft + anchorWidth - menuWidth
  5518. : anchorOffsetLeft + anchorWidth - menuWidth;
  5519. }
  5520. else {
  5521. transformOriginX = '50%';
  5522. //=======================在窗口中居中
  5523. // 显示的菜单的宽度,菜单宽度不能超过窗口宽度
  5524. let menuWidthTemp = menuWidth;
  5525. // 菜单比窗口宽,限制菜单宽度
  5526. if (menuWidth + gutter * 2 > windowWidth) {
  5527. menuWidthTemp = windowWidth - gutter * 2;
  5528. this.$element.width(menuWidthTemp);
  5529. }
  5530. menuLeft =
  5531. (windowWidth - menuWidthTemp) / 2 +
  5532. (isFixed ? 0 : anchorOffsetLeft - anchorLeft);
  5533. }
  5534. this.$element.css('left', `${menuLeft}px`);
  5535. // 设置菜单动画方向
  5536. this.$element.transformOrigin(`${transformOriginX} ${transformOriginY}`);
  5537. }
  5538. /**
  5539. * 调整子菜单的位置
  5540. * @param $submenu
  5541. */
  5542. readjustSubmenu($submenu) {
  5543. const $item = $submenu.parent('.mdui-menu-item');
  5544. let submenuTop;
  5545. let submenuLeft;
  5546. // 子菜单位置和方向
  5547. let position;
  5548. let align;
  5549. // window 窗口的宽度和高度
  5550. const windowHeight = $window.height();
  5551. const windowWidth = $window.width();
  5552. // 动画方向参数
  5553. let transformOriginX;
  5554. let transformOriginY;
  5555. // 子菜单的原始宽度和高度
  5556. const submenuWidth = $submenu.width();
  5557. const submenuHeight = $submenu.height();
  5558. // 触发子菜单的菜单项的宽度高度
  5559. const itemRect = $item[0].getBoundingClientRect();
  5560. const itemWidth = itemRect.width;
  5561. const itemHeight = itemRect.height;
  5562. const itemLeft = itemRect.left;
  5563. const itemTop = itemRect.top;
  5564. // 判断菜单上下位置
  5565. if (windowHeight - itemTop > submenuHeight) {
  5566. // 判断下方是否放得下菜单
  5567. position = 'bottom';
  5568. }
  5569. else if (itemTop + itemHeight > submenuHeight) {
  5570. // 判断上方是否放得下菜单
  5571. position = 'top';
  5572. }
  5573. else {
  5574. // 默认放在下方
  5575. position = 'bottom';
  5576. }
  5577. // 判断菜单左右位置
  5578. if (windowWidth - itemLeft - itemWidth > submenuWidth) {
  5579. // 判断右侧是否放得下菜单
  5580. align = 'left';
  5581. }
  5582. else if (itemLeft > submenuWidth) {
  5583. // 判断左侧是否放得下菜单
  5584. align = 'right';
  5585. }
  5586. else {
  5587. // 默认放在右侧
  5588. align = 'left';
  5589. }
  5590. // 设置菜单位置
  5591. if (position === 'bottom') {
  5592. transformOriginY = '0';
  5593. submenuTop = '0';
  5594. }
  5595. else if (position === 'top') {
  5596. transformOriginY = '100%';
  5597. submenuTop = -submenuHeight + itemHeight;
  5598. }
  5599. $submenu.css('top', `${submenuTop}px`);
  5600. // 设置菜单对齐方式
  5601. if (align === 'left') {
  5602. transformOriginX = '0';
  5603. submenuLeft = itemWidth;
  5604. }
  5605. else if (align === 'right') {
  5606. transformOriginX = '100%';
  5607. submenuLeft = -submenuWidth;
  5608. }
  5609. $submenu.css('left', `${submenuLeft}px`);
  5610. // 设置菜单动画方向
  5611. $submenu.transformOrigin(`${transformOriginX} ${transformOriginY}`);
  5612. }
  5613. /**
  5614. * 打开子菜单
  5615. * @param $submenu
  5616. */
  5617. openSubMenu($submenu) {
  5618. this.readjustSubmenu($submenu);
  5619. $submenu
  5620. .addClass('mdui-menu-open')
  5621. .parent('.mdui-menu-item')
  5622. .addClass('mdui-menu-item-active');
  5623. }
  5624. /**
  5625. * 关闭子菜单,及其嵌套的子菜单
  5626. * @param $submenu
  5627. */
  5628. closeSubMenu($submenu) {
  5629. // 关闭子菜单
  5630. $submenu
  5631. .removeClass('mdui-menu-open')
  5632. .addClass('mdui-menu-closing')
  5633. .transitionEnd(() => $submenu.removeClass('mdui-menu-closing'))
  5634. // 移除激活状态的样式
  5635. .parent('.mdui-menu-item')
  5636. .removeClass('mdui-menu-item-active');
  5637. // 循环关闭嵌套的子菜单
  5638. $submenu.find('.mdui-menu').each((_, menu) => {
  5639. const $subSubmenu = $(menu);
  5640. $subSubmenu
  5641. .removeClass('mdui-menu-open')
  5642. .addClass('mdui-menu-closing')
  5643. .transitionEnd(() => $subSubmenu.removeClass('mdui-menu-closing'))
  5644. .parent('.mdui-menu-item')
  5645. .removeClass('mdui-menu-item-active');
  5646. });
  5647. }
  5648. /**
  5649. * 切换子菜单状态
  5650. * @param $submenu
  5651. */
  5652. toggleSubMenu($submenu) {
  5653. $submenu.hasClass('mdui-menu-open')
  5654. ? this.closeSubMenu($submenu)
  5655. : this.openSubMenu($submenu);
  5656. }
  5657. /**
  5658. * 绑定子菜单事件
  5659. */
  5660. bindSubMenuEvent() {
  5661. // eslint-disable-next-line @typescript-eslint/no-this-alias
  5662. const that = this;
  5663. // 点击打开子菜单
  5664. this.$element.on('click', '.mdui-menu-item', function (event) {
  5665. const $item = $(this);
  5666. const $target = $(event.target);
  5667. // 禁用状态菜单不操作
  5668. if ($item.attr('disabled') !== undefined) {
  5669. return;
  5670. }
  5671. // 没有点击在子菜单的菜单项上时,不操作(点在了子菜单的空白区域、或分隔线上)
  5672. if ($target.is('.mdui-menu') || $target.is('.mdui-divider')) {
  5673. return;
  5674. }
  5675. // 阻止冒泡,点击菜单项时只在最后一级的 mdui-menu-item 上生效,不向上冒泡
  5676. if (!$target.parents('.mdui-menu-item').first().is($item)) {
  5677. return;
  5678. }
  5679. // 当前菜单的子菜单
  5680. const $submenu = $item.children('.mdui-menu');
  5681. // 先关闭除当前子菜单外的所有同级子菜单
  5682. $item
  5683. .parent('.mdui-menu')
  5684. .children('.mdui-menu-item')
  5685. .each((_, item) => {
  5686. const $tmpSubmenu = $(item).children('.mdui-menu');
  5687. if ($tmpSubmenu.length &&
  5688. (!$submenu.length || !$tmpSubmenu.is($submenu))) {
  5689. that.closeSubMenu($tmpSubmenu);
  5690. }
  5691. });
  5692. // 切换当前子菜单
  5693. if ($submenu.length) {
  5694. that.toggleSubMenu($submenu);
  5695. }
  5696. });
  5697. if (this.options.subMenuTrigger === 'hover') {
  5698. // 临时存储 setTimeout 对象
  5699. let timeout = null;
  5700. let timeoutOpen = null;
  5701. this.$element.on('mouseover mouseout', '.mdui-menu-item', function (event) {
  5702. const $item = $(this);
  5703. const eventType = event.type;
  5704. const $relatedTarget = $(event.relatedTarget);
  5705. // 禁用状态的菜单不操作
  5706. if ($item.attr('disabled') !== undefined) {
  5707. return;
  5708. }
  5709. // 用 mouseover 模拟 mouseenter
  5710. if (eventType === 'mouseover') {
  5711. if (!$item.is($relatedTarget) &&
  5712. contains($item[0], $relatedTarget[0])) {
  5713. return;
  5714. }
  5715. }
  5716. // 用 mouseout 模拟 mouseleave
  5717. else if (eventType === 'mouseout') {
  5718. if ($item.is($relatedTarget) ||
  5719. contains($item[0], $relatedTarget[0])) {
  5720. return;
  5721. }
  5722. }
  5723. // 当前菜单项下的子菜单,未必存在
  5724. const $submenu = $item.children('.mdui-menu');
  5725. // 鼠标移入菜单项时,显示菜单项下的子菜单
  5726. if (eventType === 'mouseover') {
  5727. if ($submenu.length) {
  5728. // 当前子菜单准备打开时,如果当前子菜单正准备着关闭,不用再关闭了
  5729. const tmpClose = $submenu.data('timeoutClose.mdui.menu');
  5730. if (tmpClose) {
  5731. clearTimeout(tmpClose);
  5732. }
  5733. // 如果当前子菜单已经打开,不操作
  5734. if ($submenu.hasClass('mdui-menu-open')) {
  5735. return;
  5736. }
  5737. // 当前子菜单准备打开时,其他准备打开的子菜单不用再打开了
  5738. clearTimeout(timeoutOpen);
  5739. // 准备打开当前子菜单
  5740. timeout = timeoutOpen = setTimeout(() => that.openSubMenu($submenu), that.options.subMenuDelay);
  5741. $submenu.data('timeoutOpen.mdui.menu', timeout);
  5742. }
  5743. }
  5744. // 鼠标移出菜单项时,关闭菜单项下的子菜单
  5745. else if (eventType === 'mouseout') {
  5746. if ($submenu.length) {
  5747. // 鼠标移出菜单项时,如果当前菜单项下的子菜单正准备打开,不用再打开了
  5748. const tmpOpen = $submenu.data('timeoutOpen.mdui.menu');
  5749. if (tmpOpen) {
  5750. clearTimeout(tmpOpen);
  5751. }
  5752. // 准备关闭当前子菜单
  5753. timeout = setTimeout(() => that.closeSubMenu($submenu), that.options.subMenuDelay);
  5754. $submenu.data('timeoutClose.mdui.menu', timeout);
  5755. }
  5756. }
  5757. });
  5758. }
  5759. }
  5760. /**
  5761. * 动画结束回调
  5762. */
  5763. transitionEnd() {
  5764. this.$element.removeClass('mdui-menu-closing');
  5765. if (this.state === 'opening') {
  5766. this.state = 'opened';
  5767. this.triggerEvent('opened');
  5768. }
  5769. if (this.state === 'closing') {
  5770. this.state = 'closed';
  5771. this.triggerEvent('closed');
  5772. // 关闭后,恢复菜单样式到默认状态,并恢复 fixed 定位
  5773. this.$element.css({
  5774. top: '',
  5775. left: '',
  5776. width: '',
  5777. position: 'fixed',
  5778. });
  5779. }
  5780. }
  5781. /**
  5782. * 切换菜单状态
  5783. */
  5784. toggle() {
  5785. this.isOpen() ? this.close() : this.open();
  5786. }
  5787. /**
  5788. * 打开菜单
  5789. */
  5790. open() {
  5791. if (this.isOpen()) {
  5792. return;
  5793. }
  5794. this.state = 'opening';
  5795. this.triggerEvent('open');
  5796. this.readjust();
  5797. this.$element
  5798. // 菜单隐藏状态使用使用 fixed 定位。
  5799. .css('position', this.options.fixed ? 'fixed' : 'absolute')
  5800. .addClass('mdui-menu-open')
  5801. .transitionEnd(() => this.transitionEnd());
  5802. }
  5803. /**
  5804. * 关闭菜单
  5805. */
  5806. close() {
  5807. if (!this.isOpen()) {
  5808. return;
  5809. }
  5810. this.state = 'closing';
  5811. this.triggerEvent('close');
  5812. // 菜单开始关闭时,关闭所有子菜单
  5813. this.$element.find('.mdui-menu').each((_, submenu) => {
  5814. this.closeSubMenu($(submenu));
  5815. });
  5816. this.$element
  5817. .removeClass('mdui-menu-open')
  5818. .addClass('mdui-menu-closing')
  5819. .transitionEnd(() => this.transitionEnd());
  5820. }
  5821. }
  5822. mdui.Menu = Menu;
  5823. const customAttr$9 = 'mdui-menu';
  5824. const dataName$3 = '_mdui_menu';
  5825. $(() => {
  5826. $document.on('click', `[${customAttr$9}]`, function () {
  5827. const $this = $(this);
  5828. let instance = $this.data(dataName$3);
  5829. if (!instance) {
  5830. const options = parseOptions(this, customAttr$9);
  5831. const menuSelector = options.target;
  5832. // @ts-ignore
  5833. delete options.target;
  5834. instance = new mdui.Menu($this, menuSelector, options);
  5835. $this.data(dataName$3, instance);
  5836. instance.toggle();
  5837. }
  5838. });
  5839. });
  5840. export default mdui;
  5841. //# sourceMappingURL=mdui.esm.js.map