model.js 375 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496249724982499250025012502250325042505250625072508250925102511251225132514251525162517251825192520252125222523252425252526252725282529253025312532253325342535253625372538253925402541254225432544254525462547254825492550255125522553255425552556255725582559256025612562256325642565256625672568256925702571257225732574257525762577257825792580258125822583258425852586258725882589259025912592259325942595259625972598259926002601260226032604260526062607260826092610261126122613261426152616261726182619262026212622262326242625262626272628262926302631263226332634263526362637263826392640264126422643264426452646264726482649265026512652265326542655265626572658265926602661266226632664266526662667266826692670267126722673267426752676267726782679268026812682268326842685268626872688268926902691269226932694269526962697269826992700270127022703270427052706270727082709271027112712271327142715271627172718271927202721272227232724272527262727272827292730273127322733273427352736273727382739274027412742274327442745274627472748274927502751275227532754275527562757275827592760276127622763276427652766276727682769277027712772277327742775277627772778277927802781278227832784278527862787278827892790279127922793279427952796279727982799280028012802280328042805280628072808280928102811281228132814281528162817281828192820282128222823282428252826282728282829283028312832283328342835283628372838283928402841284228432844284528462847284828492850285128522853285428552856285728582859286028612862286328642865286628672868286928702871287228732874287528762877287828792880288128822883288428852886288728882889289028912892289328942895289628972898289929002901290229032904290529062907290829092910291129122913291429152916291729182919292029212922292329242925292629272928292929302931293229332934293529362937293829392940294129422943294429452946294729482949295029512952295329542955295629572958295929602961296229632964296529662967296829692970297129722973297429752976297729782979298029812982298329842985298629872988298929902991299229932994299529962997299829993000300130023003300430053006300730083009301030113012301330143015301630173018301930203021302230233024302530263027302830293030303130323033303430353036303730383039304030413042304330443045304630473048304930503051305230533054305530563057305830593060306130623063306430653066306730683069307030713072307330743075307630773078307930803081308230833084308530863087308830893090309130923093309430953096309730983099310031013102310331043105310631073108310931103111311231133114311531163117311831193120312131223123312431253126312731283129313031313132313331343135313631373138313931403141314231433144314531463147314831493150315131523153315431553156315731583159316031613162316331643165316631673168316931703171317231733174317531763177317831793180318131823183318431853186318731883189319031913192319331943195319631973198319932003201320232033204320532063207320832093210321132123213321432153216321732183219322032213222322332243225322632273228322932303231323232333234323532363237323832393240324132423243324432453246324732483249325032513252325332543255325632573258325932603261326232633264326532663267326832693270327132723273327432753276327732783279328032813282328332843285328632873288328932903291329232933294329532963297329832993300330133023303330433053306330733083309331033113312331333143315331633173318331933203321332233233324332533263327332833293330333133323333333433353336333733383339334033413342334333443345334633473348334933503351335233533354335533563357335833593360336133623363336433653366336733683369337033713372337333743375337633773378337933803381338233833384338533863387338833893390339133923393339433953396339733983399340034013402340334043405340634073408340934103411341234133414341534163417341834193420342134223423342434253426342734283429343034313432343334343435343634373438343934403441344234433444344534463447344834493450345134523453345434553456345734583459346034613462346334643465346634673468346934703471347234733474347534763477347834793480348134823483348434853486348734883489349034913492349334943495349634973498349935003501350235033504350535063507350835093510351135123513351435153516351735183519352035213522352335243525352635273528352935303531353235333534353535363537353835393540354135423543354435453546354735483549355035513552355335543555355635573558355935603561356235633564356535663567356835693570357135723573357435753576357735783579358035813582358335843585358635873588358935903591359235933594359535963597359835993600360136023603360436053606360736083609361036113612361336143615361636173618361936203621362236233624362536263627362836293630363136323633363436353636363736383639364036413642364336443645364636473648364936503651365236533654365536563657365836593660366136623663366436653666366736683669367036713672367336743675367636773678367936803681368236833684368536863687368836893690369136923693369436953696369736983699370037013702370337043705370637073708370937103711371237133714371537163717371837193720372137223723372437253726372737283729373037313732373337343735373637373738373937403741374237433744374537463747374837493750375137523753375437553756375737583759376037613762376337643765376637673768376937703771377237733774377537763777377837793780378137823783378437853786378737883789379037913792379337943795379637973798379938003801380238033804380538063807380838093810381138123813381438153816381738183819382038213822382338243825382638273828382938303831383238333834383538363837383838393840384138423843384438453846384738483849385038513852385338543855385638573858385938603861386238633864386538663867386838693870387138723873387438753876387738783879388038813882388338843885388638873888388938903891389238933894389538963897389838993900390139023903390439053906390739083909391039113912391339143915391639173918391939203921392239233924392539263927392839293930393139323933393439353936393739383939394039413942394339443945394639473948394939503951395239533954395539563957395839593960396139623963396439653966396739683969397039713972397339743975397639773978397939803981398239833984398539863987398839893990399139923993399439953996399739983999400040014002400340044005400640074008400940104011401240134014401540164017401840194020402140224023402440254026402740284029403040314032403340344035403640374038403940404041404240434044404540464047404840494050405140524053405440554056405740584059406040614062406340644065406640674068406940704071407240734074407540764077407840794080408140824083408440854086408740884089409040914092409340944095409640974098409941004101410241034104410541064107410841094110411141124113411441154116411741184119412041214122412341244125412641274128412941304131413241334134413541364137413841394140414141424143414441454146414741484149415041514152415341544155415641574158415941604161416241634164416541664167416841694170417141724173417441754176417741784179418041814182418341844185418641874188418941904191419241934194419541964197419841994200420142024203420442054206420742084209421042114212421342144215421642174218421942204221422242234224422542264227422842294230423142324233423442354236423742384239424042414242424342444245424642474248424942504251425242534254425542564257425842594260426142624263426442654266426742684269427042714272427342744275427642774278427942804281428242834284428542864287428842894290429142924293429442954296429742984299430043014302430343044305430643074308430943104311431243134314431543164317431843194320432143224323432443254326432743284329433043314332433343344335433643374338433943404341434243434344434543464347434843494350435143524353435443554356435743584359436043614362436343644365436643674368436943704371437243734374437543764377437843794380438143824383438443854386438743884389439043914392439343944395439643974398439944004401440244034404440544064407440844094410441144124413441444154416441744184419442044214422442344244425442644274428442944304431443244334434443544364437443844394440444144424443444444454446444744484449445044514452445344544455445644574458445944604461446244634464446544664467446844694470447144724473447444754476447744784479448044814482448344844485448644874488448944904491449244934494449544964497449844994500450145024503450445054506450745084509451045114512451345144515451645174518451945204521452245234524452545264527452845294530453145324533453445354536453745384539454045414542454345444545454645474548454945504551455245534554455545564557455845594560456145624563456445654566456745684569457045714572457345744575457645774578457945804581458245834584458545864587458845894590459145924593459445954596459745984599460046014602460346044605460646074608460946104611461246134614461546164617461846194620462146224623462446254626462746284629463046314632463346344635463646374638463946404641464246434644464546464647464846494650465146524653465446554656465746584659466046614662466346644665466646674668466946704671467246734674467546764677467846794680468146824683468446854686468746884689469046914692469346944695469646974698469947004701470247034704470547064707470847094710471147124713471447154716471747184719472047214722472347244725472647274728472947304731473247334734473547364737473847394740474147424743474447454746474747484749475047514752475347544755475647574758475947604761476247634764476547664767476847694770477147724773477447754776477747784779478047814782478347844785478647874788478947904791479247934794479547964797479847994800480148024803480448054806480748084809481048114812481348144815481648174818481948204821482248234824482548264827482848294830483148324833483448354836483748384839484048414842484348444845484648474848484948504851485248534854485548564857485848594860486148624863486448654866486748684869487048714872487348744875487648774878487948804881488248834884488548864887488848894890489148924893489448954896489748984899490049014902490349044905490649074908490949104911491249134914491549164917491849194920492149224923492449254926492749284929493049314932493349344935493649374938493949404941494249434944494549464947494849494950495149524953495449554956495749584959496049614962496349644965496649674968496949704971497249734974497549764977497849794980498149824983498449854986498749884989499049914992499349944995499649974998499950005001500250035004500550065007500850095010501150125013501450155016501750185019502050215022502350245025502650275028502950305031503250335034503550365037503850395040504150425043504450455046504750485049505050515052505350545055505650575058505950605061506250635064506550665067506850695070507150725073507450755076507750785079508050815082508350845085508650875088508950905091509250935094509550965097509850995100510151025103510451055106510751085109511051115112511351145115511651175118511951205121512251235124512551265127512851295130513151325133513451355136513751385139514051415142514351445145514651475148514951505151515251535154515551565157515851595160516151625163516451655166516751685169517051715172517351745175517651775178517951805181518251835184518551865187518851895190519151925193519451955196519751985199520052015202520352045205520652075208520952105211521252135214521552165217521852195220522152225223522452255226522752285229523052315232523352345235523652375238523952405241524252435244524552465247524852495250525152525253525452555256525752585259526052615262526352645265526652675268526952705271527252735274527552765277527852795280528152825283528452855286528752885289529052915292529352945295529652975298529953005301530253035304530553065307530853095310531153125313531453155316531753185319532053215322532353245325532653275328532953305331533253335334533553365337533853395340534153425343534453455346534753485349535053515352535353545355535653575358535953605361536253635364536553665367536853695370537153725373537453755376537753785379538053815382538353845385538653875388538953905391539253935394539553965397539853995400540154025403540454055406540754085409541054115412541354145415541654175418541954205421542254235424542554265427542854295430543154325433543454355436543754385439544054415442544354445445544654475448544954505451545254535454545554565457545854595460546154625463546454655466546754685469547054715472547354745475547654775478547954805481548254835484548554865487548854895490549154925493549454955496549754985499550055015502550355045505550655075508550955105511551255135514551555165517551855195520552155225523552455255526552755285529553055315532553355345535553655375538553955405541554255435544554555465547554855495550555155525553555455555556555755585559556055615562556355645565556655675568556955705571557255735574557555765577557855795580558155825583558455855586558755885589559055915592559355945595559655975598559956005601560256035604560556065607560856095610561156125613561456155616561756185619562056215622562356245625562656275628562956305631563256335634563556365637563856395640564156425643564456455646564756485649565056515652565356545655565656575658565956605661566256635664566556665667566856695670567156725673567456755676567756785679568056815682568356845685568656875688568956905691569256935694569556965697569856995700570157025703570457055706570757085709571057115712571357145715571657175718571957205721572257235724572557265727572857295730573157325733573457355736573757385739574057415742574357445745574657475748574957505751575257535754575557565757575857595760576157625763576457655766576757685769577057715772577357745775577657775778577957805781578257835784578557865787578857895790579157925793579457955796579757985799580058015802580358045805580658075808580958105811581258135814581558165817581858195820582158225823582458255826582758285829583058315832583358345835583658375838583958405841584258435844584558465847584858495850585158525853585458555856585758585859586058615862586358645865586658675868586958705871587258735874587558765877587858795880588158825883588458855886588758885889589058915892589358945895589658975898589959005901590259035904590559065907590859095910591159125913591459155916591759185919592059215922592359245925592659275928592959305931593259335934593559365937593859395940594159425943594459455946594759485949595059515952595359545955595659575958595959605961596259635964596559665967596859695970597159725973597459755976597759785979598059815982598359845985598659875988598959905991599259935994599559965997599859996000600160026003600460056006600760086009601060116012601360146015601660176018601960206021602260236024602560266027602860296030603160326033603460356036603760386039604060416042604360446045604660476048604960506051605260536054605560566057605860596060606160626063606460656066606760686069607060716072607360746075607660776078607960806081608260836084608560866087608860896090609160926093609460956096609760986099610061016102610361046105610661076108610961106111611261136114611561166117611861196120612161226123612461256126612761286129613061316132613361346135613661376138613961406141614261436144614561466147614861496150615161526153615461556156615761586159616061616162616361646165616661676168616961706171617261736174617561766177617861796180618161826183618461856186618761886189619061916192619361946195619661976198619962006201620262036204620562066207620862096210621162126213621462156216621762186219622062216222622362246225622662276228622962306231623262336234623562366237623862396240624162426243624462456246624762486249625062516252625362546255625662576258625962606261626262636264626562666267626862696270627162726273627462756276627762786279628062816282628362846285628662876288628962906291629262936294629562966297629862996300630163026303630463056306630763086309631063116312631363146315631663176318631963206321632263236324632563266327632863296330633163326333633463356336633763386339634063416342634363446345634663476348634963506351635263536354635563566357635863596360636163626363636463656366636763686369637063716372637363746375637663776378637963806381638263836384638563866387638863896390639163926393639463956396639763986399640064016402640364046405640664076408640964106411641264136414641564166417641864196420642164226423642464256426642764286429643064316432643364346435643664376438643964406441644264436444644564466447644864496450645164526453645464556456645764586459646064616462646364646465646664676468646964706471647264736474647564766477647864796480648164826483648464856486648764886489649064916492649364946495649664976498649965006501650265036504650565066507650865096510651165126513651465156516651765186519652065216522652365246525652665276528652965306531653265336534653565366537653865396540654165426543654465456546654765486549655065516552655365546555655665576558655965606561656265636564656565666567656865696570657165726573657465756576657765786579658065816582658365846585658665876588658965906591659265936594659565966597659865996600660166026603660466056606660766086609661066116612661366146615661666176618661966206621662266236624662566266627662866296630663166326633663466356636663766386639664066416642664366446645664666476648664966506651665266536654665566566657665866596660666166626663666466656666666766686669667066716672667366746675667666776678667966806681668266836684668566866687668866896690669166926693669466956696669766986699670067016702670367046705670667076708670967106711671267136714671567166717671867196720672167226723672467256726672767286729673067316732673367346735673667376738673967406741674267436744674567466747674867496750675167526753675467556756675767586759676067616762676367646765676667676768676967706771677267736774677567766777677867796780678167826783678467856786678767886789679067916792679367946795679667976798679968006801680268036804680568066807680868096810681168126813681468156816681768186819682068216822682368246825682668276828682968306831683268336834683568366837683868396840684168426843684468456846684768486849685068516852685368546855685668576858685968606861686268636864686568666867686868696870687168726873687468756876687768786879688068816882688368846885688668876888688968906891689268936894689568966897689868996900690169026903690469056906690769086909691069116912691369146915691669176918691969206921692269236924692569266927692869296930693169326933693469356936693769386939694069416942694369446945694669476948694969506951695269536954695569566957695869596960696169626963696469656966696769686969697069716972697369746975697669776978697969806981698269836984698569866987698869896990699169926993699469956996699769986999700070017002700370047005700670077008700970107011701270137014701570167017701870197020702170227023702470257026702770287029703070317032703370347035703670377038703970407041704270437044704570467047704870497050705170527053705470557056705770587059706070617062706370647065706670677068706970707071707270737074707570767077707870797080708170827083708470857086708770887089709070917092709370947095709670977098709971007101710271037104710571067107710871097110711171127113711471157116711771187119712071217122712371247125712671277128712971307131713271337134713571367137713871397140714171427143714471457146714771487149715071517152715371547155715671577158715971607161716271637164716571667167716871697170717171727173717471757176717771787179718071817182718371847185718671877188718971907191719271937194719571967197719871997200720172027203720472057206720772087209721072117212721372147215721672177218721972207221722272237224722572267227722872297230723172327233723472357236723772387239724072417242724372447245724672477248724972507251725272537254725572567257725872597260726172627263726472657266726772687269727072717272727372747275727672777278727972807281728272837284728572867287728872897290729172927293729472957296729772987299730073017302730373047305730673077308730973107311731273137314731573167317731873197320732173227323732473257326732773287329733073317332733373347335733673377338733973407341734273437344734573467347734873497350735173527353735473557356735773587359736073617362736373647365736673677368736973707371737273737374737573767377737873797380738173827383738473857386738773887389739073917392739373947395739673977398739974007401740274037404740574067407740874097410741174127413741474157416741774187419742074217422742374247425742674277428742974307431743274337434743574367437743874397440744174427443744474457446744774487449745074517452745374547455745674577458745974607461746274637464746574667467746874697470747174727473747474757476747774787479748074817482748374847485748674877488748974907491749274937494749574967497749874997500750175027503750475057506750775087509751075117512751375147515751675177518751975207521752275237524752575267527752875297530753175327533753475357536753775387539754075417542754375447545754675477548754975507551755275537554755575567557755875597560756175627563756475657566756775687569757075717572757375747575757675777578757975807581758275837584758575867587758875897590759175927593759475957596759775987599760076017602760376047605760676077608760976107611761276137614761576167617761876197620762176227623762476257626762776287629763076317632763376347635763676377638763976407641764276437644764576467647764876497650765176527653765476557656765776587659766076617662766376647665766676677668766976707671767276737674767576767677767876797680768176827683768476857686768776887689769076917692769376947695769676977698769977007701770277037704770577067707770877097710771177127713771477157716771777187719772077217722772377247725772677277728772977307731773277337734773577367737773877397740774177427743774477457746774777487749775077517752775377547755775677577758775977607761776277637764776577667767776877697770777177727773777477757776777777787779778077817782778377847785778677877788778977907791779277937794779577967797779877997800780178027803780478057806780778087809781078117812781378147815781678177818781978207821782278237824782578267827782878297830783178327833783478357836783778387839784078417842784378447845784678477848784978507851785278537854785578567857785878597860786178627863786478657866786778687869787078717872787378747875787678777878787978807881788278837884788578867887788878897890789178927893789478957896789778987899790079017902790379047905790679077908790979107911791279137914791579167917791879197920792179227923792479257926792779287929793079317932793379347935793679377938793979407941794279437944794579467947794879497950795179527953795479557956795779587959796079617962796379647965796679677968796979707971797279737974797579767977797879797980798179827983798479857986798779887989799079917992799379947995799679977998799980008001800280038004800580068007800880098010801180128013801480158016801780188019802080218022802380248025802680278028802980308031803280338034803580368037803880398040804180428043804480458046804780488049805080518052805380548055805680578058805980608061806280638064806580668067806880698070807180728073807480758076807780788079808080818082808380848085808680878088808980908091809280938094809580968097809880998100810181028103810481058106810781088109811081118112811381148115811681178118811981208121812281238124812581268127812881298130813181328133813481358136813781388139814081418142814381448145814681478148814981508151815281538154815581568157815881598160816181628163816481658166816781688169817081718172817381748175817681778178817981808181818281838184818581868187818881898190819181928193819481958196819781988199820082018202820382048205820682078208820982108211821282138214821582168217821882198220822182228223822482258226822782288229823082318232823382348235823682378238823982408241824282438244824582468247824882498250825182528253825482558256825782588259826082618262826382648265826682678268826982708271827282738274827582768277827882798280828182828283828482858286828782888289829082918292829382948295829682978298829983008301830283038304830583068307830883098310831183128313831483158316831783188319832083218322832383248325832683278328832983308331833283338334833583368337833883398340834183428343834483458346834783488349835083518352835383548355835683578358835983608361836283638364836583668367836883698370837183728373837483758376837783788379838083818382838383848385838683878388838983908391839283938394839583968397839883998400840184028403840484058406840784088409841084118412841384148415841684178418841984208421842284238424842584268427842884298430843184328433843484358436843784388439844084418442844384448445844684478448844984508451845284538454845584568457845884598460846184628463846484658466846784688469847084718472847384748475847684778478847984808481848284838484848584868487848884898490849184928493849484958496849784988499850085018502850385048505850685078508850985108511851285138514851585168517851885198520852185228523852485258526852785288529853085318532853385348535853685378538853985408541854285438544854585468547854885498550855185528553855485558556855785588559856085618562856385648565856685678568856985708571857285738574857585768577857885798580858185828583858485858586858785888589859085918592859385948595859685978598859986008601860286038604860586068607860886098610861186128613861486158616861786188619862086218622862386248625862686278628862986308631863286338634863586368637863886398640864186428643864486458646864786488649865086518652865386548655865686578658865986608661866286638664866586668667866886698670867186728673867486758676867786788679868086818682868386848685868686878688868986908691869286938694869586968697869886998700870187028703870487058706870787088709871087118712871387148715871687178718871987208721872287238724872587268727872887298730873187328733873487358736873787388739874087418742874387448745874687478748874987508751875287538754875587568757875887598760876187628763876487658766876787688769877087718772877387748775877687778778877987808781878287838784878587868787878887898790879187928793879487958796879787988799880088018802880388048805880688078808880988108811881288138814881588168817881888198820882188228823882488258826882788288829883088318832883388348835883688378838883988408841884288438844884588468847884888498850885188528853885488558856885788588859886088618862886388648865886688678868886988708871887288738874887588768877887888798880888188828883888488858886888788888889889088918892889388948895889688978898889989008901890289038904890589068907890889098910891189128913891489158916891789188919892089218922892389248925892689278928892989308931893289338934893589368937893889398940894189428943894489458946894789488949895089518952895389548955895689578958895989608961896289638964896589668967896889698970897189728973897489758976897789788979898089818982898389848985
  1. /**
  2. * TinyMCE version 8.0.2 (2025-08-14)
  3. */
  4. (function () {
  5. 'use strict';
  6. var global$1 = tinymce.util.Tools.resolve('tinymce.ModelManager');
  7. /* eslint-disable @typescript-eslint/no-wrapper-object-types */
  8. const hasProto = (v, constructor, predicate) => {
  9. var _a;
  10. if (predicate(v, constructor.prototype)) {
  11. return true;
  12. }
  13. else {
  14. // String-based fallback time
  15. return ((_a = v.constructor) === null || _a === void 0 ? void 0 : _a.name) === constructor.name;
  16. }
  17. };
  18. const typeOf = (x) => {
  19. const t = typeof x;
  20. if (x === null) {
  21. return 'null';
  22. }
  23. else if (t === 'object' && Array.isArray(x)) {
  24. return 'array';
  25. }
  26. else if (t === 'object' && hasProto(x, String, (o, proto) => proto.isPrototypeOf(o))) {
  27. return 'string';
  28. }
  29. else {
  30. return t;
  31. }
  32. };
  33. const isType$1 = (type) => (value) => typeOf(value) === type;
  34. const isSimpleType = (type) => (value) => typeof value === type;
  35. const eq$2 = (t) => (a) => t === a;
  36. const isString = isType$1('string');
  37. const isObject = isType$1('object');
  38. const isArray = isType$1('array');
  39. const isNull = eq$2(null);
  40. const isBoolean = isSimpleType('boolean');
  41. const isUndefined = eq$2(undefined);
  42. const isNullable = (a) => a === null || a === undefined;
  43. const isNonNullable = (a) => !isNullable(a);
  44. const isFunction = isSimpleType('function');
  45. const isNumber = isSimpleType('number');
  46. const noop = () => { };
  47. /** Compose a unary function with an n-ary function */
  48. const compose = (fa, fb) => {
  49. return (...args) => {
  50. return fa(fb.apply(null, args));
  51. };
  52. };
  53. /** Compose two unary functions. Similar to compose, but avoids using Function.prototype.apply. */
  54. const compose1 = (fbc, fab) => (a) => fbc(fab(a));
  55. const constant = (value) => {
  56. return () => {
  57. return value;
  58. };
  59. };
  60. const identity = (x) => {
  61. return x;
  62. };
  63. const tripleEquals = (a, b) => {
  64. return a === b;
  65. };
  66. function curry(fn, ...initialArgs) {
  67. return (...restArgs) => {
  68. const all = initialArgs.concat(restArgs);
  69. return fn.apply(null, all);
  70. };
  71. }
  72. const not = (f) => (t) => !f(t);
  73. const die = (msg) => {
  74. return () => {
  75. throw new Error(msg);
  76. };
  77. };
  78. const apply = (f) => {
  79. return f();
  80. };
  81. const never = constant(false);
  82. const always = constant(true);
  83. /**
  84. * The `Optional` type represents a value (of any type) that potentially does
  85. * not exist. Any `Optional<T>` can either be a `Some<T>` (in which case the
  86. * value does exist) or a `None` (in which case the value does not exist). This
  87. * module defines a whole lot of FP-inspired utility functions for dealing with
  88. * `Optional` objects.
  89. *
  90. * Comparison with null or undefined:
  91. * - We don't get fancy null coalescing operators with `Optional`
  92. * - We do get fancy helper functions with `Optional`
  93. * - `Optional` support nesting, and allow for the type to still be nullable (or
  94. * another `Optional`)
  95. * - There is no option to turn off strict-optional-checks like there is for
  96. * strict-null-checks
  97. */
  98. class Optional {
  99. // The internal representation has a `tag` and a `value`, but both are
  100. // private: able to be console.logged, but not able to be accessed by code
  101. constructor(tag, value) {
  102. this.tag = tag;
  103. this.value = value;
  104. }
  105. // --- Identities ---
  106. /**
  107. * Creates a new `Optional<T>` that **does** contain a value.
  108. */
  109. static some(value) {
  110. return new Optional(true, value);
  111. }
  112. /**
  113. * Create a new `Optional<T>` that **does not** contain a value. `T` can be
  114. * any type because we don't actually have a `T`.
  115. */
  116. static none() {
  117. return Optional.singletonNone;
  118. }
  119. /**
  120. * Perform a transform on an `Optional` type. Regardless of whether this
  121. * `Optional` contains a value or not, `fold` will return a value of type `U`.
  122. * If this `Optional` does not contain a value, the `U` will be created by
  123. * calling `onNone`. If this `Optional` does contain a value, the `U` will be
  124. * created by calling `onSome`.
  125. *
  126. * For the FP enthusiasts in the room, this function:
  127. * 1. Could be used to implement all of the functions below
  128. * 2. Forms a catamorphism
  129. */
  130. fold(onNone, onSome) {
  131. if (this.tag) {
  132. return onSome(this.value);
  133. }
  134. else {
  135. return onNone();
  136. }
  137. }
  138. /**
  139. * Determine if this `Optional` object contains a value.
  140. */
  141. isSome() {
  142. return this.tag;
  143. }
  144. /**
  145. * Determine if this `Optional` object **does not** contain a value.
  146. */
  147. isNone() {
  148. return !this.tag;
  149. }
  150. // --- Functor (name stolen from Haskell / maths) ---
  151. /**
  152. * Perform a transform on an `Optional` object, **if** there is a value. If
  153. * you provide a function to turn a T into a U, this is the function you use
  154. * to turn an `Optional<T>` into an `Optional<U>`. If this **does** contain
  155. * a value then the output will also contain a value (that value being the
  156. * output of `mapper(this.value)`), and if this **does not** contain a value
  157. * then neither will the output.
  158. */
  159. map(mapper) {
  160. if (this.tag) {
  161. return Optional.some(mapper(this.value));
  162. }
  163. else {
  164. return Optional.none();
  165. }
  166. }
  167. // --- Monad (name stolen from Haskell / maths) ---
  168. /**
  169. * Perform a transform on an `Optional` object, **if** there is a value.
  170. * Unlike `map`, here the transform itself also returns an `Optional`.
  171. */
  172. bind(binder) {
  173. if (this.tag) {
  174. return binder(this.value);
  175. }
  176. else {
  177. return Optional.none();
  178. }
  179. }
  180. // --- Traversable (name stolen from Haskell / maths) ---
  181. /**
  182. * For a given predicate, this function finds out if there **exists** a value
  183. * inside this `Optional` object that meets the predicate. In practice, this
  184. * means that for `Optional`s that do not contain a value it returns false (as
  185. * no predicate-meeting value exists).
  186. */
  187. exists(predicate) {
  188. return this.tag && predicate(this.value);
  189. }
  190. /**
  191. * For a given predicate, this function finds out if **all** the values inside
  192. * this `Optional` object meet the predicate. In practice, this means that
  193. * for `Optional`s that do not contain a value it returns true (as all 0
  194. * objects do meet the predicate).
  195. */
  196. forall(predicate) {
  197. return !this.tag || predicate(this.value);
  198. }
  199. filter(predicate) {
  200. if (!this.tag || predicate(this.value)) {
  201. return this;
  202. }
  203. else {
  204. return Optional.none();
  205. }
  206. }
  207. // --- Getters ---
  208. /**
  209. * Get the value out of the inside of the `Optional` object, using a default
  210. * `replacement` value if the provided `Optional` object does not contain a
  211. * value.
  212. */
  213. getOr(replacement) {
  214. return this.tag ? this.value : replacement;
  215. }
  216. /**
  217. * Get the value out of the inside of the `Optional` object, using a default
  218. * `replacement` value if the provided `Optional` object does not contain a
  219. * value. Unlike `getOr`, in this method the `replacement` object is also
  220. * `Optional` - meaning that this method will always return an `Optional`.
  221. */
  222. or(replacement) {
  223. return this.tag ? this : replacement;
  224. }
  225. /**
  226. * Get the value out of the inside of the `Optional` object, using a default
  227. * `replacement` value if the provided `Optional` object does not contain a
  228. * value. Unlike `getOr`, in this method the `replacement` value is
  229. * "thunked" - that is to say that you don't pass a value to `getOrThunk`, you
  230. * pass a function which (if called) will **return** the `value` you want to
  231. * use.
  232. */
  233. getOrThunk(thunk) {
  234. return this.tag ? this.value : thunk();
  235. }
  236. /**
  237. * Get the value out of the inside of the `Optional` object, using a default
  238. * `replacement` value if the provided Optional object does not contain a
  239. * value.
  240. *
  241. * Unlike `or`, in this method the `replacement` value is "thunked" - that is
  242. * to say that you don't pass a value to `orThunk`, you pass a function which
  243. * (if called) will **return** the `value` you want to use.
  244. *
  245. * Unlike `getOrThunk`, in this method the `replacement` value is also
  246. * `Optional`, meaning that this method will always return an `Optional`.
  247. */
  248. orThunk(thunk) {
  249. return this.tag ? this : thunk();
  250. }
  251. /**
  252. * Get the value out of the inside of the `Optional` object, throwing an
  253. * exception if the provided `Optional` object does not contain a value.
  254. *
  255. * WARNING:
  256. * You should only be using this function if you know that the `Optional`
  257. * object **is not** empty (otherwise you're throwing exceptions in production
  258. * code, which is bad).
  259. *
  260. * In tests this is more acceptable.
  261. *
  262. * Prefer other methods to this, such as `.each`.
  263. */
  264. getOrDie(message) {
  265. if (!this.tag) {
  266. throw new Error(message !== null && message !== void 0 ? message : 'Called getOrDie on None');
  267. }
  268. else {
  269. return this.value;
  270. }
  271. }
  272. // --- Interop with null and undefined ---
  273. /**
  274. * Creates an `Optional` value from a nullable (or undefined-able) input.
  275. * Null, or undefined, is converted to `None`, and anything else is converted
  276. * to `Some`.
  277. */
  278. static from(value) {
  279. return isNonNullable(value) ? Optional.some(value) : Optional.none();
  280. }
  281. /**
  282. * Converts an `Optional` to a nullable type, by getting the value if it
  283. * exists, or returning `null` if it does not.
  284. */
  285. getOrNull() {
  286. return this.tag ? this.value : null;
  287. }
  288. /**
  289. * Converts an `Optional` to an undefined-able type, by getting the value if
  290. * it exists, or returning `undefined` if it does not.
  291. */
  292. getOrUndefined() {
  293. return this.value;
  294. }
  295. // --- Utilities ---
  296. /**
  297. * If the `Optional` contains a value, perform an action on that value.
  298. * Unlike the rest of the methods on this type, `.each` has side-effects. If
  299. * you want to transform an `Optional<T>` **into** something, then this is not
  300. * the method for you. If you want to use an `Optional<T>` to **do**
  301. * something, then this is the method for you - provided you're okay with not
  302. * doing anything in the case where the `Optional` doesn't have a value inside
  303. * it. If you're not sure whether your use-case fits into transforming
  304. * **into** something or **doing** something, check whether it has a return
  305. * value. If it does, you should be performing a transform.
  306. */
  307. each(worker) {
  308. if (this.tag) {
  309. worker(this.value);
  310. }
  311. }
  312. /**
  313. * Turn the `Optional` object into an array that contains all of the values
  314. * stored inside the `Optional`. In practice, this means the output will have
  315. * either 0 or 1 elements.
  316. */
  317. toArray() {
  318. return this.tag ? [this.value] : [];
  319. }
  320. /**
  321. * Turn the `Optional` object into a string for debugging or printing. Not
  322. * recommended for production code, but good for debugging. Also note that
  323. * these days an `Optional` object can be logged to the console directly, and
  324. * its inner value (if it exists) will be visible.
  325. */
  326. toString() {
  327. return this.tag ? `some(${this.value})` : 'none()';
  328. }
  329. }
  330. // Sneaky optimisation: every instance of Optional.none is identical, so just
  331. // reuse the same object
  332. Optional.singletonNone = new Optional(false);
  333. const nativeSlice = Array.prototype.slice;
  334. const nativeIndexOf = Array.prototype.indexOf;
  335. const nativePush = Array.prototype.push;
  336. const rawIndexOf = (ts, t) => nativeIndexOf.call(ts, t);
  337. const contains$2 = (xs, x) => rawIndexOf(xs, x) > -1;
  338. const exists = (xs, pred) => {
  339. for (let i = 0, len = xs.length; i < len; i++) {
  340. const x = xs[i];
  341. if (pred(x, i)) {
  342. return true;
  343. }
  344. }
  345. return false;
  346. };
  347. const range$1 = (num, f) => {
  348. const r = [];
  349. for (let i = 0; i < num; i++) {
  350. r.push(f(i));
  351. }
  352. return r;
  353. };
  354. const map$1 = (xs, f) => {
  355. // pre-allocating array size when it's guaranteed to be known
  356. // http://jsperf.com/push-allocated-vs-dynamic/22
  357. const len = xs.length;
  358. const r = new Array(len);
  359. for (let i = 0; i < len; i++) {
  360. const x = xs[i];
  361. r[i] = f(x, i);
  362. }
  363. return r;
  364. };
  365. // Unwound implementing other functions in terms of each.
  366. // The code size is roughly the same, and it should allow for better optimisation.
  367. // const each = function<T, U>(xs: T[], f: (x: T, i?: number, xs?: T[]) => void): void {
  368. const each$2 = (xs, f) => {
  369. for (let i = 0, len = xs.length; i < len; i++) {
  370. const x = xs[i];
  371. f(x, i);
  372. }
  373. };
  374. const eachr = (xs, f) => {
  375. for (let i = xs.length - 1; i >= 0; i--) {
  376. const x = xs[i];
  377. f(x, i);
  378. }
  379. };
  380. const partition = (xs, pred) => {
  381. const pass = [];
  382. const fail = [];
  383. for (let i = 0, len = xs.length; i < len; i++) {
  384. const x = xs[i];
  385. const arr = pred(x, i) ? pass : fail;
  386. arr.push(x);
  387. }
  388. return { pass, fail };
  389. };
  390. const filter$2 = (xs, pred) => {
  391. const r = [];
  392. for (let i = 0, len = xs.length; i < len; i++) {
  393. const x = xs[i];
  394. if (pred(x, i)) {
  395. r.push(x);
  396. }
  397. }
  398. return r;
  399. };
  400. const foldr = (xs, f, acc) => {
  401. eachr(xs, (x, i) => {
  402. acc = f(acc, x, i);
  403. });
  404. return acc;
  405. };
  406. const foldl = (xs, f, acc) => {
  407. each$2(xs, (x, i) => {
  408. acc = f(acc, x, i);
  409. });
  410. return acc;
  411. };
  412. const findUntil = (xs, pred, until) => {
  413. for (let i = 0, len = xs.length; i < len; i++) {
  414. const x = xs[i];
  415. if (pred(x, i)) {
  416. return Optional.some(x);
  417. }
  418. else if (until(x, i)) {
  419. break;
  420. }
  421. }
  422. return Optional.none();
  423. };
  424. const find$1 = (xs, pred) => {
  425. return findUntil(xs, pred, never);
  426. };
  427. const findIndex = (xs, pred) => {
  428. for (let i = 0, len = xs.length; i < len; i++) {
  429. const x = xs[i];
  430. if (pred(x, i)) {
  431. return Optional.some(i);
  432. }
  433. }
  434. return Optional.none();
  435. };
  436. const flatten = (xs) => {
  437. // Note, this is possible because push supports multiple arguments:
  438. // http://jsperf.com/concat-push/6
  439. // Note that in the past, concat() would silently work (very slowly) for array-like objects.
  440. // With this change it will throw an error.
  441. const r = [];
  442. for (let i = 0, len = xs.length; i < len; ++i) {
  443. // Ensure that each value is an array itself
  444. if (!isArray(xs[i])) {
  445. throw new Error('Arr.flatten item ' + i + ' was not an array, input: ' + xs);
  446. }
  447. nativePush.apply(r, xs[i]);
  448. }
  449. return r;
  450. };
  451. const bind$2 = (xs, f) => flatten(map$1(xs, f));
  452. const forall = (xs, pred) => {
  453. for (let i = 0, len = xs.length; i < len; ++i) {
  454. const x = xs[i];
  455. if (pred(x, i) !== true) {
  456. return false;
  457. }
  458. }
  459. return true;
  460. };
  461. const reverse = (xs) => {
  462. const r = nativeSlice.call(xs, 0);
  463. r.reverse();
  464. return r;
  465. };
  466. const mapToObject = (xs, f) => {
  467. const r = {};
  468. for (let i = 0, len = xs.length; i < len; i++) {
  469. const x = xs[i];
  470. r[String(x)] = f(x, i);
  471. }
  472. return r;
  473. };
  474. const sort$1 = (xs, comparator) => {
  475. const copy = nativeSlice.call(xs, 0);
  476. copy.sort(comparator);
  477. return copy;
  478. };
  479. const get$d = (xs, i) => i >= 0 && i < xs.length ? Optional.some(xs[i]) : Optional.none();
  480. const head = (xs) => get$d(xs, 0);
  481. const last$2 = (xs) => get$d(xs, xs.length - 1);
  482. const findMap = (arr, f) => {
  483. for (let i = 0; i < arr.length; i++) {
  484. const r = f(arr[i], i);
  485. if (r.isSome()) {
  486. return r;
  487. }
  488. }
  489. return Optional.none();
  490. };
  491. // There are many variations of Object iteration that are faster than the 'for-in' style:
  492. // http://jsperf.com/object-keys-iteration/107
  493. //
  494. // Use the native keys if it is available (IE9+), otherwise fall back to manually filtering
  495. const keys = Object.keys;
  496. const hasOwnProperty = Object.hasOwnProperty;
  497. const each$1 = (obj, f) => {
  498. const props = keys(obj);
  499. for (let k = 0, len = props.length; k < len; k++) {
  500. const i = props[k];
  501. const x = obj[i];
  502. f(x, i);
  503. }
  504. };
  505. const map = (obj, f) => {
  506. return tupleMap(obj, (x, i) => ({
  507. k: i,
  508. v: f(x, i)
  509. }));
  510. };
  511. const tupleMap = (obj, f) => {
  512. const r = {};
  513. each$1(obj, (x, i) => {
  514. const tuple = f(x, i);
  515. r[tuple.k] = tuple.v;
  516. });
  517. return r;
  518. };
  519. const objAcc = (r) => (x, i) => {
  520. r[i] = x;
  521. };
  522. const internalFilter = (obj, pred, onTrue, onFalse) => {
  523. each$1(obj, (x, i) => {
  524. (pred(x, i) ? onTrue : onFalse)(x, i);
  525. });
  526. };
  527. const filter$1 = (obj, pred) => {
  528. const t = {};
  529. internalFilter(obj, pred, objAcc(t), noop);
  530. return t;
  531. };
  532. const mapToArray = (obj, f) => {
  533. const r = [];
  534. each$1(obj, (value, name) => {
  535. r.push(f(value, name));
  536. });
  537. return r;
  538. };
  539. const values = (obj) => {
  540. return mapToArray(obj, identity);
  541. };
  542. const get$c = (obj, key) => {
  543. return has$1(obj, key) ? Optional.from(obj[key]) : Optional.none();
  544. };
  545. const has$1 = (obj, key) => hasOwnProperty.call(obj, key);
  546. const hasNonNullableKey = (obj, key) => has$1(obj, key) && obj[key] !== undefined && obj[key] !== null;
  547. const isEmpty = (r) => {
  548. for (const x in r) {
  549. if (hasOwnProperty.call(r, x)) {
  550. return false;
  551. }
  552. }
  553. return true;
  554. };
  555. /*
  556. * Generates a church encoded ADT (https://en.wikipedia.org/wiki/Church_encoding)
  557. * For syntax and use, look at the test code.
  558. */
  559. const generate$1 = (cases) => {
  560. // validation
  561. if (!isArray(cases)) {
  562. throw new Error('cases must be an array');
  563. }
  564. if (cases.length === 0) {
  565. throw new Error('there must be at least one case');
  566. }
  567. const constructors = [];
  568. // adt is mutated to add the individual cases
  569. const adt = {};
  570. each$2(cases, (acase, count) => {
  571. const keys$1 = keys(acase);
  572. // validation
  573. if (keys$1.length !== 1) {
  574. throw new Error('one and only one name per case');
  575. }
  576. const key = keys$1[0];
  577. const value = acase[key];
  578. // validation
  579. if (adt[key] !== undefined) {
  580. throw new Error('duplicate key detected:' + key);
  581. }
  582. else if (key === 'cata') {
  583. throw new Error('cannot have a case named cata (sorry)');
  584. }
  585. else if (!isArray(value)) {
  586. // this implicitly checks if acase is an object
  587. throw new Error('case arguments must be an array');
  588. }
  589. constructors.push(key);
  590. //
  591. // constructor for key
  592. //
  593. adt[key] = (...args) => {
  594. const argLength = args.length;
  595. // validation
  596. if (argLength !== value.length) {
  597. throw new Error('Wrong number of arguments to case ' + key + '. Expected ' + value.length + ' (' + value + '), got ' + argLength);
  598. }
  599. const match = (branches) => {
  600. const branchKeys = keys(branches);
  601. if (constructors.length !== branchKeys.length) {
  602. throw new Error('Wrong number of arguments to match. Expected: ' + constructors.join(',') + '\nActual: ' + branchKeys.join(','));
  603. }
  604. const allReqd = forall(constructors, (reqKey) => {
  605. return contains$2(branchKeys, reqKey);
  606. });
  607. if (!allReqd) {
  608. throw new Error('Not all branches were specified when using match. Specified: ' + branchKeys.join(', ') + '\nRequired: ' + constructors.join(', '));
  609. }
  610. return branches[key].apply(null, args);
  611. };
  612. //
  613. // the fold function for key
  614. //
  615. return {
  616. fold: (...foldArgs) => {
  617. // runtime validation
  618. if (foldArgs.length !== cases.length) {
  619. throw new Error('Wrong number of arguments to fold. Expected ' + cases.length + ', got ' + foldArgs.length);
  620. }
  621. const target = foldArgs[count];
  622. return target.apply(null, args);
  623. },
  624. match,
  625. // NOTE: Only for debugging.
  626. log: (label) => {
  627. // eslint-disable-next-line no-console
  628. console.log(label, {
  629. constructors,
  630. constructor: key,
  631. params: args
  632. });
  633. }
  634. };
  635. };
  636. });
  637. return adt;
  638. };
  639. const Adt = {
  640. generate: generate$1
  641. };
  642. const Cell = (initial) => {
  643. let value = initial;
  644. const get = () => {
  645. return value;
  646. };
  647. const set = (v) => {
  648. value = v;
  649. };
  650. return {
  651. get,
  652. set
  653. };
  654. };
  655. const sort = (arr) => {
  656. return arr.slice(0).sort();
  657. };
  658. const reqMessage = (required, keys) => {
  659. throw new Error('All required keys (' + sort(required).join(', ') + ') were not specified. Specified keys were: ' + sort(keys).join(', ') + '.');
  660. };
  661. const unsuppMessage = (unsupported) => {
  662. throw new Error('Unsupported keys for object: ' + sort(unsupported).join(', '));
  663. };
  664. const validateStrArr = (label, array) => {
  665. if (!isArray(array)) {
  666. throw new Error('The ' + label + ' fields must be an array. Was: ' + array + '.');
  667. }
  668. each$2(array, (a) => {
  669. if (!isString(a)) {
  670. throw new Error('The value ' + a + ' in the ' + label + ' fields was not a string.');
  671. }
  672. });
  673. };
  674. const invalidTypeMessage = (incorrect, type) => {
  675. throw new Error('All values need to be of type: ' + type + '. Keys (' + sort(incorrect).join(', ') + ') were not.');
  676. };
  677. const checkDupes = (everything) => {
  678. const sorted = sort(everything);
  679. const dupe = find$1(sorted, (s, i) => {
  680. return i < sorted.length - 1 && s === sorted[i + 1];
  681. });
  682. dupe.each((d) => {
  683. throw new Error('The field: ' + d + ' occurs more than once in the combined fields: [' + sorted.join(', ') + '].');
  684. });
  685. };
  686. // Ensure that the object has all required fields. They must be functions.
  687. const base = (handleUnsupported, required) => {
  688. return baseWith(handleUnsupported, required, {
  689. validate: isFunction,
  690. label: 'function'
  691. });
  692. };
  693. // Ensure that the object has all required fields. They must satisy predicates.
  694. const baseWith = (handleUnsupported, required, pred) => {
  695. if (required.length === 0) {
  696. throw new Error('You must specify at least one required field.');
  697. }
  698. validateStrArr('required', required);
  699. checkDupes(required);
  700. return (obj) => {
  701. const keys$1 = keys(obj);
  702. // Ensure all required keys are present.
  703. const allReqd = forall(required, (req) => {
  704. return contains$2(keys$1, req);
  705. });
  706. if (!allReqd) {
  707. reqMessage(required, keys$1);
  708. }
  709. handleUnsupported(required, keys$1);
  710. const invalidKeys = filter$2(required, (key) => {
  711. return !pred.validate(obj[key], key);
  712. });
  713. if (invalidKeys.length > 0) {
  714. invalidTypeMessage(invalidKeys, pred.label);
  715. }
  716. return obj;
  717. };
  718. };
  719. const handleExact = (required, keys) => {
  720. const unsupported = filter$2(keys, (key) => {
  721. return !contains$2(required, key);
  722. });
  723. if (unsupported.length > 0) {
  724. unsuppMessage(unsupported);
  725. }
  726. };
  727. const exactly = (required) => base(handleExact, required);
  728. /**
  729. * Creates a new `Result<T, E>` that **does** contain a value.
  730. */
  731. const value$1 = (value) => {
  732. const applyHelper = (fn) => fn(value);
  733. const constHelper = constant(value);
  734. const outputHelper = () => output;
  735. const output = {
  736. // Debug info
  737. tag: true,
  738. inner: value,
  739. // Actual Result methods
  740. fold: (_onError, onValue) => onValue(value),
  741. isValue: always,
  742. isError: never,
  743. map: (mapper) => Result.value(mapper(value)),
  744. mapError: outputHelper,
  745. bind: applyHelper,
  746. exists: applyHelper,
  747. forall: applyHelper,
  748. getOr: constHelper,
  749. or: outputHelper,
  750. getOrThunk: constHelper,
  751. orThunk: outputHelper,
  752. getOrDie: constHelper,
  753. each: (fn) => {
  754. // Can't write the function inline because we don't want to return something by mistake
  755. fn(value);
  756. },
  757. toOptional: () => Optional.some(value),
  758. };
  759. return output;
  760. };
  761. /**
  762. * Creates a new `Result<T, E>` that **does not** contain a value, and therefore
  763. * contains an error.
  764. */
  765. const error = (error) => {
  766. const outputHelper = () => output;
  767. const output = {
  768. // Debug info
  769. tag: false,
  770. inner: error,
  771. // Actual Result methods
  772. fold: (onError, _onValue) => onError(error),
  773. isValue: never,
  774. isError: always,
  775. map: outputHelper,
  776. mapError: (mapper) => Result.error(mapper(error)),
  777. bind: outputHelper,
  778. exists: never,
  779. forall: always,
  780. getOr: identity,
  781. or: identity,
  782. getOrThunk: apply,
  783. orThunk: apply,
  784. getOrDie: die(String(error)),
  785. each: noop,
  786. toOptional: Optional.none,
  787. };
  788. return output;
  789. };
  790. /**
  791. * Creates a new `Result<T, E>` from an `Optional<T>` and an `E`. If the
  792. * `Optional` contains a value, so will the outputted `Result`. If it does not,
  793. * the outputted `Result` will contain an error (and that error will be the
  794. * error passed in).
  795. */
  796. const fromOption = (optional, err) => optional.fold(() => error(err), value$1);
  797. const Result = {
  798. value: value$1,
  799. error,
  800. fromOption
  801. };
  802. // Use window object as the global if it's available since CSP will block script evals
  803. // eslint-disable-next-line @typescript-eslint/no-implied-eval
  804. const Global = typeof window !== 'undefined' ? window : Function('return this;')();
  805. // This API is intended to give the capability to return namespaced strings.
  806. // For CSS, since dots are not valid class names, the dots are turned into dashes.
  807. const css = (namespace) => {
  808. const dashNamespace = namespace.replace(/\./g, '-');
  809. const resolve = (str) => {
  810. return dashNamespace + '-' + str;
  811. };
  812. return {
  813. resolve
  814. };
  815. };
  816. /**
  817. * **Is** the value stored inside this Optional object equal to `rhs`?
  818. */
  819. const is$2 = (lhs, rhs, comparator = tripleEquals) => lhs.exists((left) => comparator(left, rhs));
  820. const cat = (arr) => {
  821. const r = [];
  822. const push = (x) => {
  823. r.push(x);
  824. };
  825. for (let i = 0; i < arr.length; i++) {
  826. arr[i].each(push);
  827. }
  828. return r;
  829. };
  830. const bindFrom = (a, f) => (a !== undefined && a !== null) ? f(a) : Optional.none();
  831. // This can help with type inference, by specifying the type param on the none case, so the caller doesn't have to.
  832. const someIf = (b, a) => b ? Optional.some(a) : Optional.none();
  833. /** path :: ([String], JsObj?) -> JsObj */
  834. const path = (parts, scope) => {
  835. let o = scope !== undefined && scope !== null ? scope : Global;
  836. for (let i = 0; i < parts.length && o !== undefined && o !== null; ++i) {
  837. o = o[parts[i]];
  838. }
  839. return o;
  840. };
  841. /** resolve :: (String, JsObj?) -> JsObj */
  842. const resolve$2 = (p, scope) => {
  843. const parts = p.split('.');
  844. return path(parts, scope);
  845. };
  846. const singleton = (doRevoke) => {
  847. const subject = Cell(Optional.none());
  848. const revoke = () => subject.get().each(doRevoke);
  849. const clear = () => {
  850. revoke();
  851. subject.set(Optional.none());
  852. };
  853. const isSet = () => subject.get().isSome();
  854. const get = () => subject.get();
  855. const set = (s) => {
  856. revoke();
  857. subject.set(Optional.some(s));
  858. };
  859. return {
  860. clear,
  861. isSet,
  862. get,
  863. set
  864. };
  865. };
  866. const value = () => {
  867. const subject = singleton(noop);
  868. const on = (f) => subject.get().each(f);
  869. return {
  870. ...subject,
  871. on
  872. };
  873. };
  874. const removeFromStart = (str, numChars) => {
  875. return str.substring(numChars);
  876. };
  877. const checkRange = (str, substr, start) => substr === '' || str.length >= substr.length && str.substr(start, start + substr.length) === substr;
  878. const removeLeading = (str, prefix) => {
  879. return startsWith(str, prefix) ? removeFromStart(str, prefix.length) : str;
  880. };
  881. const contains$1 = (str, substr, start = 0, end) => {
  882. const idx = str.indexOf(substr, start);
  883. if (idx !== -1) {
  884. return isUndefined(end) ? true : idx + substr.length <= end;
  885. }
  886. else {
  887. return false;
  888. }
  889. };
  890. /** Does 'str' start with 'prefix'?
  891. * Note: all strings start with the empty string.
  892. * More formally, for all strings x, startsWith(x, "").
  893. * This is so that for all strings x and y, startsWith(y + x, y)
  894. */
  895. const startsWith = (str, prefix) => {
  896. return checkRange(str, prefix, 0);
  897. };
  898. /** Does 'str' end with 'suffix'?
  899. * Note: all strings end with the empty string.
  900. * More formally, for all strings x, endsWith(x, "").
  901. * This is so that for all strings x and y, endsWith(x + y, y)
  902. */
  903. const endsWith = (str, suffix) => {
  904. return checkRange(str, suffix, str.length - suffix.length);
  905. };
  906. const blank = (r) => (s) => s.replace(r, '');
  907. /** removes all leading and trailing spaces */
  908. const trim = blank(/^\s+|\s+$/g);
  909. const isNotEmpty = (s) => s.length > 0;
  910. const toFloat = (value) => {
  911. const num = parseFloat(value);
  912. return isNaN(num) ? Optional.none() : Optional.some(num);
  913. };
  914. // Run a function fn after rate ms. If another invocation occurs
  915. // during the time it is waiting, reschedule the function again
  916. // with the new arguments.
  917. const last$1 = (fn, rate) => {
  918. let timer = null;
  919. const cancel = () => {
  920. if (!isNull(timer)) {
  921. clearTimeout(timer);
  922. timer = null;
  923. }
  924. };
  925. const throttle = (...args) => {
  926. cancel();
  927. timer = setTimeout(() => {
  928. timer = null;
  929. fn.apply(null, args);
  930. }, rate);
  931. };
  932. return {
  933. cancel,
  934. throttle
  935. };
  936. };
  937. const cached = (f) => {
  938. let called = false;
  939. let r;
  940. return (...args) => {
  941. if (!called) {
  942. called = true;
  943. r = f.apply(null, args);
  944. }
  945. return r;
  946. };
  947. };
  948. const nbsp = '\u00A0';
  949. const validSectionList = ['tfoot', 'thead', 'tbody', 'colgroup'];
  950. const isValidSection = (parentName) => contains$2(validSectionList, parentName);
  951. const grid = (rows, columns) => ({
  952. rows,
  953. columns
  954. });
  955. const address = (row, column) => ({
  956. row,
  957. column
  958. });
  959. const detail = (element, rowspan, colspan) => ({
  960. element,
  961. rowspan,
  962. colspan
  963. });
  964. const detailnew = (element, rowspan, colspan, isNew) => ({
  965. element,
  966. rowspan,
  967. colspan,
  968. isNew
  969. });
  970. const extended = (element, rowspan, colspan, row, column, isLocked) => ({
  971. element,
  972. rowspan,
  973. colspan,
  974. row,
  975. column,
  976. isLocked
  977. });
  978. const rowdetail = (element, cells, section) => ({
  979. element,
  980. cells,
  981. section
  982. });
  983. const rowdetailnew = (element, cells, section, isNew) => ({
  984. element,
  985. cells,
  986. section,
  987. isNew
  988. });
  989. const elementnew = (element, isNew, isLocked) => ({
  990. element,
  991. isNew,
  992. isLocked
  993. });
  994. const rowcells = (element, cells, section, isNew) => ({
  995. element,
  996. cells,
  997. section,
  998. isNew
  999. });
  1000. const bounds = (startRow, startCol, finishRow, finishCol) => ({
  1001. startRow,
  1002. startCol,
  1003. finishRow,
  1004. finishCol
  1005. });
  1006. const columnext = (element, colspan, column) => ({
  1007. element,
  1008. colspan,
  1009. column
  1010. });
  1011. const colgroup = (element, columns) => ({
  1012. element,
  1013. columns
  1014. });
  1015. const addCells = (gridRow, index, cells) => {
  1016. const existingCells = gridRow.cells;
  1017. const before = existingCells.slice(0, index);
  1018. const after = existingCells.slice(index);
  1019. const newCells = before.concat(cells).concat(after);
  1020. return setCells(gridRow, newCells);
  1021. };
  1022. const addCell = (gridRow, index, cell) => addCells(gridRow, index, [cell]);
  1023. const mutateCell = (gridRow, index, cell) => {
  1024. const cells = gridRow.cells;
  1025. cells[index] = cell;
  1026. };
  1027. const setCells = (gridRow, cells) => rowcells(gridRow.element, cells, gridRow.section, gridRow.isNew);
  1028. const mapCells = (gridRow, f) => {
  1029. const cells = gridRow.cells;
  1030. const r = map$1(cells, f);
  1031. return rowcells(gridRow.element, r, gridRow.section, gridRow.isNew);
  1032. };
  1033. const getCell = (gridRow, index) => gridRow.cells[index];
  1034. const getCellElement = (gridRow, index) => getCell(gridRow, index).element;
  1035. const cellLength = (gridRow) => gridRow.cells.length;
  1036. const extractGridDetails = (grid) => {
  1037. const result = partition(grid, (row) => row.section === 'colgroup');
  1038. return {
  1039. rows: result.fail,
  1040. cols: result.pass
  1041. };
  1042. };
  1043. const clone$2 = (gridRow, cloneRow, cloneCell) => {
  1044. const newCells = map$1(gridRow.cells, cloneCell);
  1045. return rowcells(cloneRow(gridRow.element), newCells, gridRow.section, true);
  1046. };
  1047. const fromHtml$1 = (html, scope) => {
  1048. const doc = scope || document;
  1049. const div = doc.createElement('div');
  1050. div.innerHTML = html;
  1051. if (!div.hasChildNodes() || div.childNodes.length > 1) {
  1052. const message = 'HTML does not have a single root node';
  1053. // eslint-disable-next-line no-console
  1054. console.error(message, html);
  1055. throw new Error(message);
  1056. }
  1057. return fromDom$1(div.childNodes[0]);
  1058. };
  1059. const fromTag = (tag, scope) => {
  1060. const doc = scope || document;
  1061. const node = doc.createElement(tag);
  1062. return fromDom$1(node);
  1063. };
  1064. const fromText = (text, scope) => {
  1065. const doc = scope || document;
  1066. const node = doc.createTextNode(text);
  1067. return fromDom$1(node);
  1068. };
  1069. const fromDom$1 = (node) => {
  1070. // TODO: Consider removing this check, but left atm for safety
  1071. if (node === null || node === undefined) {
  1072. throw new Error('Node cannot be null or undefined');
  1073. }
  1074. return {
  1075. dom: node
  1076. };
  1077. };
  1078. const fromPoint$1 = (docElm, x, y) => Optional.from(docElm.dom.elementFromPoint(x, y)).map(fromDom$1);
  1079. // tslint:disable-next-line:variable-name
  1080. const SugarElement = {
  1081. fromHtml: fromHtml$1,
  1082. fromTag,
  1083. fromText,
  1084. fromDom: fromDom$1,
  1085. fromPoint: fromPoint$1
  1086. };
  1087. const selectNode = (win, element) => {
  1088. const rng = win.document.createRange();
  1089. rng.selectNode(element.dom);
  1090. return rng;
  1091. };
  1092. const selectNodeContents = (win, element) => {
  1093. const rng = win.document.createRange();
  1094. selectNodeContentsUsing(rng, element);
  1095. return rng;
  1096. };
  1097. const selectNodeContentsUsing = (rng, element) => rng.selectNodeContents(element.dom);
  1098. // NOTE: Mutates the range.
  1099. const setStart = (rng, situ) => {
  1100. situ.fold((e) => {
  1101. rng.setStartBefore(e.dom);
  1102. }, (e, o) => {
  1103. rng.setStart(e.dom, o);
  1104. }, (e) => {
  1105. rng.setStartAfter(e.dom);
  1106. });
  1107. };
  1108. const setFinish = (rng, situ) => {
  1109. situ.fold((e) => {
  1110. rng.setEndBefore(e.dom);
  1111. }, (e, o) => {
  1112. rng.setEnd(e.dom, o);
  1113. }, (e) => {
  1114. rng.setEndAfter(e.dom);
  1115. });
  1116. };
  1117. const relativeToNative = (win, startSitu, finishSitu) => {
  1118. const range = win.document.createRange();
  1119. setStart(range, startSitu);
  1120. setFinish(range, finishSitu);
  1121. return range;
  1122. };
  1123. const exactToNative = (win, start, soffset, finish, foffset) => {
  1124. const rng = win.document.createRange();
  1125. rng.setStart(start.dom, soffset);
  1126. rng.setEnd(finish.dom, foffset);
  1127. return rng;
  1128. };
  1129. const toRect = (rect) => ({
  1130. left: rect.left,
  1131. top: rect.top,
  1132. right: rect.right,
  1133. bottom: rect.bottom,
  1134. width: rect.width,
  1135. height: rect.height
  1136. });
  1137. const getFirstRect$1 = (rng) => {
  1138. const rects = rng.getClientRects();
  1139. // ASSUMPTION: The first rectangle is the start of the selection
  1140. const rect = rects.length > 0 ? rects[0] : rng.getBoundingClientRect();
  1141. return rect.width > 0 || rect.height > 0 ? Optional.some(rect).map(toRect) : Optional.none();
  1142. };
  1143. const adt$6 = Adt.generate([
  1144. { ltr: ['start', 'soffset', 'finish', 'foffset'] },
  1145. { rtl: ['start', 'soffset', 'finish', 'foffset'] }
  1146. ]);
  1147. const fromRange = (win, type, range) => type(SugarElement.fromDom(range.startContainer), range.startOffset, SugarElement.fromDom(range.endContainer), range.endOffset);
  1148. const getRanges = (win, selection) => selection.match({
  1149. domRange: (rng) => {
  1150. return {
  1151. ltr: constant(rng),
  1152. rtl: Optional.none
  1153. };
  1154. },
  1155. relative: (startSitu, finishSitu) => {
  1156. return {
  1157. ltr: cached(() => relativeToNative(win, startSitu, finishSitu)),
  1158. rtl: cached(() => Optional.some(relativeToNative(win, finishSitu, startSitu)))
  1159. };
  1160. },
  1161. exact: (start, soffset, finish, foffset) => {
  1162. return {
  1163. ltr: cached(() => exactToNative(win, start, soffset, finish, foffset)),
  1164. rtl: cached(() => Optional.some(exactToNative(win, finish, foffset, start, soffset)))
  1165. };
  1166. }
  1167. });
  1168. const doDiagnose = (win, ranges) => {
  1169. // If we cannot create a ranged selection from start > finish, it could be RTL
  1170. const rng = ranges.ltr();
  1171. if (rng.collapsed) {
  1172. // Let's check if it's RTL ... if it is, then reversing the direction will not be collapsed
  1173. const reversed = ranges.rtl().filter((rev) => rev.collapsed === false);
  1174. return reversed.map((rev) =>
  1175. // We need to use "reversed" here, because the original only has one point (collapsed)
  1176. adt$6.rtl(SugarElement.fromDom(rev.endContainer), rev.endOffset, SugarElement.fromDom(rev.startContainer), rev.startOffset)).getOrThunk(() => fromRange(win, adt$6.ltr, rng));
  1177. }
  1178. else {
  1179. return fromRange(win, adt$6.ltr, rng);
  1180. }
  1181. };
  1182. const diagnose = (win, selection) => {
  1183. const ranges = getRanges(win, selection);
  1184. return doDiagnose(win, ranges);
  1185. };
  1186. const asLtrRange = (win, selection) => {
  1187. const diagnosis = diagnose(win, selection);
  1188. return diagnosis.match({
  1189. ltr: (start, soffset, finish, foffset) => {
  1190. const rng = win.document.createRange();
  1191. rng.setStart(start.dom, soffset);
  1192. rng.setEnd(finish.dom, foffset);
  1193. return rng;
  1194. },
  1195. rtl: (start, soffset, finish, foffset) => {
  1196. // NOTE: Reversing start and finish
  1197. const rng = win.document.createRange();
  1198. rng.setStart(finish.dom, foffset);
  1199. rng.setEnd(start.dom, soffset);
  1200. return rng;
  1201. }
  1202. });
  1203. };
  1204. adt$6.ltr;
  1205. adt$6.rtl;
  1206. const COMMENT = 8;
  1207. const DOCUMENT = 9;
  1208. const DOCUMENT_FRAGMENT = 11;
  1209. const ELEMENT = 1;
  1210. const TEXT = 3;
  1211. const is$1 = (element, selector) => {
  1212. const dom = element.dom;
  1213. if (dom.nodeType !== ELEMENT) {
  1214. return false;
  1215. }
  1216. else {
  1217. const elem = dom;
  1218. if (elem.matches !== undefined) {
  1219. return elem.matches(selector);
  1220. }
  1221. else if (elem.msMatchesSelector !== undefined) {
  1222. return elem.msMatchesSelector(selector);
  1223. }
  1224. else if (elem.webkitMatchesSelector !== undefined) {
  1225. return elem.webkitMatchesSelector(selector);
  1226. }
  1227. else if (elem.mozMatchesSelector !== undefined) {
  1228. // cast to any as mozMatchesSelector doesn't exist in TS DOM lib
  1229. return elem.mozMatchesSelector(selector);
  1230. }
  1231. else {
  1232. throw new Error('Browser lacks native selectors');
  1233. } // unfortunately we can't throw this on startup :(
  1234. }
  1235. };
  1236. const bypassSelector = (dom) =>
  1237. // Only elements, documents and shadow roots support querySelector
  1238. // shadow root element type is DOCUMENT_FRAGMENT
  1239. dom.nodeType !== ELEMENT && dom.nodeType !== DOCUMENT && dom.nodeType !== DOCUMENT_FRAGMENT ||
  1240. // IE fix for complex queries on empty nodes: http://jsfiddle.net/spyder/fv9ptr5L/
  1241. dom.childElementCount === 0;
  1242. const all$1 = (selector, scope) => {
  1243. const base = scope === undefined ? document : scope.dom;
  1244. return bypassSelector(base) ? [] : map$1(base.querySelectorAll(selector), SugarElement.fromDom);
  1245. };
  1246. const one = (selector, scope) => {
  1247. const base = scope === undefined ? document : scope.dom;
  1248. return bypassSelector(base) ? Optional.none() : Optional.from(base.querySelector(selector)).map(SugarElement.fromDom);
  1249. };
  1250. const eq$1 = (e1, e2) => e1.dom === e2.dom;
  1251. // Returns: true if node e1 contains e2, otherwise false.
  1252. // (returns false if e1===e2: A node does not contain itself).
  1253. const contains = (e1, e2) => {
  1254. const d1 = e1.dom;
  1255. const d2 = e2.dom;
  1256. return d1 === d2 ? false : d1.contains(d2);
  1257. };
  1258. const is = is$1;
  1259. const DeviceType = (os, browser, userAgent, mediaMatch) => {
  1260. const isiPad = os.isiOS() && /ipad/i.test(userAgent) === true;
  1261. const isiPhone = os.isiOS() && !isiPad;
  1262. const isMobile = os.isiOS() || os.isAndroid();
  1263. const isTouch = isMobile || mediaMatch('(pointer:coarse)');
  1264. const isTablet = isiPad || !isiPhone && isMobile && mediaMatch('(min-device-width:768px)');
  1265. const isPhone = isiPhone || isMobile && !isTablet;
  1266. const iOSwebview = browser.isSafari() && os.isiOS() && /safari/i.test(userAgent) === false;
  1267. const isDesktop = !isPhone && !isTablet && !iOSwebview;
  1268. return {
  1269. isiPad: constant(isiPad),
  1270. isiPhone: constant(isiPhone),
  1271. isTablet: constant(isTablet),
  1272. isPhone: constant(isPhone),
  1273. isTouch: constant(isTouch),
  1274. isAndroid: os.isAndroid,
  1275. isiOS: os.isiOS,
  1276. isWebView: constant(iOSwebview),
  1277. isDesktop: constant(isDesktop)
  1278. };
  1279. };
  1280. const firstMatch = (regexes, s) => {
  1281. for (let i = 0; i < regexes.length; i++) {
  1282. const x = regexes[i];
  1283. if (x.test(s)) {
  1284. return x;
  1285. }
  1286. }
  1287. return undefined;
  1288. };
  1289. const find = (regexes, agent) => {
  1290. const r = firstMatch(regexes, agent);
  1291. if (!r) {
  1292. return { major: 0, minor: 0 };
  1293. }
  1294. const group = (i) => {
  1295. return Number(agent.replace(r, '$' + i));
  1296. };
  1297. return nu$2(group(1), group(2));
  1298. };
  1299. const detect$5 = (versionRegexes, agent) => {
  1300. const cleanedAgent = String(agent).toLowerCase();
  1301. if (versionRegexes.length === 0) {
  1302. return unknown$2();
  1303. }
  1304. return find(versionRegexes, cleanedAgent);
  1305. };
  1306. const unknown$2 = () => {
  1307. return nu$2(0, 0);
  1308. };
  1309. const nu$2 = (major, minor) => {
  1310. return { major, minor };
  1311. };
  1312. const Version = {
  1313. nu: nu$2,
  1314. detect: detect$5,
  1315. unknown: unknown$2
  1316. };
  1317. const detectBrowser$1 = (browsers, userAgentData) => {
  1318. return findMap(userAgentData.brands, (uaBrand) => {
  1319. const lcBrand = uaBrand.brand.toLowerCase();
  1320. return find$1(browsers, (browser) => { var _a; return lcBrand === ((_a = browser.brand) === null || _a === void 0 ? void 0 : _a.toLowerCase()); })
  1321. .map((info) => ({
  1322. current: info.name,
  1323. version: Version.nu(parseInt(uaBrand.version, 10), 0)
  1324. }));
  1325. });
  1326. };
  1327. const detect$4 = (candidates, userAgent) => {
  1328. const agent = String(userAgent).toLowerCase();
  1329. return find$1(candidates, (candidate) => {
  1330. return candidate.search(agent);
  1331. });
  1332. };
  1333. // They (browser and os) are the same at the moment, but they might
  1334. // not stay that way.
  1335. const detectBrowser = (browsers, userAgent) => {
  1336. return detect$4(browsers, userAgent).map((browser) => {
  1337. const version = Version.detect(browser.versionRegexes, userAgent);
  1338. return {
  1339. current: browser.name,
  1340. version
  1341. };
  1342. });
  1343. };
  1344. const detectOs = (oses, userAgent) => {
  1345. return detect$4(oses, userAgent).map((os) => {
  1346. const version = Version.detect(os.versionRegexes, userAgent);
  1347. return {
  1348. current: os.name,
  1349. version
  1350. };
  1351. });
  1352. };
  1353. const normalVersionRegex = /.*?version\/\ ?([0-9]+)\.([0-9]+).*/;
  1354. const checkContains = (target) => {
  1355. return (uastring) => {
  1356. return contains$1(uastring, target);
  1357. };
  1358. };
  1359. const browsers = [
  1360. // This is legacy Edge
  1361. {
  1362. name: 'Edge',
  1363. versionRegexes: [/.*?edge\/ ?([0-9]+)\.([0-9]+)$/],
  1364. search: (uastring) => {
  1365. return contains$1(uastring, 'edge/') && contains$1(uastring, 'chrome') && contains$1(uastring, 'safari') && contains$1(uastring, 'applewebkit');
  1366. }
  1367. },
  1368. // This is Google Chrome and Chromium Edge
  1369. {
  1370. name: 'Chromium',
  1371. brand: 'Chromium',
  1372. versionRegexes: [/.*?chrome\/([0-9]+)\.([0-9]+).*/, normalVersionRegex],
  1373. search: (uastring) => {
  1374. return contains$1(uastring, 'chrome') && !contains$1(uastring, 'chromeframe');
  1375. }
  1376. },
  1377. {
  1378. name: 'IE',
  1379. versionRegexes: [/.*?msie\ ?([0-9]+)\.([0-9]+).*/, /.*?rv:([0-9]+)\.([0-9]+).*/],
  1380. search: (uastring) => {
  1381. return contains$1(uastring, 'msie') || contains$1(uastring, 'trident');
  1382. }
  1383. },
  1384. // INVESTIGATE: Is this still the Opera user agent?
  1385. {
  1386. name: 'Opera',
  1387. versionRegexes: [normalVersionRegex, /.*?opera\/([0-9]+)\.([0-9]+).*/],
  1388. search: checkContains('opera')
  1389. },
  1390. {
  1391. name: 'Firefox',
  1392. versionRegexes: [/.*?firefox\/\ ?([0-9]+)\.([0-9]+).*/],
  1393. search: checkContains('firefox')
  1394. },
  1395. {
  1396. name: 'Safari',
  1397. versionRegexes: [normalVersionRegex, /.*?cpu os ([0-9]+)_([0-9]+).*/],
  1398. search: (uastring) => {
  1399. return (contains$1(uastring, 'safari') || contains$1(uastring, 'mobile/')) && contains$1(uastring, 'applewebkit');
  1400. }
  1401. }
  1402. ];
  1403. const oses = [
  1404. {
  1405. name: 'Windows',
  1406. search: checkContains('win'),
  1407. versionRegexes: [/.*?windows\ nt\ ?([0-9]+)\.([0-9]+).*/]
  1408. },
  1409. {
  1410. name: 'iOS',
  1411. search: (uastring) => {
  1412. return contains$1(uastring, 'iphone') || contains$1(uastring, 'ipad');
  1413. },
  1414. versionRegexes: [/.*?version\/\ ?([0-9]+)\.([0-9]+).*/, /.*cpu os ([0-9]+)_([0-9]+).*/, /.*cpu iphone os ([0-9]+)_([0-9]+).*/]
  1415. },
  1416. {
  1417. name: 'Android',
  1418. search: checkContains('android'),
  1419. versionRegexes: [/.*?android\ ?([0-9]+)\.([0-9]+).*/]
  1420. },
  1421. {
  1422. name: 'macOS',
  1423. search: checkContains('mac os x'),
  1424. versionRegexes: [/.*?mac\ os\ x\ ?([0-9]+)_([0-9]+).*/]
  1425. },
  1426. {
  1427. name: 'Linux',
  1428. search: checkContains('linux'),
  1429. versionRegexes: []
  1430. },
  1431. { name: 'Solaris',
  1432. search: checkContains('sunos'),
  1433. versionRegexes: []
  1434. },
  1435. {
  1436. name: 'FreeBSD',
  1437. search: checkContains('freebsd'),
  1438. versionRegexes: []
  1439. },
  1440. {
  1441. name: 'ChromeOS',
  1442. search: checkContains('cros'),
  1443. versionRegexes: [/.*?chrome\/([0-9]+)\.([0-9]+).*/]
  1444. }
  1445. ];
  1446. const PlatformInfo = {
  1447. browsers: constant(browsers),
  1448. oses: constant(oses)
  1449. };
  1450. const edge = 'Edge';
  1451. const chromium = 'Chromium';
  1452. const ie = 'IE';
  1453. const opera = 'Opera';
  1454. const firefox = 'Firefox';
  1455. const safari = 'Safari';
  1456. const unknown$1 = () => {
  1457. return nu$1({
  1458. current: undefined,
  1459. version: Version.unknown()
  1460. });
  1461. };
  1462. const nu$1 = (info) => {
  1463. const current = info.current;
  1464. const version = info.version;
  1465. const isBrowser = (name) => () => current === name;
  1466. return {
  1467. current,
  1468. version,
  1469. isEdge: isBrowser(edge),
  1470. isChromium: isBrowser(chromium),
  1471. // NOTE: isIe just looks too weird
  1472. isIE: isBrowser(ie),
  1473. isOpera: isBrowser(opera),
  1474. isFirefox: isBrowser(firefox),
  1475. isSafari: isBrowser(safari)
  1476. };
  1477. };
  1478. const Browser = {
  1479. unknown: unknown$1,
  1480. nu: nu$1,
  1481. edge: constant(edge),
  1482. chromium: constant(chromium),
  1483. ie: constant(ie),
  1484. opera: constant(opera),
  1485. firefox: constant(firefox),
  1486. safari: constant(safari)
  1487. };
  1488. const windows = 'Windows';
  1489. const ios = 'iOS';
  1490. const android = 'Android';
  1491. const linux = 'Linux';
  1492. const macos = 'macOS';
  1493. const solaris = 'Solaris';
  1494. const freebsd = 'FreeBSD';
  1495. const chromeos = 'ChromeOS';
  1496. // Though there is a bit of dupe with this and Browser, trying to
  1497. // reuse code makes it much harder to follow and change.
  1498. const unknown = () => {
  1499. return nu({
  1500. current: undefined,
  1501. version: Version.unknown()
  1502. });
  1503. };
  1504. const nu = (info) => {
  1505. const current = info.current;
  1506. const version = info.version;
  1507. const isOS = (name) => () => current === name;
  1508. return {
  1509. current,
  1510. version,
  1511. isWindows: isOS(windows),
  1512. // TODO: Fix capitalisation
  1513. isiOS: isOS(ios),
  1514. isAndroid: isOS(android),
  1515. isMacOS: isOS(macos),
  1516. isLinux: isOS(linux),
  1517. isSolaris: isOS(solaris),
  1518. isFreeBSD: isOS(freebsd),
  1519. isChromeOS: isOS(chromeos)
  1520. };
  1521. };
  1522. const OperatingSystem = {
  1523. unknown,
  1524. nu,
  1525. windows: constant(windows),
  1526. ios: constant(ios),
  1527. android: constant(android),
  1528. linux: constant(linux),
  1529. macos: constant(macos),
  1530. solaris: constant(solaris),
  1531. freebsd: constant(freebsd),
  1532. chromeos: constant(chromeos)
  1533. };
  1534. const detect$3 = (userAgent, userAgentDataOpt, mediaMatch) => {
  1535. const browsers = PlatformInfo.browsers();
  1536. const oses = PlatformInfo.oses();
  1537. const browser = userAgentDataOpt.bind((userAgentData) => detectBrowser$1(browsers, userAgentData))
  1538. .orThunk(() => detectBrowser(browsers, userAgent))
  1539. .fold(Browser.unknown, Browser.nu);
  1540. const os = detectOs(oses, userAgent).fold(OperatingSystem.unknown, OperatingSystem.nu);
  1541. const deviceType = DeviceType(os, browser, userAgent, mediaMatch);
  1542. return {
  1543. browser,
  1544. os,
  1545. deviceType
  1546. };
  1547. };
  1548. const PlatformDetection = {
  1549. detect: detect$3
  1550. };
  1551. const mediaMatch = (query) => window.matchMedia(query).matches;
  1552. // IMPORTANT: Must be in a thunk, otherwise rollup thinks calling this immediately
  1553. // causes side effects and won't tree shake this away
  1554. // Note: navigator.userAgentData is not part of the native typescript types yet
  1555. let platform = cached(() => PlatformDetection.detect(window.navigator.userAgent, Optional.from((window.navigator.userAgentData)), mediaMatch));
  1556. const detect$2 = () => platform();
  1557. const unsafe = (name, scope) => {
  1558. return resolve$2(name, scope);
  1559. };
  1560. const getOrDie = (name, scope) => {
  1561. const actual = unsafe(name, scope);
  1562. if (actual === undefined || actual === null) {
  1563. throw new Error(name + ' not available on this browser');
  1564. }
  1565. return actual;
  1566. };
  1567. const getPrototypeOf = Object.getPrototypeOf;
  1568. /*
  1569. * IE9 and above
  1570. *
  1571. * MDN no use on this one, but here's the link anyway:
  1572. * https://developer.mozilla.org/en/docs/Web/API/HTMLElement
  1573. */
  1574. const sandHTMLElement = (scope) => {
  1575. return getOrDie('HTMLElement', scope);
  1576. };
  1577. const isPrototypeOf = (x) => {
  1578. // use Resolve to get the window object for x and just return undefined if it can't find it.
  1579. // undefined scope later triggers using the global window.
  1580. const scope = resolve$2('ownerDocument.defaultView', x);
  1581. // TINY-7374: We can't rely on looking at the owner window HTMLElement as the element may have
  1582. // been constructed in a different window and then appended to the current window document.
  1583. return isObject(x) && (sandHTMLElement(scope).prototype.isPrototypeOf(x) || /^HTML\w*Element$/.test(getPrototypeOf(x).constructor.name));
  1584. };
  1585. const name = (element) => {
  1586. const r = element.dom.nodeName;
  1587. return r.toLowerCase();
  1588. };
  1589. const type = (element) => element.dom.nodeType;
  1590. const isType = (t) => (element) => type(element) === t;
  1591. const isComment = (element) => type(element) === COMMENT || name(element) === '#comment';
  1592. const isHTMLElement = (element) => isElement(element) && isPrototypeOf(element.dom);
  1593. const isElement = isType(ELEMENT);
  1594. const isText = isType(TEXT);
  1595. const isDocument = isType(DOCUMENT);
  1596. const isDocumentFragment = isType(DOCUMENT_FRAGMENT);
  1597. const isTag = (tag) => (e) => isElement(e) && name(e) === tag;
  1598. /**
  1599. * The document associated with the current element
  1600. * NOTE: this will throw if the owner is null.
  1601. */
  1602. const owner = (element) => SugarElement.fromDom(element.dom.ownerDocument);
  1603. /**
  1604. * If the element is a document, return it. Otherwise, return its ownerDocument.
  1605. * @param dos
  1606. */
  1607. const documentOrOwner = (dos) => isDocument(dos) ? dos : owner(dos);
  1608. const documentElement = (element) => SugarElement.fromDom(documentOrOwner(element).dom.documentElement);
  1609. /**
  1610. * The window element associated with the element
  1611. * NOTE: this will throw if the defaultView is null.
  1612. */
  1613. const defaultView = (element) => SugarElement.fromDom(documentOrOwner(element).dom.defaultView);
  1614. const parent = (element) => Optional.from(element.dom.parentNode).map(SugarElement.fromDom);
  1615. const parentElement = (element) => Optional.from(element.dom.parentElement).map(SugarElement.fromDom);
  1616. const parents = (element, isRoot) => {
  1617. const stop = isFunction(isRoot) ? isRoot : never;
  1618. // This is used a *lot* so it needs to be performant, not recursive
  1619. let dom = element.dom;
  1620. const ret = [];
  1621. while (dom.parentNode !== null && dom.parentNode !== undefined) {
  1622. const rawParent = dom.parentNode;
  1623. const p = SugarElement.fromDom(rawParent);
  1624. ret.push(p);
  1625. if (stop(p) === true) {
  1626. break;
  1627. }
  1628. else {
  1629. dom = rawParent;
  1630. }
  1631. }
  1632. return ret;
  1633. };
  1634. const prevSibling = (element) => Optional.from(element.dom.previousSibling).map(SugarElement.fromDom);
  1635. const nextSibling = (element) => Optional.from(element.dom.nextSibling).map(SugarElement.fromDom);
  1636. const children$2 = (element) => map$1(element.dom.childNodes, SugarElement.fromDom);
  1637. const child$2 = (element, index) => {
  1638. const cs = element.dom.childNodes;
  1639. return Optional.from(cs[index]).map(SugarElement.fromDom);
  1640. };
  1641. const firstChild = (element) => child$2(element, 0);
  1642. const makeRange = (start, soffset, finish, foffset) => {
  1643. const doc = owner(start);
  1644. // TODO: We need to think about a better place to put native range creation code. Does it even belong in sugar?
  1645. // Could the `Compare` checks (node.compareDocumentPosition) handle these situations better?
  1646. const rng = doc.dom.createRange();
  1647. rng.setStart(start.dom, soffset);
  1648. rng.setEnd(finish.dom, foffset);
  1649. return rng;
  1650. };
  1651. const after$5 = (start, soffset, finish, foffset) => {
  1652. const r = makeRange(start, soffset, finish, foffset);
  1653. const same = eq$1(start, finish) && soffset === foffset;
  1654. return r.collapsed && !same;
  1655. };
  1656. /**
  1657. * Is the element a ShadowRoot?
  1658. *
  1659. * Note: this is insufficient to test if any element is a shadow root, but it is sufficient to differentiate between
  1660. * a Document and a ShadowRoot.
  1661. */
  1662. const isShadowRoot = (dos) => isDocumentFragment(dos) && isNonNullable(dos.dom.host);
  1663. const getRootNode = (e) => SugarElement.fromDom(e.dom.getRootNode());
  1664. /** Where content needs to go. ShadowRoot or document body */
  1665. const getContentContainer = (dos) =>
  1666. // Can't use SugarBody.body without causing a circular module reference (since SugarBody.inBody uses SugarShadowDom)
  1667. isShadowRoot(dos) ? dos : SugarElement.fromDom(documentOrOwner(dos).dom.body);
  1668. /** If this element is in a ShadowRoot, return it. */
  1669. const getShadowRoot = (e) => {
  1670. const r = getRootNode(e);
  1671. return isShadowRoot(r) ? Optional.some(r) : Optional.none();
  1672. };
  1673. /** Return the host of a ShadowRoot.
  1674. *
  1675. * This function will throw if Shadow DOM is unsupported in the browser, or if the host is null.
  1676. * If you actually have a ShadowRoot, this shouldn't happen.
  1677. */
  1678. const getShadowHost = (e) => SugarElement.fromDom(e.dom.host);
  1679. /**
  1680. * When Events bubble up through a ShadowRoot, the browser changes the target to be the shadow host.
  1681. * This function gets the "original" event target if possible.
  1682. * This only works if the shadow tree is open - if the shadow tree is closed, event.target is returned.
  1683. * See: https://developers.google.com/web/fundamentals/web-components/shadowdom#events
  1684. */
  1685. const getOriginalEventTarget = (event) => {
  1686. if (isNonNullable(event.target)) {
  1687. const el = SugarElement.fromDom(event.target);
  1688. if (isElement(el) && isOpenShadowHost(el)) {
  1689. // When target element is inside Shadow DOM we need to take first element from composedPath
  1690. // otherwise we'll get Shadow Root parent, not actual target element.
  1691. if (event.composed && event.composedPath) {
  1692. const composedPath = event.composedPath();
  1693. if (composedPath) {
  1694. return head(composedPath);
  1695. }
  1696. }
  1697. }
  1698. }
  1699. return Optional.from(event.target);
  1700. };
  1701. /** Return true if the element is a host of an open shadow root.
  1702. * Return false if the element is a host of a closed shadow root, or if the element is not a host.
  1703. */
  1704. const isOpenShadowHost = (element) => isNonNullable(element.dom.shadowRoot);
  1705. const mkEvent = (target, x, y, stop, prevent, kill, raw) => ({
  1706. target,
  1707. x,
  1708. y,
  1709. stop,
  1710. prevent,
  1711. kill,
  1712. raw
  1713. });
  1714. /** Wraps an Event in an EventArgs structure.
  1715. * The returned EventArgs structure has its target set to the "original" target if possible.
  1716. * See SugarShadowDom.getOriginalEventTarget
  1717. */
  1718. const fromRawEvent$1 = (rawEvent) => {
  1719. const target = SugarElement.fromDom(getOriginalEventTarget(rawEvent).getOr(rawEvent.target));
  1720. const stop = () => rawEvent.stopPropagation();
  1721. const prevent = () => rawEvent.preventDefault();
  1722. const kill = compose(prevent, stop); // more of a sequence than a compose, but same effect
  1723. // FIX: Don't just expose the raw event. Need to identify what needs standardisation.
  1724. return mkEvent(target, rawEvent.clientX, rawEvent.clientY, stop, prevent, kill, rawEvent);
  1725. };
  1726. const handle$1 = (filter, handler) => (rawEvent) => {
  1727. if (filter(rawEvent)) {
  1728. handler(fromRawEvent$1(rawEvent));
  1729. }
  1730. };
  1731. const binder = (element, event, filter, handler, useCapture) => {
  1732. const wrapped = handle$1(filter, handler);
  1733. // IE9 minimum
  1734. element.dom.addEventListener(event, wrapped, useCapture);
  1735. return {
  1736. unbind: curry(unbind, element, event, wrapped, useCapture)
  1737. };
  1738. };
  1739. const bind$1 = (element, event, filter, handler) => binder(element, event, filter, handler, false);
  1740. const unbind = (element, event, handler, useCapture) => {
  1741. // IE9 minimum
  1742. element.dom.removeEventListener(event, handler, useCapture);
  1743. };
  1744. const filter = always; // no filter on plain DomEvents
  1745. const bind = (element, event, handler) => bind$1(element, event, filter, handler);
  1746. const fromRawEvent = fromRawEvent$1;
  1747. const before$3 = (marker, element) => {
  1748. const parent$1 = parent(marker);
  1749. parent$1.each((v) => {
  1750. v.dom.insertBefore(element.dom, marker.dom);
  1751. });
  1752. };
  1753. const after$4 = (marker, element) => {
  1754. const sibling = nextSibling(marker);
  1755. sibling.fold(() => {
  1756. const parent$1 = parent(marker);
  1757. parent$1.each((v) => {
  1758. append$1(v, element);
  1759. });
  1760. }, (v) => {
  1761. before$3(v, element);
  1762. });
  1763. };
  1764. const prepend = (parent, element) => {
  1765. const firstChild$1 = firstChild(parent);
  1766. firstChild$1.fold(() => {
  1767. append$1(parent, element);
  1768. }, (v) => {
  1769. parent.dom.insertBefore(element.dom, v.dom);
  1770. });
  1771. };
  1772. const append$1 = (parent, element) => {
  1773. parent.dom.appendChild(element.dom);
  1774. };
  1775. const appendAt = (parent, element, index) => {
  1776. child$2(parent, index).fold(() => {
  1777. append$1(parent, element);
  1778. }, (v) => {
  1779. before$3(v, element);
  1780. });
  1781. };
  1782. const wrap = (element, wrapper) => {
  1783. before$3(element, wrapper);
  1784. append$1(wrapper, element);
  1785. };
  1786. const after$3 = (marker, elements) => {
  1787. each$2(elements, (x, i) => {
  1788. const e = i === 0 ? marker : elements[i - 1];
  1789. after$4(e, x);
  1790. });
  1791. };
  1792. const append = (parent, elements) => {
  1793. each$2(elements, (x) => {
  1794. append$1(parent, x);
  1795. });
  1796. };
  1797. const rawSet = (dom, key, value) => {
  1798. /*
  1799. * JQuery coerced everything to a string, and silently did nothing on text node/null/undefined.
  1800. *
  1801. * We fail on those invalid cases, only allowing numbers and booleans.
  1802. */
  1803. if (isString(value) || isBoolean(value) || isNumber(value)) {
  1804. dom.setAttribute(key, value + '');
  1805. }
  1806. else {
  1807. // eslint-disable-next-line no-console
  1808. console.error('Invalid call to Attribute.set. Key ', key, ':: Value ', value, ':: Element ', dom);
  1809. throw new Error('Attribute value was not simple');
  1810. }
  1811. };
  1812. const set$2 = (element, key, value) => {
  1813. rawSet(element.dom, key, value);
  1814. };
  1815. const setAll$1 = (element, attrs) => {
  1816. const dom = element.dom;
  1817. each$1(attrs, (v, k) => {
  1818. rawSet(dom, k, v);
  1819. });
  1820. };
  1821. const setOptions = (element, attrs) => {
  1822. each$1(attrs, (v, k) => {
  1823. v.fold(() => {
  1824. remove$6(element, k);
  1825. }, (value) => {
  1826. rawSet(element.dom, k, value);
  1827. });
  1828. });
  1829. };
  1830. const get$b = (element, key) => {
  1831. const v = element.dom.getAttribute(key);
  1832. // undefined is the more appropriate value for JS, and this matches JQuery
  1833. return v === null ? undefined : v;
  1834. };
  1835. const getOpt = (element, key) => Optional.from(get$b(element, key));
  1836. const remove$6 = (element, key) => {
  1837. element.dom.removeAttribute(key);
  1838. };
  1839. const clone$1 = (element) => foldl(element.dom.attributes, (acc, attr) => {
  1840. acc[attr.name] = attr.value;
  1841. return acc;
  1842. }, {});
  1843. const empty = (element) => {
  1844. // shortcut "empty node" trick. Requires IE 9.
  1845. element.dom.textContent = '';
  1846. // If the contents was a single empty text node, the above doesn't remove it. But, it's still faster in general
  1847. // than removing every child node manually.
  1848. // The following is (probably) safe for performance as 99.9% of the time the trick works and
  1849. // Traverse.children will return an empty array.
  1850. each$2(children$2(element), (rogue) => {
  1851. remove$5(rogue);
  1852. });
  1853. };
  1854. const remove$5 = (element) => {
  1855. const dom = element.dom;
  1856. if (dom.parentNode !== null) {
  1857. dom.parentNode.removeChild(dom);
  1858. }
  1859. };
  1860. const unwrap = (wrapper) => {
  1861. const children = children$2(wrapper);
  1862. if (children.length > 0) {
  1863. after$3(wrapper, children);
  1864. }
  1865. remove$5(wrapper);
  1866. };
  1867. const clone = (original, isDeep) => SugarElement.fromDom(original.dom.cloneNode(isDeep));
  1868. /** Shallow clone - just the tag, no children */
  1869. const shallow = (original) => clone(original, false);
  1870. /** Deep clone - everything copied including children */
  1871. const deep = (original) => clone(original, true);
  1872. /** Shallow clone, with a new tag */
  1873. const shallowAs = (original, tag) => {
  1874. const nu = SugarElement.fromTag(tag);
  1875. const attributes = clone$1(original);
  1876. setAll$1(nu, attributes);
  1877. return nu;
  1878. };
  1879. /** Deep clone, with a new tag */
  1880. const copy$2 = (original, tag) => {
  1881. const nu = shallowAs(original, tag);
  1882. // NOTE
  1883. // previously this used serialisation:
  1884. // nu.dom.innerHTML = original.dom.innerHTML;
  1885. //
  1886. // Clone should be equivalent (and faster), but if TD <-> TH toggle breaks, put it back.
  1887. const cloneChildren = children$2(deep(original));
  1888. append(nu, cloneChildren);
  1889. return nu;
  1890. };
  1891. /** Change the tag name, but keep all children */
  1892. const mutate$1 = (original, tag) => {
  1893. const nu = shallowAs(original, tag);
  1894. after$4(original, nu);
  1895. const children = children$2(original);
  1896. append(nu, children);
  1897. remove$5(original);
  1898. return nu;
  1899. };
  1900. const fromHtml = (html, scope) => {
  1901. const doc = scope || document;
  1902. const div = doc.createElement('div');
  1903. div.innerHTML = html;
  1904. return children$2(SugarElement.fromDom(div));
  1905. };
  1906. const fromDom = (nodes) => map$1(nodes, SugarElement.fromDom);
  1907. const get$a = (element) => element.dom.innerHTML;
  1908. const getOuter$2 = (element) => {
  1909. const container = SugarElement.fromTag('div');
  1910. const clone = SugarElement.fromDom(element.dom.cloneNode(true));
  1911. append$1(container, clone);
  1912. return get$a(container);
  1913. };
  1914. // some elements, such as mathml, don't have style attributes
  1915. // others, such as angular elements, have style attributes that aren't a CSSStyleDeclaration
  1916. const isSupported = (dom) => dom.style !== undefined && isFunction(dom.style.getPropertyValue);
  1917. // Node.contains() is very, very, very good performance
  1918. // http://jsperf.com/closest-vs-contains/5
  1919. const inBody = (element) => {
  1920. // Technically this is only required on IE, where contains() returns false for text nodes.
  1921. // But it's cheap enough to run everywhere and Sugar doesn't have platform detection (yet).
  1922. const dom = isText(element) ? element.dom.parentNode : element.dom;
  1923. // use ownerDocument.body to ensure this works inside iframes.
  1924. // Normally contains is bad because an element "contains" itself, but here we want that.
  1925. if (dom === undefined || dom === null || dom.ownerDocument === null) {
  1926. return false;
  1927. }
  1928. const doc = dom.ownerDocument;
  1929. return getShadowRoot(SugarElement.fromDom(dom)).fold(() => doc.body.contains(dom), compose1(inBody, getShadowHost));
  1930. };
  1931. const getBody$1 = (doc) => {
  1932. const b = doc.dom.body;
  1933. if (b === null || b === undefined) {
  1934. throw new Error('Body is not available yet');
  1935. }
  1936. return SugarElement.fromDom(b);
  1937. };
  1938. const internalSet = (dom, property, value) => {
  1939. // This is going to hurt. Apologies.
  1940. // JQuery coerces numbers to pixels for certain property names, and other times lets numbers through.
  1941. // we're going to be explicit; strings only.
  1942. if (!isString(value)) {
  1943. // eslint-disable-next-line no-console
  1944. console.error('Invalid call to CSS.set. Property ', property, ':: Value ', value, ':: Element ', dom);
  1945. throw new Error('CSS value must be a string: ' + value);
  1946. }
  1947. // removed: support for dom().style[property] where prop is camel case instead of normal property name
  1948. if (isSupported(dom)) {
  1949. dom.style.setProperty(property, value);
  1950. }
  1951. };
  1952. const internalRemove = (dom, property) => {
  1953. /*
  1954. * IE9 and above - MDN doesn't have details, but here's a couple of random internet claims
  1955. *
  1956. * http://help.dottoro.com/ljopsjck.php
  1957. * http://stackoverflow.com/a/7901886/7546
  1958. */
  1959. if (isSupported(dom)) {
  1960. dom.style.removeProperty(property);
  1961. }
  1962. };
  1963. const set$1 = (element, property, value) => {
  1964. const dom = element.dom;
  1965. internalSet(dom, property, value);
  1966. };
  1967. const setAll = (element, css) => {
  1968. const dom = element.dom;
  1969. each$1(css, (v, k) => {
  1970. internalSet(dom, k, v);
  1971. });
  1972. };
  1973. /*
  1974. * NOTE: For certain properties, this returns the "used value" which is subtly different to the "computed value" (despite calling getComputedStyle).
  1975. * Blame CSS 2.0.
  1976. *
  1977. * https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
  1978. */
  1979. const get$9 = (element, property) => {
  1980. const dom = element.dom;
  1981. /*
  1982. * IE9 and above per
  1983. * https://developer.mozilla.org/en/docs/Web/API/window.getComputedStyle
  1984. *
  1985. * Not in numerosity, because it doesn't memoize and looking this up dynamically in performance critical code would be horrendous.
  1986. *
  1987. * JQuery has some magic here for IE popups, but we don't really need that.
  1988. * It also uses element.ownerDocument.defaultView to handle iframes but that hasn't been required since FF 3.6.
  1989. */
  1990. const styles = window.getComputedStyle(dom);
  1991. const r = styles.getPropertyValue(property);
  1992. // jquery-ism: If r is an empty string, check that the element is not in a document. If it isn't, return the raw value.
  1993. // Turns out we do this a lot.
  1994. return (r === '' && !inBody(element)) ? getUnsafeProperty(dom, property) : r;
  1995. };
  1996. // removed: support for dom().style[property] where prop is camel case instead of normal property name
  1997. // empty string is what the browsers (IE11 and Chrome) return when the propertyValue doesn't exists.
  1998. const getUnsafeProperty = (dom, property) => isSupported(dom) ? dom.style.getPropertyValue(property) : '';
  1999. /*
  2000. * Gets the raw value from the style attribute. Useful for retrieving "used values" from the DOM:
  2001. * https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
  2002. *
  2003. * Returns NONE if the property isn't set, or the value is an empty string.
  2004. */
  2005. const getRaw$2 = (element, property) => {
  2006. const dom = element.dom;
  2007. const raw = getUnsafeProperty(dom, property);
  2008. return Optional.from(raw).filter((r) => r.length > 0);
  2009. };
  2010. const remove$4 = (element, property) => {
  2011. const dom = element.dom;
  2012. internalRemove(dom, property);
  2013. if (is$2(getOpt(element, 'style').map(trim), '')) {
  2014. // No more styles left, remove the style attribute as well
  2015. remove$6(element, 'style');
  2016. }
  2017. };
  2018. const copy$1 = (source, target) => {
  2019. const sourceDom = source.dom;
  2020. const targetDom = target.dom;
  2021. if (isSupported(sourceDom) && isSupported(targetDom)) {
  2022. targetDom.style.cssText = sourceDom.style.cssText;
  2023. }
  2024. };
  2025. const Dimension = (name, getOffset) => {
  2026. const set = (element, h) => {
  2027. if (!isNumber(h) && !h.match(/^[0-9]+$/)) {
  2028. throw new Error(name + '.set accepts only positive integer values. Value was ' + h);
  2029. }
  2030. const dom = element.dom;
  2031. if (isSupported(dom)) {
  2032. dom.style[name] = h + 'px';
  2033. }
  2034. };
  2035. /*
  2036. * jQuery supports querying width and height on the document and window objects.
  2037. *
  2038. * TBIO doesn't do this, so the code is removed to save space, but left here just in case.
  2039. */
  2040. /*
  2041. var getDocumentWidth = (element) => {
  2042. var dom = element.dom;
  2043. if (Node.isDocument(element)) {
  2044. var body = dom.body;
  2045. var doc = dom.documentElement;
  2046. return Math.max(
  2047. body.scrollHeight,
  2048. doc.scrollHeight,
  2049. body.offsetHeight,
  2050. doc.offsetHeight,
  2051. doc.clientHeight
  2052. );
  2053. }
  2054. };
  2055. var getWindowWidth = (element) => {
  2056. var dom = element.dom;
  2057. if (dom.window === dom) {
  2058. // There is no offsetHeight on a window, so use the clientHeight of the document
  2059. return dom.document.documentElement.clientHeight;
  2060. }
  2061. };
  2062. */
  2063. const get = (element) => {
  2064. const r = getOffset(element);
  2065. // zero or null means non-standard or disconnected, fall back to CSS
  2066. if (r <= 0 || r === null) {
  2067. const css = get$9(element, name);
  2068. // ugh this feels dirty, but it saves cycles
  2069. return parseFloat(css) || 0;
  2070. }
  2071. return r;
  2072. };
  2073. // in jQuery, getOuter replicates (or uses) box-sizing: border-box calculations
  2074. // although these calculations only seem relevant for quirks mode, and edge cases TBIO doesn't rely on
  2075. const getOuter = get;
  2076. const aggregate = (element, properties) => foldl(properties, (acc, property) => {
  2077. const val = get$9(element, property);
  2078. const value = val === undefined ? 0 : parseInt(val, 10);
  2079. return isNaN(value) ? acc : acc + value;
  2080. }, 0);
  2081. const max = (element, value, properties) => {
  2082. const cumulativeInclusions = aggregate(element, properties);
  2083. // if max-height is 100px and your cumulativeInclusions is 150px, there is no way max-height can be 100px, so we return 0.
  2084. const absoluteMax = value > cumulativeInclusions ? value - cumulativeInclusions : 0;
  2085. return absoluteMax;
  2086. };
  2087. return {
  2088. set,
  2089. get,
  2090. getOuter,
  2091. aggregate,
  2092. max
  2093. };
  2094. };
  2095. const toNumber = (px, fallback) => toFloat(px).getOr(fallback);
  2096. const getProp = (element, name, fallback) => toNumber(get$9(element, name), fallback);
  2097. const calcContentBoxSize = (element, size, upper, lower) => {
  2098. const paddingUpper = getProp(element, `padding-${upper}`, 0);
  2099. const paddingLower = getProp(element, `padding-${lower}`, 0);
  2100. const borderUpper = getProp(element, `border-${upper}-width`, 0);
  2101. const borderLower = getProp(element, `border-${lower}-width`, 0);
  2102. return size - paddingUpper - paddingLower - borderUpper - borderLower;
  2103. };
  2104. const getCalculatedWidth = (element, boxSizing) => {
  2105. const dom = element.dom;
  2106. const width = dom.getBoundingClientRect().width || dom.offsetWidth;
  2107. return boxSizing === 'border-box' ? width : calcContentBoxSize(element, width, 'left', 'right');
  2108. };
  2109. const getHeight$1 = (element) => getProp(element, 'height', element.dom.offsetHeight);
  2110. const getWidth = (element) => getProp(element, 'width', element.dom.offsetWidth);
  2111. const getInnerWidth = (element) => getCalculatedWidth(element, 'content-box');
  2112. const api$2 = Dimension('height', (element) => {
  2113. // getBoundingClientRect gives better results than offsetHeight for tables with captions on Firefox
  2114. const dom = element.dom;
  2115. return inBody(element) ? dom.getBoundingClientRect().height : dom.offsetHeight;
  2116. });
  2117. const get$8 = (element) => api$2.get(element);
  2118. const getOuter$1 = (element) => api$2.getOuter(element);
  2119. const getRuntime$1 = getHeight$1;
  2120. const api$1 = Dimension('width', (element) => {
  2121. const dom = element.dom;
  2122. return inBody(element) ? dom.getBoundingClientRect().width : dom.offsetWidth;
  2123. });
  2124. const get$7 = (element) => api$1.get(element);
  2125. const getOuter = (element) => api$1.getOuter(element);
  2126. const getInner = getInnerWidth;
  2127. const getRuntime = getWidth;
  2128. const r = (left, top) => {
  2129. const translate = (x, y) => r(left + x, top + y);
  2130. return {
  2131. left,
  2132. top,
  2133. translate
  2134. };
  2135. };
  2136. // tslint:disable-next-line:variable-name
  2137. const SugarPosition = r;
  2138. const boxPosition = (dom) => {
  2139. const box = dom.getBoundingClientRect();
  2140. return SugarPosition(box.left, box.top);
  2141. };
  2142. // Avoids falsy false fallthrough
  2143. const firstDefinedOrZero = (a, b) => {
  2144. if (a !== undefined) {
  2145. return a;
  2146. }
  2147. else {
  2148. return b !== undefined ? b : 0;
  2149. }
  2150. };
  2151. const absolute = (element) => {
  2152. const doc = element.dom.ownerDocument;
  2153. const body = doc.body;
  2154. const win = doc.defaultView;
  2155. const html = doc.documentElement;
  2156. if (body === element.dom) {
  2157. return SugarPosition(body.offsetLeft, body.offsetTop);
  2158. }
  2159. const scrollTop = firstDefinedOrZero(win === null || win === void 0 ? void 0 : win.pageYOffset, html.scrollTop);
  2160. const scrollLeft = firstDefinedOrZero(win === null || win === void 0 ? void 0 : win.pageXOffset, html.scrollLeft);
  2161. const clientTop = firstDefinedOrZero(html.clientTop, body.clientTop);
  2162. const clientLeft = firstDefinedOrZero(html.clientLeft, body.clientLeft);
  2163. return viewport(element).translate(scrollLeft - clientLeft, scrollTop - clientTop);
  2164. };
  2165. const viewport = (element) => {
  2166. const dom = element.dom;
  2167. const doc = dom.ownerDocument;
  2168. const body = doc.body;
  2169. if (body === dom) {
  2170. return SugarPosition(body.offsetLeft, body.offsetTop);
  2171. }
  2172. if (!inBody(element)) {
  2173. return SugarPosition(0, 0);
  2174. }
  2175. return boxPosition(dom);
  2176. };
  2177. // get scroll position (x,y) relative to document _doc (or global if not supplied)
  2178. const get$6 = (_DOC) => {
  2179. const doc = _DOC !== undefined ? _DOC.dom : document;
  2180. // ASSUMPTION: This is for cross-browser support, body works for Safari & EDGE, and when we have an iframe body scroller
  2181. const x = doc.body.scrollLeft || doc.documentElement.scrollLeft;
  2182. const y = doc.body.scrollTop || doc.documentElement.scrollTop;
  2183. return SugarPosition(x, y);
  2184. };
  2185. // Scroll content by (x,y) relative to document _doc (or global if not supplied)
  2186. const by = (x, y, _DOC) => {
  2187. const doc = _DOC !== undefined ? _DOC.dom : document;
  2188. const win = doc.defaultView;
  2189. if (win) {
  2190. win.scrollBy(x, y);
  2191. }
  2192. };
  2193. const NodeValue = (is, name) => {
  2194. const get = (element) => {
  2195. if (!is(element)) {
  2196. throw new Error('Can only get ' + name + ' value of a ' + name + ' node');
  2197. }
  2198. return getOption(element).getOr('');
  2199. };
  2200. const getOption = (element) => is(element) ? Optional.from(element.dom.nodeValue) : Optional.none();
  2201. const set = (element, value) => {
  2202. if (!is(element)) {
  2203. throw new Error('Can only set raw ' + name + ' value of a ' + name + ' node');
  2204. }
  2205. element.dom.nodeValue = value;
  2206. };
  2207. return {
  2208. get,
  2209. getOption,
  2210. set
  2211. };
  2212. };
  2213. const api = NodeValue(isText, 'text');
  2214. const get$5 = (element) => api.get(element);
  2215. const getOption = (element) => api.getOption(element);
  2216. const set = (element, value) => api.set(element, value);
  2217. const onDirection = (isLtr, isRtl) => (element) => getDirection(element) === 'rtl' ? isRtl : isLtr;
  2218. const getDirection = (element) => get$9(element, 'direction') === 'rtl' ? 'rtl' : 'ltr';
  2219. // Methods for handling attributes that contain a list of values <div foo="alpha beta theta">
  2220. const read = (element, attr) => {
  2221. const value = get$b(element, attr);
  2222. return value === undefined || value === '' ? [] : value.split(' ');
  2223. };
  2224. const add$3 = (element, attr, id) => {
  2225. const old = read(element, attr);
  2226. const nu = old.concat([id]);
  2227. set$2(element, attr, nu.join(' '));
  2228. return true;
  2229. };
  2230. const remove$3 = (element, attr, id) => {
  2231. const nu = filter$2(read(element, attr), (v) => v !== id);
  2232. if (nu.length > 0) {
  2233. set$2(element, attr, nu.join(' '));
  2234. }
  2235. else {
  2236. remove$6(element, attr);
  2237. }
  2238. return false;
  2239. };
  2240. var ClosestOrAncestor = (is, ancestor, scope, a, isRoot) => {
  2241. if (is(scope, a)) {
  2242. return Optional.some(scope);
  2243. }
  2244. else if (isFunction(isRoot) && isRoot(scope)) {
  2245. return Optional.none();
  2246. }
  2247. else {
  2248. return ancestor(scope, a, isRoot);
  2249. }
  2250. };
  2251. const ancestor$2 = (scope, predicate, isRoot) => {
  2252. let element = scope.dom;
  2253. const stop = isFunction(isRoot) ? isRoot : never;
  2254. while (element.parentNode) {
  2255. element = element.parentNode;
  2256. const el = SugarElement.fromDom(element);
  2257. if (predicate(el)) {
  2258. return Optional.some(el);
  2259. }
  2260. else if (stop(el)) {
  2261. break;
  2262. }
  2263. }
  2264. return Optional.none();
  2265. };
  2266. const closest$2 = (scope, predicate, isRoot) => {
  2267. // This is required to avoid ClosestOrAncestor passing the predicate to itself
  2268. const is = (s, test) => test(s);
  2269. return ClosestOrAncestor(is, ancestor$2, scope, predicate, isRoot);
  2270. };
  2271. const child$1 = (scope, predicate) => {
  2272. const pred = (node) => predicate(SugarElement.fromDom(node));
  2273. const result = find$1(scope.dom.childNodes, pred);
  2274. return result.map(SugarElement.fromDom);
  2275. };
  2276. const descendant$1 = (scope, predicate) => {
  2277. const descend = (node) => {
  2278. // tslint:disable-next-line:prefer-for-of
  2279. for (let i = 0; i < node.childNodes.length; i++) {
  2280. const child = SugarElement.fromDom(node.childNodes[i]);
  2281. if (predicate(child)) {
  2282. return Optional.some(child);
  2283. }
  2284. const res = descend(node.childNodes[i]);
  2285. if (res.isSome()) {
  2286. return res;
  2287. }
  2288. }
  2289. return Optional.none();
  2290. };
  2291. return descend(scope.dom);
  2292. };
  2293. const ancestor$1 = (scope, selector, isRoot) => ancestor$2(scope, (e) => is$1(e, selector), isRoot);
  2294. const child = (scope, selector) => child$1(scope, (e) => is$1(e, selector));
  2295. const descendant = (scope, selector) => one(selector, scope);
  2296. // Returns Some(closest ancestor element (sugared)) matching 'selector' up to isRoot, or None() otherwise
  2297. const closest$1 = (scope, selector, isRoot) => {
  2298. const is = (element, selector) => is$1(element, selector);
  2299. return ClosestOrAncestor(is, ancestor$1, scope, selector, isRoot);
  2300. };
  2301. // IE11 Can return undefined for a classList on elements such as math, so we make sure it's not undefined before attempting to use it.
  2302. const supports = (element) => element.dom.classList !== undefined;
  2303. const get$4 = (element) => read(element, 'class');
  2304. const add$2 = (element, clazz) => add$3(element, 'class', clazz);
  2305. const remove$2 = (element, clazz) => remove$3(element, 'class', clazz);
  2306. /*
  2307. * ClassList is IE10 minimum:
  2308. * https://developer.mozilla.org/en-US/docs/Web/API/Element.classList
  2309. *
  2310. * Note that IE doesn't support the second argument to toggle (at all).
  2311. * If it did, the toggler could be better.
  2312. */
  2313. const add$1 = (element, clazz) => {
  2314. if (supports(element)) {
  2315. element.dom.classList.add(clazz);
  2316. }
  2317. else {
  2318. add$2(element, clazz);
  2319. }
  2320. };
  2321. const cleanClass = (element) => {
  2322. const classList = supports(element) ? element.dom.classList : get$4(element);
  2323. // classList is a "live list", so this is up to date already
  2324. if (classList.length === 0) {
  2325. // No more classes left, remove the class attribute as well
  2326. remove$6(element, 'class');
  2327. }
  2328. };
  2329. const remove$1 = (element, clazz) => {
  2330. if (supports(element)) {
  2331. const classList = element.dom.classList;
  2332. classList.remove(clazz);
  2333. }
  2334. else {
  2335. remove$2(element, clazz);
  2336. }
  2337. cleanClass(element);
  2338. };
  2339. const has = (element, clazz) => supports(element) && element.dom.classList.contains(clazz);
  2340. const remove = (element, classes) => {
  2341. each$2(classes, (x) => {
  2342. remove$1(element, x);
  2343. });
  2344. };
  2345. const closest = (target) => closest$1(target, '[contenteditable]');
  2346. const isEditable$1 = (element, assumeEditable = false) => {
  2347. if (inBody(element)) {
  2348. return element.dom.isContentEditable;
  2349. }
  2350. else {
  2351. // Find the closest contenteditable element and check if it's editable
  2352. return closest(element).fold(constant(assumeEditable), (editable) => getRaw$1(editable) === 'true');
  2353. }
  2354. };
  2355. const getRaw$1 = (element) => element.dom.contentEditable;
  2356. const addClass = (clazz) => (element) => {
  2357. add$1(element, clazz);
  2358. };
  2359. const removeClasses = (classes) => (element) => {
  2360. remove(element, classes);
  2361. };
  2362. const ancestors$4 = (scope, predicate, isRoot) => filter$2(parents(scope, isRoot), predicate);
  2363. const children$1 = (scope, predicate) => filter$2(children$2(scope), predicate);
  2364. const descendants$1 = (scope, predicate) => {
  2365. let result = [];
  2366. // Recurse.toArray() might help here
  2367. each$2(children$2(scope), (x) => {
  2368. if (predicate(x)) {
  2369. result = result.concat([x]);
  2370. }
  2371. result = result.concat(descendants$1(x, predicate));
  2372. });
  2373. return result;
  2374. };
  2375. // For all of the following:
  2376. //
  2377. // jQuery does siblings of firstChild. IE9+ supports scope.dom.children (similar to Traverse.children but elements only).
  2378. // Traverse should also do this (but probably not by default).
  2379. //
  2380. const ancestors$3 = (scope, selector, isRoot) =>
  2381. // It may surprise you to learn this is exactly what JQuery does
  2382. // TODO: Avoid all this wrapping and unwrapping
  2383. ancestors$4(scope, (e) => is$1(e, selector), isRoot);
  2384. const children = (scope, selector) =>
  2385. // It may surprise you to learn this is exactly what JQuery does
  2386. // TODO: Avoid all the wrapping and unwrapping
  2387. children$1(scope, (e) => is$1(e, selector));
  2388. const descendants = (scope, selector) => all$1(selector, scope);
  2389. const inParent = (parent, children, element, index) => ({
  2390. parent,
  2391. children,
  2392. element,
  2393. index
  2394. });
  2395. const indexInParent = (element) => parent(element).bind((parent) => {
  2396. const children = children$2(parent);
  2397. return indexOf(children, element).map((index) => inParent(parent, children, element, index));
  2398. });
  2399. const indexOf = (elements, element) => findIndex(elements, curry(eq$1, element));
  2400. const ancestor = (scope, predicate, isRoot) => ancestor$2(scope, predicate, isRoot).isSome();
  2401. const getEnd = (element) => name(element) === 'img' ? 1 : getOption(element).fold(() => children$2(element).length, (v) => v.length);
  2402. const isTextNodeWithCursorPosition = (el) => getOption(el).filter((text) =>
  2403. // For the purposes of finding cursor positions only allow text nodes with content,
  2404. // but trim removes &nbsp; and that's allowed
  2405. text.trim().length !== 0 || text.indexOf(nbsp) > -1).isSome();
  2406. const isContentEditableFalse = (elem) => isHTMLElement(elem) && (get$b(elem, 'contenteditable') === 'false');
  2407. const elementsWithCursorPosition = ['img', 'br'];
  2408. const isCursorPosition = (elem) => {
  2409. const hasCursorPosition = isTextNodeWithCursorPosition(elem);
  2410. return hasCursorPosition || contains$2(elementsWithCursorPosition, name(elem)) || isContentEditableFalse(elem);
  2411. };
  2412. const first = (element) => descendant$1(element, isCursorPosition);
  2413. const last = (element) => descendantRtl(element, isCursorPosition);
  2414. // Note, sugar probably needs some RTL traversals.
  2415. const descendantRtl = (scope, predicate) => {
  2416. const descend = (element) => {
  2417. const children = children$2(element);
  2418. for (let i = children.length - 1; i >= 0; i--) {
  2419. const child = children[i];
  2420. if (predicate(child)) {
  2421. return Optional.some(child);
  2422. }
  2423. const res = descend(child);
  2424. if (res.isSome()) {
  2425. return res;
  2426. }
  2427. }
  2428. return Optional.none();
  2429. };
  2430. return descend(scope);
  2431. };
  2432. const create$4 = (start, soffset, finish, foffset) => ({
  2433. start,
  2434. soffset,
  2435. finish,
  2436. foffset
  2437. });
  2438. // tslint:disable-next-line:variable-name
  2439. const SimRange = {
  2440. create: create$4
  2441. };
  2442. const adt$5 = Adt.generate([
  2443. { before: ['element'] },
  2444. { on: ['element', 'offset'] },
  2445. { after: ['element'] }
  2446. ]);
  2447. // Probably don't need this given that we now have "match"
  2448. const cata$1 = (subject, onBefore, onOn, onAfter) => subject.fold(onBefore, onOn, onAfter);
  2449. const getStart$1 = (situ) => situ.fold(identity, identity, identity);
  2450. const before$2 = adt$5.before;
  2451. const on = adt$5.on;
  2452. const after$2 = adt$5.after;
  2453. // tslint:disable-next-line:variable-name
  2454. const Situ = {
  2455. before: before$2,
  2456. on,
  2457. after: after$2,
  2458. cata: cata$1,
  2459. getStart: getStart$1
  2460. };
  2461. // Consider adding a type for "element"
  2462. const adt$4 = Adt.generate([
  2463. { domRange: ['rng'] },
  2464. { relative: ['startSitu', 'finishSitu'] },
  2465. { exact: ['start', 'soffset', 'finish', 'foffset'] }
  2466. ]);
  2467. const exactFromRange = (simRange) => adt$4.exact(simRange.start, simRange.soffset, simRange.finish, simRange.foffset);
  2468. const getStart = (selection) => selection.match({
  2469. domRange: (rng) => SugarElement.fromDom(rng.startContainer),
  2470. relative: (startSitu, _finishSitu) => Situ.getStart(startSitu),
  2471. exact: (start, _soffset, _finish, _foffset) => start
  2472. });
  2473. const domRange = adt$4.domRange;
  2474. const relative = adt$4.relative;
  2475. const exact = adt$4.exact;
  2476. const getWin = (selection) => {
  2477. const start = getStart(selection);
  2478. return defaultView(start);
  2479. };
  2480. // This is out of place but it's API so I can't remove it
  2481. const range = SimRange.create;
  2482. // tslint:disable-next-line:variable-name
  2483. const SimSelection = {
  2484. domRange,
  2485. relative,
  2486. exact,
  2487. exactFromRange,
  2488. getWin,
  2489. range
  2490. };
  2491. const caretPositionFromPoint = (doc, x, y) => {
  2492. var _a;
  2493. return Optional.from((_a = doc.caretPositionFromPoint) === null || _a === void 0 ? void 0 : _a.call(doc, x, y))
  2494. .bind((pos) => {
  2495. // It turns out that Firefox can return null for pos.offsetNode
  2496. if (pos.offsetNode === null) {
  2497. return Optional.none();
  2498. }
  2499. const r = doc.createRange();
  2500. r.setStart(pos.offsetNode, pos.offset);
  2501. r.collapse();
  2502. return Optional.some(r);
  2503. });
  2504. };
  2505. const caretRangeFromPoint = (doc, x, y) => { var _a; return Optional.from((_a = doc.caretRangeFromPoint) === null || _a === void 0 ? void 0 : _a.call(doc, x, y)); };
  2506. const availableSearch = (doc, x, y) => {
  2507. if (doc.caretPositionFromPoint) {
  2508. return caretPositionFromPoint(doc, x, y); // defined standard, firefox only
  2509. }
  2510. else if (doc.caretRangeFromPoint) {
  2511. return caretRangeFromPoint(doc, x, y); // webkit/blink implementation
  2512. }
  2513. else {
  2514. return Optional.none(); // unsupported browser
  2515. }
  2516. };
  2517. const fromPoint = (win, x, y) => {
  2518. const doc = win.document;
  2519. return availableSearch(doc, x, y).map((rng) => SimRange.create(SugarElement.fromDom(rng.startContainer), rng.startOffset, SugarElement.fromDom(rng.endContainer), rng.endOffset));
  2520. };
  2521. const beforeSpecial = (element, offset) => {
  2522. // From memory, we don't want to use <br> directly on Firefox because it locks the keyboard input.
  2523. // It turns out that <img> directly on IE locks the keyboard as well.
  2524. // If the offset is 0, use before. If the offset is 1, use after.
  2525. // TBIO-3889: Firefox Situ.on <input> results in a child of the <input>; Situ.before <input> results in platform inconsistencies
  2526. const name$1 = name(element);
  2527. if ('input' === name$1) {
  2528. return Situ.after(element);
  2529. }
  2530. else if (!contains$2(['br', 'img'], name$1)) {
  2531. return Situ.on(element, offset);
  2532. }
  2533. else {
  2534. return offset === 0 ? Situ.before(element) : Situ.after(element);
  2535. }
  2536. };
  2537. const preprocessRelative = (startSitu, finishSitu) => {
  2538. const start = startSitu.fold(Situ.before, beforeSpecial, Situ.after);
  2539. const finish = finishSitu.fold(Situ.before, beforeSpecial, Situ.after);
  2540. return SimSelection.relative(start, finish);
  2541. };
  2542. const preprocessExact = (start, soffset, finish, foffset) => {
  2543. const startSitu = beforeSpecial(start, soffset);
  2544. const finishSitu = beforeSpecial(finish, foffset);
  2545. return SimSelection.relative(startSitu, finishSitu);
  2546. };
  2547. const getNativeSelection = (win) => Optional.from(win.getSelection());
  2548. const doSetNativeRange = (win, rng) => {
  2549. getNativeSelection(win).each((selection) => {
  2550. selection.removeAllRanges();
  2551. selection.addRange(rng);
  2552. });
  2553. };
  2554. const doSetRange = (win, start, soffset, finish, foffset) => {
  2555. const rng = exactToNative(win, start, soffset, finish, foffset);
  2556. doSetNativeRange(win, rng);
  2557. };
  2558. const setLegacyRtlRange = (win, selection, start, soffset, finish, foffset) => {
  2559. selection.collapse(start.dom, soffset);
  2560. selection.extend(finish.dom, foffset);
  2561. };
  2562. const setRangeFromRelative = (win, relative) => diagnose(win, relative).match({
  2563. ltr: (start, soffset, finish, foffset) => {
  2564. doSetRange(win, start, soffset, finish, foffset);
  2565. },
  2566. rtl: (start, soffset, finish, foffset) => {
  2567. getNativeSelection(win).each((selection) => {
  2568. // If this selection is backwards, then we need to use extend.
  2569. if (selection.setBaseAndExtent) {
  2570. selection.setBaseAndExtent(start.dom, soffset, finish.dom, foffset);
  2571. }
  2572. else if (selection.extend) {
  2573. // This try catch is for older browsers (Firefox 52) as they're sometimes unable to handle setting backwards selections using selection.extend and error out.
  2574. try {
  2575. setLegacyRtlRange(win, selection, start, soffset, finish, foffset);
  2576. }
  2577. catch (_a) {
  2578. // If it does fail, try again with ltr.
  2579. doSetRange(win, finish, foffset, start, soffset);
  2580. }
  2581. }
  2582. else {
  2583. doSetRange(win, finish, foffset, start, soffset);
  2584. }
  2585. });
  2586. }
  2587. });
  2588. const setExact = (win, start, soffset, finish, foffset) => {
  2589. const relative = preprocessExact(start, soffset, finish, foffset);
  2590. setRangeFromRelative(win, relative);
  2591. };
  2592. const setRelative = (win, startSitu, finishSitu) => {
  2593. const relative = preprocessRelative(startSitu, finishSitu);
  2594. setRangeFromRelative(win, relative);
  2595. };
  2596. // NOTE: We are still reading the range because it gives subtly different behaviour
  2597. // than using the anchorNode and focusNode. I'm not sure if this behaviour is any
  2598. // better or worse; it's just different.
  2599. const readRange = (selection) => {
  2600. if (selection.rangeCount > 0) {
  2601. const firstRng = selection.getRangeAt(0);
  2602. const lastRng = selection.getRangeAt(selection.rangeCount - 1);
  2603. return Optional.some(SimRange.create(SugarElement.fromDom(firstRng.startContainer), firstRng.startOffset, SugarElement.fromDom(lastRng.endContainer), lastRng.endOffset));
  2604. }
  2605. else {
  2606. return Optional.none();
  2607. }
  2608. };
  2609. const doGetExact = (selection) => {
  2610. if (selection.anchorNode === null || selection.focusNode === null) {
  2611. return readRange(selection);
  2612. }
  2613. else {
  2614. const anchor = SugarElement.fromDom(selection.anchorNode);
  2615. const focus = SugarElement.fromDom(selection.focusNode);
  2616. // if this returns true anchor is _after_ focus, so we need a custom selection object to maintain the RTL selection
  2617. return after$5(anchor, selection.anchorOffset, focus, selection.focusOffset) ? Optional.some(SimRange.create(anchor, selection.anchorOffset, focus, selection.focusOffset)) : readRange(selection);
  2618. }
  2619. };
  2620. const setToElement = (win, element, selectNodeContents$1 = true) => {
  2621. const rngGetter = selectNodeContents$1 ? selectNodeContents : selectNode;
  2622. const rng = rngGetter(win, element);
  2623. doSetNativeRange(win, rng);
  2624. };
  2625. const getExact = (win) =>
  2626. // We want to retrieve the selection as it is.
  2627. getNativeSelection(win)
  2628. .filter((sel) => sel.rangeCount > 0)
  2629. .bind(doGetExact);
  2630. // TODO: Test this.
  2631. const get$3 = (win) => getExact(win).map((range) => SimSelection.exact(range.start, range.soffset, range.finish, range.foffset));
  2632. const getFirstRect = (win, selection) => {
  2633. const rng = asLtrRange(win, selection);
  2634. return getFirstRect$1(rng);
  2635. };
  2636. const getAtPoint = (win, x, y) => fromPoint(win, x, y);
  2637. const clear = (win) => {
  2638. getNativeSelection(win).each((selection) => selection.removeAllRanges());
  2639. };
  2640. const units = {
  2641. // we don't really support all of these different ways to express a length
  2642. unsupportedLength: [
  2643. 'em',
  2644. 'ex',
  2645. 'cap',
  2646. 'ch',
  2647. 'ic',
  2648. 'rem',
  2649. 'lh',
  2650. 'rlh',
  2651. 'vw',
  2652. 'vh',
  2653. 'vi',
  2654. 'vb',
  2655. 'vmin',
  2656. 'vmax',
  2657. 'cm',
  2658. 'mm',
  2659. 'Q',
  2660. 'in',
  2661. 'pc',
  2662. 'pt',
  2663. 'px'
  2664. ],
  2665. // these are the length values we do support
  2666. fixed: ['px', 'pt'],
  2667. relative: ['%'],
  2668. empty: ['']
  2669. };
  2670. // Built from https://tc39.es/ecma262/#prod-StrDecimalLiteral
  2671. // Matches a float followed by a trailing set of characters
  2672. const pattern = (() => {
  2673. const decimalDigits = '[0-9]+';
  2674. const signedInteger = '[+-]?' + decimalDigits;
  2675. const exponentPart = '[eE]' + signedInteger;
  2676. const dot = '\\.';
  2677. const opt = (input) => `(?:${input})?`;
  2678. const unsignedDecimalLiteral = [
  2679. 'Infinity',
  2680. decimalDigits + dot + opt(decimalDigits) + opt(exponentPart),
  2681. dot + decimalDigits + opt(exponentPart),
  2682. decimalDigits + opt(exponentPart)
  2683. ].join('|');
  2684. const float = `[+-]?(?:${unsignedDecimalLiteral})`;
  2685. return new RegExp(`^(${float})(.*)$`);
  2686. })();
  2687. const isUnit = (unit, accepted) => exists(accepted, (acc) => exists(units[acc], (check) => unit === check));
  2688. const parse = (input, accepted) => {
  2689. const match = Optional.from(pattern.exec(input));
  2690. return match.bind((array) => {
  2691. const value = Number(array[1]);
  2692. const unitRaw = array[2];
  2693. if (isUnit(unitRaw, accepted)) {
  2694. return Optional.some({
  2695. value,
  2696. unit: unitRaw
  2697. });
  2698. }
  2699. else {
  2700. return Optional.none();
  2701. }
  2702. });
  2703. };
  2704. const zero = (array) => map$1(array, constant(0));
  2705. const surround = (sizes, startIndex, endIndex, results, f) => f(sizes.slice(0, startIndex)).concat(results).concat(f(sizes.slice(endIndex)));
  2706. // Clamp positive or negative delta so that a column/row cannot be reduced past its min size
  2707. const clampDeltaHelper = (predicate) => (sizes, index, delta, minCellSize) => {
  2708. if (!predicate(delta)) {
  2709. return delta;
  2710. }
  2711. else {
  2712. const newSize = Math.max(minCellSize, sizes[index] - Math.abs(delta));
  2713. const diff = Math.abs(newSize - sizes[index]);
  2714. return delta >= 0 ? diff : -diff;
  2715. }
  2716. };
  2717. const clampNegativeDelta = clampDeltaHelper((delta) => delta < 0);
  2718. const clampDelta = clampDeltaHelper(always);
  2719. // Preserve the size of the columns/rows and adjust the table size
  2720. const resizeTable = () => {
  2721. const calcFixedDeltas = (sizes, index, next, delta, minCellSize) => {
  2722. const clampedDelta = clampNegativeDelta(sizes, index, delta, minCellSize);
  2723. return surround(sizes, index, next + 1, [clampedDelta, 0], zero);
  2724. };
  2725. // Calculate delta for adjusted column
  2726. // Also need to calculate deltas for all other columns/rows to ensure they stay at the same visual width/height
  2727. // when the table width/height is adjusted
  2728. const calcRelativeDeltas = (sizes, index, delta, minCellSize) => {
  2729. // ASSUMPTION: The delta will be a percentage. This may not be correct if other relative sizing is added, so we probably
  2730. // need a better way to calc the ratio.
  2731. const ratio = (100 + delta) / 100;
  2732. const newThis = Math.max(minCellSize, (sizes[index] + delta) / ratio);
  2733. return map$1(sizes, (size, idx) => {
  2734. const newSize = idx === index ? newThis : size / ratio;
  2735. return newSize - size;
  2736. });
  2737. };
  2738. // Calculations for the inner columns/rows
  2739. const calcLeftEdgeDeltas = (sizes, index, next, delta, minCellSize, isRelative) => {
  2740. if (isRelative) {
  2741. return calcRelativeDeltas(sizes, index, delta, minCellSize);
  2742. }
  2743. else {
  2744. return calcFixedDeltas(sizes, index, next, delta, minCellSize);
  2745. }
  2746. };
  2747. const calcMiddleDeltas = (sizes, _prev, index, next, delta, minCellSize, isRelative) => calcLeftEdgeDeltas(sizes, index, next, delta, minCellSize, isRelative);
  2748. const resizeTable = (resizer, delta) => resizer(delta);
  2749. // Calculations for the last column/row resizer
  2750. const calcRightEdgeDeltas = (sizes, _prev, index, delta, minCellSize, isRelative) => {
  2751. if (isRelative) {
  2752. return calcRelativeDeltas(sizes, index, delta, minCellSize);
  2753. }
  2754. else {
  2755. const clampedDelta = clampNegativeDelta(sizes, index, delta, minCellSize);
  2756. return zero(sizes.slice(0, index)).concat([clampedDelta]);
  2757. }
  2758. };
  2759. const calcRedestributedWidths = (sizes, totalWidth, pixelDelta, isRelative) => {
  2760. if (isRelative) {
  2761. const tableWidth = totalWidth + pixelDelta;
  2762. const ratio = tableWidth / totalWidth;
  2763. const newSizes = map$1(sizes, (size) => size / ratio);
  2764. return {
  2765. delta: (ratio * 100) - 100,
  2766. newSizes,
  2767. };
  2768. }
  2769. else {
  2770. return {
  2771. delta: pixelDelta,
  2772. newSizes: sizes,
  2773. };
  2774. }
  2775. };
  2776. return {
  2777. resizeTable,
  2778. clampTableDelta: clampNegativeDelta,
  2779. calcLeftEdgeDeltas,
  2780. calcMiddleDeltas,
  2781. calcRightEdgeDeltas,
  2782. calcRedestributedWidths,
  2783. };
  2784. };
  2785. // Distribute the column/rows and try to preserve the table size
  2786. const preserveTable = () => {
  2787. // Calculations for the inner columns/rows
  2788. const calcLeftEdgeDeltas = (sizes, index, next, delta, minCellSize) => {
  2789. const idx = delta >= 0 ? next : index;
  2790. const clampedDelta = clampDelta(sizes, idx, delta, minCellSize);
  2791. // negative delta -> deltas becomes [ neg, pos ], positive delta -> deltas becomes [ pos, neg ]
  2792. return surround(sizes, index, next + 1, [clampedDelta, -clampedDelta], zero);
  2793. };
  2794. const calcMiddleDeltas = (sizes, _prev, index, next, delta, minCellSize) => calcLeftEdgeDeltas(sizes, index, next, delta, minCellSize);
  2795. const resizeTable = (resizer, delta, isLastColumn) => {
  2796. if (isLastColumn) {
  2797. resizer(delta);
  2798. }
  2799. };
  2800. // Calculations for the last column/row resizer
  2801. const calcRightEdgeDeltas = (sizes, _prev, _index, delta, _minCellSize, isRelative) => {
  2802. if (isRelative) {
  2803. return zero(sizes);
  2804. }
  2805. else {
  2806. // Distribute the delta amongst all of the columns/rows
  2807. const diff = delta / sizes.length;
  2808. return map$1(sizes, constant(diff));
  2809. }
  2810. };
  2811. const clampTableDelta = (sizes, index, delta, minCellSize, isLastColumn) => {
  2812. // Don't clamp the last resizer using normal methods
  2813. // Need to allow table width to be reduced past the last column position to allow for distributive resizing
  2814. if (isLastColumn) {
  2815. if (delta >= 0) {
  2816. return delta;
  2817. }
  2818. else {
  2819. // Clamp delta so that none of the columns/rows can reduce below their min size
  2820. const maxDelta = foldl(sizes, (a, b) => a + b - minCellSize, 0);
  2821. return Math.max(-maxDelta, delta);
  2822. }
  2823. }
  2824. else {
  2825. return clampNegativeDelta(sizes, index, delta, minCellSize);
  2826. }
  2827. };
  2828. const calcRedestributedWidths = (sizes, _totalWidth, _pixelDelta, _isRelative) => ({
  2829. delta: 0,
  2830. newSizes: sizes,
  2831. });
  2832. return {
  2833. resizeTable,
  2834. clampTableDelta,
  2835. calcLeftEdgeDeltas,
  2836. calcMiddleDeltas,
  2837. calcRightEdgeDeltas,
  2838. calcRedestributedWidths
  2839. };
  2840. };
  2841. const getAttrValue = (cell, name, fallback = 0) => getOpt(cell, name).map((value) => parseInt(value, 10)).getOr(fallback);
  2842. const getSpan = (cell, type) => getAttrValue(cell, type, 1);
  2843. const hasColspan = (cellOrCol) => {
  2844. if (isTag('col')(cellOrCol)) {
  2845. return getAttrValue(cellOrCol, 'span', 1) > 1;
  2846. }
  2847. else {
  2848. return getSpan(cellOrCol, 'colspan') > 1;
  2849. }
  2850. };
  2851. const hasRowspan = (cell) => getSpan(cell, 'rowspan') > 1;
  2852. const getCssValue = (element, property) => parseInt(get$9(element, property), 10);
  2853. const minWidth = constant(10);
  2854. const minHeight = constant(10);
  2855. const firstLayer = (scope, selector) => {
  2856. return filterFirstLayer(scope, selector, always);
  2857. };
  2858. const filterFirstLayer = (scope, selector, predicate) => {
  2859. return bind$2(children$2(scope), (x) => {
  2860. if (is$1(x, selector)) {
  2861. return predicate(x) ? [x] : [];
  2862. }
  2863. else {
  2864. return filterFirstLayer(x, selector, predicate);
  2865. }
  2866. });
  2867. };
  2868. // lookup inside this table
  2869. const lookup = (tags, element, isRoot = never) => {
  2870. // If the element we're inspecting is the root, we definitely don't want it.
  2871. if (isRoot(element)) {
  2872. return Optional.none();
  2873. }
  2874. // This looks a lot like SelectorFind.closest, with one big exception - the isRoot check.
  2875. // The code here will look for parents if passed a table, SelectorFind.closest with that specific isRoot check won't.
  2876. if (contains$2(tags, name(element))) {
  2877. return Optional.some(element);
  2878. }
  2879. const isRootOrUpperTable = (elm) => is$1(elm, 'table') || isRoot(elm);
  2880. return ancestor$1(element, tags.join(','), isRootOrUpperTable);
  2881. };
  2882. /*
  2883. * Identify the optional cell that element represents.
  2884. */
  2885. const cell = (element, isRoot) => lookup(['td', 'th'], element, isRoot);
  2886. const cells$1 = (ancestor) => firstLayer(ancestor, 'th,td');
  2887. const columns$1 = (ancestor) => {
  2888. if (is$1(ancestor, 'colgroup')) {
  2889. return children(ancestor, 'col');
  2890. }
  2891. else {
  2892. return bind$2(columnGroups(ancestor), (columnGroup) => children(columnGroup, 'col'));
  2893. }
  2894. };
  2895. const table = (element, isRoot) => closest$1(element, 'table', isRoot);
  2896. const rows$1 = (ancestor) => firstLayer(ancestor, 'tr');
  2897. const columnGroups = (ancestor) => table(ancestor).fold(constant([]), (table) => children(table, 'colgroup'));
  2898. const isHeaderCell = isTag('th');
  2899. const isHeaderCells = (cells) => forall(cells, (cell) => isHeaderCell(cell.element));
  2900. const getRowHeaderType = (isHeaderRow, isHeaderCells) => {
  2901. if (isHeaderRow && isHeaderCells) {
  2902. return 'sectionCells';
  2903. }
  2904. else if (isHeaderRow) {
  2905. return 'section';
  2906. }
  2907. else {
  2908. return 'cells';
  2909. }
  2910. };
  2911. const getRowType = (row) => {
  2912. // Header rows can use a combination of theads and ths - want to detect the different combinations
  2913. const isHeaderRow = row.section === 'thead';
  2914. const isHeaderCells = is$2(findCommonCellType(row.cells), 'th');
  2915. if (row.section === 'tfoot') {
  2916. return { type: 'footer' };
  2917. }
  2918. else if (isHeaderRow || isHeaderCells) {
  2919. return { type: 'header', subType: getRowHeaderType(isHeaderRow, isHeaderCells) };
  2920. }
  2921. else {
  2922. return { type: 'body' };
  2923. }
  2924. };
  2925. const findCommonCellType = (cells) => {
  2926. const headerCells = filter$2(cells, (cell) => isHeaderCell(cell.element));
  2927. if (headerCells.length === 0) {
  2928. return Optional.some('td');
  2929. }
  2930. else if (headerCells.length === cells.length) {
  2931. return Optional.some('th');
  2932. }
  2933. else {
  2934. return Optional.none();
  2935. }
  2936. };
  2937. const findCommonRowType = (rows) => {
  2938. const rowTypes = map$1(rows, (row) => getRowType(row).type);
  2939. const hasHeader = contains$2(rowTypes, 'header');
  2940. const hasFooter = contains$2(rowTypes, 'footer');
  2941. if (!hasHeader && !hasFooter) {
  2942. return Optional.some('body');
  2943. }
  2944. else {
  2945. const hasBody = contains$2(rowTypes, 'body');
  2946. if (hasHeader && !hasBody && !hasFooter) {
  2947. return Optional.some('header');
  2948. }
  2949. else if (!hasHeader && !hasBody && hasFooter) {
  2950. return Optional.some('footer');
  2951. }
  2952. else {
  2953. return Optional.none();
  2954. }
  2955. }
  2956. };
  2957. const findTableRowHeaderType = (warehouse) => findMap(warehouse.all, (row) => {
  2958. const rowType = getRowType(row);
  2959. return rowType.type === 'header' ? Optional.from(rowType.subType) : Optional.none();
  2960. });
  2961. const fromRowsOrColGroups = (elems, getSection) => map$1(elems, (row) => {
  2962. if (name(row) === 'colgroup') {
  2963. const cells = map$1(columns$1(row), (column) => {
  2964. const colspan = getAttrValue(column, 'span', 1);
  2965. return detail(column, 1, colspan);
  2966. });
  2967. return rowdetail(row, cells, 'colgroup');
  2968. }
  2969. else {
  2970. const cells = map$1(cells$1(row), (cell) => {
  2971. const rowspan = getAttrValue(cell, 'rowspan', 1);
  2972. const colspan = getAttrValue(cell, 'colspan', 1);
  2973. return detail(cell, rowspan, colspan);
  2974. });
  2975. return rowdetail(row, cells, getSection(row));
  2976. }
  2977. });
  2978. const getParentSection = (group) => parent(group).map((parent) => {
  2979. const parentName = name(parent);
  2980. return isValidSection(parentName) ? parentName : 'tbody';
  2981. }).getOr('tbody');
  2982. /*
  2983. * Takes a DOM table and returns a list of list of:
  2984. element: row element
  2985. cells: (id, rowspan, colspan) structs
  2986. */
  2987. const fromTable$1 = (table) => {
  2988. const rows = rows$1(table);
  2989. const columnGroups$1 = columnGroups(table);
  2990. const elems = [...columnGroups$1, ...rows];
  2991. return fromRowsOrColGroups(elems, getParentSection);
  2992. };
  2993. const fromPastedRows = (elems, section) => fromRowsOrColGroups(elems, () => section);
  2994. const LOCKED_COL_ATTR = 'data-snooker-locked-cols';
  2995. const getLockedColumnsFromTable = (table) => getOpt(table, LOCKED_COL_ATTR)
  2996. .bind((lockedColStr) => Optional.from(lockedColStr.match(/\d+/g)))
  2997. .map((lockedCols) => mapToObject(lockedCols, always));
  2998. // Need to check all of the cells to determine which columns are locked - reasoning is because rowspan and colspan cells where the same cell is used by multiple columns
  2999. const getLockedColumnsFromGrid = (grid) => {
  3000. const locked = foldl(extractGridDetails(grid).rows, (acc, row) => {
  3001. each$2(row.cells, (cell, idx) => {
  3002. if (cell.isLocked) {
  3003. acc[idx] = true;
  3004. }
  3005. });
  3006. return acc;
  3007. }, {});
  3008. const lockedArr = mapToArray(locked, (_val, key) => parseInt(key, 10));
  3009. return sort$1(lockedArr);
  3010. };
  3011. const key = (row, column) => {
  3012. return row + ',' + column;
  3013. };
  3014. const getAt = (warehouse, row, column) => Optional.from(warehouse.access[key(row, column)]);
  3015. const findItem = (warehouse, item, comparator) => {
  3016. const filtered = filterItems(warehouse, (detail) => {
  3017. return comparator(item, detail.element);
  3018. });
  3019. return filtered.length > 0 ? Optional.some(filtered[0]) : Optional.none();
  3020. };
  3021. const filterItems = (warehouse, predicate) => {
  3022. const all = bind$2(warehouse.all, (r) => {
  3023. return r.cells;
  3024. });
  3025. return filter$2(all, predicate);
  3026. };
  3027. const generateColumns = (rowData) => {
  3028. const columnsGroup = {};
  3029. let index = 0;
  3030. each$2(rowData.cells, (column) => {
  3031. const colspan = column.colspan;
  3032. range$1(colspan, (columnIndex) => {
  3033. const colIndex = index + columnIndex;
  3034. columnsGroup[colIndex] = columnext(column.element, colspan, colIndex);
  3035. });
  3036. index += colspan;
  3037. });
  3038. return columnsGroup;
  3039. };
  3040. /*
  3041. * From a list of list of Detail, generate three pieces of information:
  3042. * 1. the grid size
  3043. * 2. a data structure which can efficiently identify which cell is in which row,column position
  3044. * 3. a list of all cells in order left-to-right, top-to-bottom
  3045. */
  3046. const generate = (list) => {
  3047. // list is an array of objects, made by cells and elements
  3048. // elements: is the TR
  3049. // cells: is an array of objects representing the cells in the row.
  3050. // It is made of:
  3051. // colspan (merge cell)
  3052. // element
  3053. // rowspan (merge cols)
  3054. const access = {};
  3055. const cells = [];
  3056. const tableOpt = head(list).map((rowData) => rowData.element).bind(table);
  3057. const lockedColumns = tableOpt.bind(getLockedColumnsFromTable).getOr({});
  3058. let maxRows = 0;
  3059. let maxColumns = 0;
  3060. let rowCount = 0;
  3061. const { pass: colgroupRows, fail: rows } = partition(list, (rowData) => rowData.section === 'colgroup');
  3062. // Handle rows first
  3063. each$2(rows, (rowData) => {
  3064. const currentRow = [];
  3065. each$2(rowData.cells, (rowCell) => {
  3066. let start = 0;
  3067. // If this spot has been taken by a previous rowspan, skip it.
  3068. while (access[key(rowCount, start)] !== undefined) {
  3069. start++;
  3070. }
  3071. const isLocked = hasNonNullableKey(lockedColumns, start.toString());
  3072. const current = extended(rowCell.element, rowCell.rowspan, rowCell.colspan, rowCount, start, isLocked);
  3073. // Occupy all the (row, column) positions that this cell spans for.
  3074. for (let occupiedColumnPosition = 0; occupiedColumnPosition < rowCell.colspan; occupiedColumnPosition++) {
  3075. for (let occupiedRowPosition = 0; occupiedRowPosition < rowCell.rowspan; occupiedRowPosition++) {
  3076. const rowPosition = rowCount + occupiedRowPosition;
  3077. const columnPosition = start + occupiedColumnPosition;
  3078. const newpos = key(rowPosition, columnPosition);
  3079. access[newpos] = current;
  3080. maxColumns = Math.max(maxColumns, columnPosition + 1);
  3081. }
  3082. }
  3083. currentRow.push(current);
  3084. });
  3085. maxRows++;
  3086. cells.push(rowdetail(rowData.element, currentRow, rowData.section));
  3087. rowCount++;
  3088. });
  3089. // Handle colgroups
  3090. // Note: Currently only a single colgroup is supported so just use the last one
  3091. const { columns, colgroups } = last$2(colgroupRows).map((rowData) => {
  3092. const columns = generateColumns(rowData);
  3093. const colgroup$1 = colgroup(rowData.element, values(columns));
  3094. return {
  3095. colgroups: [colgroup$1],
  3096. columns
  3097. };
  3098. }).getOrThunk(() => ({
  3099. colgroups: [],
  3100. columns: {}
  3101. }));
  3102. const grid$1 = grid(maxRows, maxColumns);
  3103. return {
  3104. grid: grid$1,
  3105. access,
  3106. all: cells,
  3107. columns,
  3108. colgroups
  3109. };
  3110. };
  3111. const fromTable = (table) => {
  3112. const list = fromTable$1(table);
  3113. return generate(list);
  3114. };
  3115. const justCells = (warehouse) => bind$2(warehouse.all, (w) => w.cells);
  3116. const justColumns = (warehouse) => values(warehouse.columns);
  3117. const hasColumns = (warehouse) => keys(warehouse.columns).length > 0;
  3118. const getColumnAt = (warehouse, columnIndex) => Optional.from(warehouse.columns[columnIndex]);
  3119. const Warehouse = {
  3120. fromTable,
  3121. generate,
  3122. getAt,
  3123. findItem,
  3124. filterItems,
  3125. justCells,
  3126. justColumns,
  3127. hasColumns,
  3128. getColumnAt
  3129. };
  3130. const transformCell = (cell, comparator, substitution) => elementnew(substitution(cell.element, comparator), true, cell.isLocked);
  3131. const transformRow = (row, section) => row.section !== section ? rowcells(row.element, row.cells, section, row.isNew) : row;
  3132. const section = () => ({
  3133. transformRow,
  3134. transformCell: (cell, comparator, substitution) => {
  3135. const newCell = substitution(cell.element, comparator);
  3136. // Convert the cell to a td element as "section" should always use td element
  3137. const fixedCell = name(newCell) !== 'td' ? mutate$1(newCell, 'td') : newCell;
  3138. return elementnew(fixedCell, cell.isNew, cell.isLocked);
  3139. }
  3140. });
  3141. const sectionCells = () => ({
  3142. transformRow,
  3143. transformCell
  3144. });
  3145. const cells = () => ({
  3146. transformRow: (row, section) => {
  3147. // Ensure that cells are always within the tbody for headers
  3148. const newSection = section === 'thead' ? 'tbody' : section;
  3149. return transformRow(row, newSection);
  3150. },
  3151. transformCell
  3152. });
  3153. // A fallback legacy type that won't adjust the row/section type
  3154. // and instead will only modify cells
  3155. const fallback = () => ({
  3156. transformRow: identity,
  3157. transformCell
  3158. });
  3159. const getTableSectionType = (table, fallback) => {
  3160. const warehouse = Warehouse.fromTable(table);
  3161. const type = findTableRowHeaderType(warehouse).getOr(fallback);
  3162. switch (type) {
  3163. case 'section':
  3164. return section();
  3165. case 'sectionCells':
  3166. return sectionCells();
  3167. case 'cells':
  3168. return cells();
  3169. }
  3170. };
  3171. const TableSection = {
  3172. getTableSectionType,
  3173. section,
  3174. sectionCells,
  3175. cells,
  3176. fallback
  3177. };
  3178. /*
  3179. * Identify for each column, a cell that has colspan 1. Note, this
  3180. * may actually fail, and future work will be to calculate column
  3181. * sizes that are only available through the difference of two
  3182. * spanning columns.
  3183. */
  3184. const columns = (warehouse, isValidCell = always) => {
  3185. const grid = warehouse.grid;
  3186. const cols = range$1(grid.columns, identity);
  3187. const rowsArr = range$1(grid.rows, identity);
  3188. return map$1(cols, (col) => {
  3189. const getBlock = () => bind$2(rowsArr, (r) => Warehouse.getAt(warehouse, r, col)
  3190. .filter((detail) => detail.column === col)
  3191. .toArray());
  3192. const isValid = (detail) => detail.colspan === 1 && isValidCell(detail.element);
  3193. const getFallback = () => Warehouse.getAt(warehouse, 0, col);
  3194. return decide(getBlock, isValid, getFallback);
  3195. });
  3196. };
  3197. const decide = (getBlock, isValid, getFallback) => {
  3198. const inBlock = getBlock();
  3199. const validInBlock = find$1(inBlock, isValid);
  3200. const detailOption = validInBlock.orThunk(() => Optional.from(inBlock[0]).orThunk(getFallback));
  3201. return detailOption.map((detail) => detail.element);
  3202. };
  3203. const rows = (warehouse) => {
  3204. const grid = warehouse.grid;
  3205. const rowsArr = range$1(grid.rows, identity);
  3206. const cols = range$1(grid.columns, identity);
  3207. return map$1(rowsArr, (row) => {
  3208. const getBlock = () => bind$2(cols, (c) => Warehouse.getAt(warehouse, row, c)
  3209. .filter((detail) => detail.row === row)
  3210. .fold(constant([]), (detail) => [detail]));
  3211. const isSingle = (detail) => detail.rowspan === 1;
  3212. const getFallback = () => Warehouse.getAt(warehouse, row, 0);
  3213. return decide(getBlock, isSingle, getFallback);
  3214. });
  3215. };
  3216. const deduce = (xs, index) => {
  3217. if (index < 0 || index >= xs.length - 1) {
  3218. return Optional.none();
  3219. }
  3220. const current = xs[index].fold(() => {
  3221. const rest = reverse(xs.slice(0, index));
  3222. return findMap(rest, (a, i) => a.map((aa) => ({ value: aa, delta: i + 1 })));
  3223. }, (c) => Optional.some({ value: c, delta: 0 }));
  3224. const next = xs[index + 1].fold(() => {
  3225. const rest = xs.slice(index + 1);
  3226. return findMap(rest, (a, i) => a.map((aa) => ({ value: aa, delta: i + 1 })));
  3227. }, (n) => Optional.some({ value: n, delta: 1 }));
  3228. return current.bind((c) => next.map((n) => {
  3229. const extras = n.delta + c.delta;
  3230. return Math.abs(n.value - c.value) / extras;
  3231. }));
  3232. };
  3233. const rowInfo = (row, y) => ({
  3234. row,
  3235. y
  3236. });
  3237. const colInfo = (col, x) => ({
  3238. col,
  3239. x
  3240. });
  3241. const rtlEdge = (cell) => {
  3242. const pos = absolute(cell);
  3243. return pos.left + getOuter(cell);
  3244. };
  3245. const ltrEdge = (cell) => {
  3246. return absolute(cell).left;
  3247. };
  3248. const getLeftEdge = (index, cell) => {
  3249. return colInfo(index, ltrEdge(cell));
  3250. };
  3251. const getRightEdge = (index, cell) => {
  3252. return colInfo(index, rtlEdge(cell));
  3253. };
  3254. const getTop$1 = (cell) => {
  3255. return absolute(cell).top;
  3256. };
  3257. const getTopEdge = (index, cell) => {
  3258. return rowInfo(index, getTop$1(cell));
  3259. };
  3260. const getBottomEdge = (index, cell) => {
  3261. return rowInfo(index, getTop$1(cell) + getOuter$1(cell));
  3262. };
  3263. const findPositions = (getInnerEdge, getOuterEdge, array) => {
  3264. if (array.length === 0) {
  3265. return [];
  3266. }
  3267. const lines = map$1(array.slice(1), (cellOption, index) => {
  3268. return cellOption.map((cell) => {
  3269. return getInnerEdge(index, cell);
  3270. });
  3271. });
  3272. const lastLine = array[array.length - 1].map((cell) => {
  3273. return getOuterEdge(array.length - 1, cell);
  3274. });
  3275. return lines.concat([lastLine]);
  3276. };
  3277. const negate = (step) => {
  3278. return -step;
  3279. };
  3280. const height = {
  3281. delta: identity,
  3282. positions: (optElements) => findPositions(getTopEdge, getBottomEdge, optElements),
  3283. edge: getTop$1
  3284. };
  3285. const ltr$1 = {
  3286. delta: identity,
  3287. edge: ltrEdge,
  3288. positions: (optElements) => findPositions(getLeftEdge, getRightEdge, optElements)
  3289. };
  3290. const rtl$1 = {
  3291. delta: negate,
  3292. edge: rtlEdge,
  3293. positions: (optElements) => findPositions(getRightEdge, getLeftEdge, optElements)
  3294. };
  3295. const detect$1 = onDirection(ltr$1, rtl$1);
  3296. const width = {
  3297. delta: (amount, table) => detect$1(table).delta(amount, table),
  3298. positions: (cols, table) => detect$1(table).positions(cols, table),
  3299. edge: (cell) => detect$1(cell).edge(cell)
  3300. };
  3301. const rPercentageBasedSizeRegex = /(\d+(\.\d+)?)%/;
  3302. const rPixelBasedSizeRegex = /(\d+(\.\d+)?)px|em/;
  3303. const isCol$2 = isTag('col');
  3304. const isRow$2 = isTag('tr');
  3305. const getPercentSize = (elm, outerGetter, innerGetter) => {
  3306. const relativeParent = parentElement(elm).getOrThunk(() => getBody$1(owner(elm)));
  3307. return outerGetter(elm) / innerGetter(relativeParent) * 100;
  3308. };
  3309. const setPixelWidth = (cell, amount) => {
  3310. set$1(cell, 'width', amount + 'px');
  3311. };
  3312. const setPercentageWidth = (cell, amount) => {
  3313. set$1(cell, 'width', amount + '%');
  3314. };
  3315. const setHeight = (cell, amount) => {
  3316. set$1(cell, 'height', amount + 'px');
  3317. };
  3318. const removeHeight = (cell) => {
  3319. remove$4(cell, 'height');
  3320. };
  3321. const getHeightValue = (cell) => getRuntime$1(cell) + 'px';
  3322. const convert = (cell, number, getter, setter) => {
  3323. const newSize = table(cell).map((table) => {
  3324. const total = getter(table);
  3325. return Math.floor((number / 100.0) * total);
  3326. }).getOr(number);
  3327. setter(cell, newSize);
  3328. return newSize;
  3329. };
  3330. const normalizePixelSize = (value, cell, getter, setter) => {
  3331. const number = parseFloat(value);
  3332. return endsWith(value, '%') && name(cell) !== 'table' ? convert(cell, number, getter, setter) : number;
  3333. };
  3334. const getTotalHeight = (cell) => {
  3335. const value = getHeightValue(cell);
  3336. if (!value) {
  3337. return get$8(cell);
  3338. }
  3339. return normalizePixelSize(value, cell, get$8, setHeight);
  3340. };
  3341. const get$2 = (cell, type, f) => {
  3342. const v = f(cell);
  3343. const span = getSpan(cell, type);
  3344. return v / span;
  3345. };
  3346. const getRaw = (element, prop) => {
  3347. // Try to use the style first, otherwise attempt to get the value from an attribute
  3348. return getRaw$2(element, prop).orThunk(() => {
  3349. return getOpt(element, prop).map((val) => val + 'px');
  3350. });
  3351. };
  3352. const getRawWidth$1 = (element) => getRaw(element, 'width');
  3353. const getRawHeight$1 = (element) => getRaw(element, 'height');
  3354. // Get a percentage size for a percentage parent table
  3355. const getPercentageWidth = (cell) => getPercentSize(cell, get$7, getInner);
  3356. const getPixelWidth$1 = (cell) =>
  3357. // For col elements use the computed width as col elements aren't affected by borders, padding, etc...
  3358. isCol$2(cell) ? Math.round(get$7(cell)) : getRuntime(cell);
  3359. const getHeight = (cell) => {
  3360. return isRow$2(cell) ? get$8(cell) : get$2(cell, 'rowspan', getTotalHeight);
  3361. };
  3362. const getGenericWidth = (cell) => {
  3363. const width = getRawWidth$1(cell);
  3364. return width.bind((w) => parse(w, ['fixed', 'relative', 'empty']));
  3365. };
  3366. const setGenericWidth = (cell, amount, unit) => {
  3367. set$1(cell, 'width', amount + unit);
  3368. };
  3369. const getPixelTableWidth = (table) => get$7(table) + 'px';
  3370. const getPixelTableHeight = (table) => get$8(table) + 'px';
  3371. const getPercentTableWidth = (table) => getPercentSize(table, get$7, getInner) + '%';
  3372. const isPercentSizing$1 = (table) => getRawWidth$1(table).exists((size) => rPercentageBasedSizeRegex.test(size));
  3373. const isPixelSizing$1 = (table) => getRawWidth$1(table).exists((size) => rPixelBasedSizeRegex.test(size));
  3374. const isNoneSizing$1 = (table) => getRawWidth$1(table).isNone();
  3375. const percentageBasedSizeRegex = constant(rPercentageBasedSizeRegex);
  3376. const isCol$1 = isTag('col');
  3377. const getRawW = (cell) => {
  3378. return getRawWidth$1(cell).getOrThunk(() => getPixelWidth$1(cell) + 'px');
  3379. };
  3380. const getRawH = (cell) => {
  3381. return getRawHeight$1(cell).getOrThunk(() => getHeight(cell) + 'px');
  3382. };
  3383. const justCols = (warehouse) => map$1(Warehouse.justColumns(warehouse), (column) => Optional.from(column.element));
  3384. // Col elements don't have valid computed widths/positions in all browsers, so treat them as invalid in that case
  3385. const isValidColumn = (cell) => {
  3386. const browser = detect$2().browser;
  3387. const supportsColWidths = browser.isChromium() || browser.isFirefox();
  3388. return isCol$1(cell) ? supportsColWidths : true;
  3389. };
  3390. const getDimension = (cellOpt, index, backups, filter, getter, fallback) => cellOpt.filter(filter).fold(
  3391. // Can't just read the width of a cell, so calculate.
  3392. () => fallback(deduce(backups, index)), (cell) => getter(cell));
  3393. const getWidthFrom = (warehouse, table, getWidth, fallback) => {
  3394. // Only treat a cell as being valid for a column representation if it has a raw width, otherwise we won't be able to calculate the expected width.
  3395. // This is needed as one cell may have a width but others may not, so we need to try and use one with a specified width first.
  3396. const columnCells = columns(warehouse);
  3397. const columns$1 = Warehouse.hasColumns(warehouse) ? justCols(warehouse) : columnCells;
  3398. const backups = [Optional.some(width.edge(table))].concat(map$1(width.positions(columnCells, table), (pos) => pos.map((p) => p.x)));
  3399. // Only use the width of cells that have no column span (or colspan 1)
  3400. const colFilter = not(hasColspan);
  3401. return map$1(columns$1, (cellOption, c) => {
  3402. return getDimension(cellOption, c, backups, colFilter, (column) => {
  3403. if (isValidColumn(column)) {
  3404. return getWidth(column);
  3405. }
  3406. else {
  3407. // Invalid column so fallback to trying to get the computed width from the cell
  3408. const cell = bindFrom(columnCells[c], identity);
  3409. return getDimension(cell, c, backups, colFilter, (cell) => fallback(Optional.some(Math.round(get$7(cell)))), fallback);
  3410. }
  3411. }, fallback);
  3412. });
  3413. };
  3414. const getDeduced = (deduced) => {
  3415. return deduced.map((d) => {
  3416. return d + 'px';
  3417. }).getOr('');
  3418. };
  3419. const getRawWidths = (warehouse, table) => {
  3420. return getWidthFrom(warehouse, table, getRawW, getDeduced);
  3421. };
  3422. const getPercentageWidths = (warehouse, table, tableSize) => {
  3423. return getWidthFrom(warehouse, table, getPercentageWidth, (deduced) => {
  3424. return deduced.fold(() => {
  3425. return tableSize.minCellWidth();
  3426. }, (cellWidth) => {
  3427. return cellWidth / tableSize.pixelWidth() * 100;
  3428. });
  3429. });
  3430. };
  3431. const getPixelWidths = (warehouse, table, tableSize) => {
  3432. return getWidthFrom(warehouse, table, getPixelWidth$1, (deduced) => {
  3433. // Minimum cell width when all else fails.
  3434. return deduced.getOrThunk(tableSize.minCellWidth);
  3435. });
  3436. };
  3437. const getHeightFrom = (warehouse, table, getHeight, fallback) => {
  3438. const rowCells = rows(warehouse);
  3439. const rows$1 = map$1(warehouse.all, (r) => Optional.some(r.element));
  3440. const backups = [Optional.some(height.edge(table))].concat(map$1(height.positions(rowCells, table), (pos) => pos.map((p) => p.y)));
  3441. return map$1(rows$1, (row, i) => getDimension(row, i, backups, always, getHeight, fallback));
  3442. };
  3443. const getPixelHeights = (warehouse, table) => {
  3444. return getHeightFrom(warehouse, table, getHeight, (deduced) => {
  3445. return deduced.getOrThunk(minHeight);
  3446. });
  3447. };
  3448. const getRawHeights = (warehouse, table) => {
  3449. return getHeightFrom(warehouse, table, getRawH, getDeduced);
  3450. };
  3451. const widthLookup = (table, getter) => () => {
  3452. // Use the actual width if attached, otherwise fallback to the raw width
  3453. if (inBody(table)) {
  3454. return getter(table);
  3455. }
  3456. else {
  3457. return parseFloat(getRaw$2(table, 'width').getOr('0'));
  3458. }
  3459. };
  3460. const noneSize = (table) => {
  3461. const getWidth = widthLookup(table, get$7);
  3462. const zero = constant(0);
  3463. const getWidths = (warehouse, tableSize) => getPixelWidths(warehouse, table, tableSize);
  3464. // Note: The 3 delta functions below return 0 to signify a change shouldn't be made
  3465. // however this is currently not used, so may need changing if ever used
  3466. return {
  3467. width: getWidth,
  3468. pixelWidth: getWidth,
  3469. getWidths,
  3470. getCellDelta: zero,
  3471. singleColumnWidth: constant([0]),
  3472. minCellWidth: zero,
  3473. setElementWidth: noop,
  3474. adjustTableWidth: noop,
  3475. isRelative: true,
  3476. label: 'none'
  3477. };
  3478. };
  3479. const percentageSize = (table) => {
  3480. const getFloatWidth = widthLookup(table, (elem) => parseFloat(getPercentTableWidth(elem)));
  3481. const getWidth = widthLookup(table, get$7);
  3482. const getCellDelta = (delta) => delta / getWidth() * 100;
  3483. // If we have one column in a percent based table, that column should be 100% of the width of the table.
  3484. const singleColumnWidth = (w, _delta) => [100 - w];
  3485. // Get the width of a 10 pixel wide cell over the width of the table as a percentage
  3486. const minCellWidth = () => minWidth() / getWidth() * 100;
  3487. const adjustTableWidth = (delta) => {
  3488. const currentWidth = getFloatWidth();
  3489. const change = delta / 100 * currentWidth;
  3490. const newWidth = currentWidth + change;
  3491. setPercentageWidth(table, newWidth);
  3492. };
  3493. const getWidths = (warehouse, tableSize) => getPercentageWidths(warehouse, table, tableSize);
  3494. return {
  3495. width: getFloatWidth,
  3496. pixelWidth: getWidth,
  3497. getWidths,
  3498. getCellDelta,
  3499. singleColumnWidth,
  3500. minCellWidth,
  3501. setElementWidth: setPercentageWidth,
  3502. adjustTableWidth,
  3503. isRelative: true,
  3504. label: 'percent'
  3505. };
  3506. };
  3507. const pixelSize = (table) => {
  3508. const getWidth = widthLookup(table, get$7);
  3509. const getCellDelta = identity;
  3510. const singleColumnWidth = (w, delta) => {
  3511. const newNext = Math.max(minWidth(), w + delta);
  3512. return [newNext - w];
  3513. };
  3514. const adjustTableWidth = (delta) => {
  3515. const newWidth = getWidth() + delta;
  3516. setPixelWidth(table, newWidth);
  3517. };
  3518. const getWidths = (warehouse, tableSize) => getPixelWidths(warehouse, table, tableSize);
  3519. return {
  3520. width: getWidth,
  3521. pixelWidth: getWidth,
  3522. getWidths,
  3523. getCellDelta,
  3524. singleColumnWidth,
  3525. minCellWidth: minWidth,
  3526. setElementWidth: setPixelWidth,
  3527. adjustTableWidth,
  3528. isRelative: false,
  3529. label: 'pixel'
  3530. };
  3531. };
  3532. const chooseSize = (element, width) => {
  3533. const percentMatch = percentageBasedSizeRegex().exec(width);
  3534. if (percentMatch !== null) {
  3535. return percentageSize(element);
  3536. }
  3537. else {
  3538. return pixelSize(element);
  3539. }
  3540. };
  3541. const getTableSize = (table) => {
  3542. const width = getRawWidth$1(table);
  3543. return width.fold(() => noneSize(table), (w) => chooseSize(table, w));
  3544. };
  3545. const TableSize = {
  3546. getTableSize,
  3547. pixelSize,
  3548. percentageSize,
  3549. noneSize
  3550. };
  3551. const setIfNot = (element, property, value, ignore) => {
  3552. if (value === ignore) {
  3553. remove$6(element, property);
  3554. }
  3555. else {
  3556. set$2(element, property, value);
  3557. }
  3558. };
  3559. const insert$1 = (table, selector, element) => {
  3560. last$2(children(table, selector)).fold(() => prepend(table, element), (child) => after$4(child, element));
  3561. };
  3562. const generateSection = (table, sectionName) => {
  3563. const section = child(table, sectionName).getOrThunk(() => {
  3564. const newSection = SugarElement.fromTag(sectionName, owner(table).dom);
  3565. if (sectionName === 'thead') {
  3566. insert$1(table, 'caption,colgroup', newSection);
  3567. }
  3568. else if (sectionName === 'colgroup') {
  3569. insert$1(table, 'caption', newSection);
  3570. }
  3571. else {
  3572. append$1(table, newSection);
  3573. }
  3574. return newSection;
  3575. });
  3576. empty(section);
  3577. return section;
  3578. };
  3579. const render$1 = (table, grid) => {
  3580. const newRows = [];
  3581. const newCells = [];
  3582. const syncRows = (gridSection) => map$1(gridSection, (row) => {
  3583. if (row.isNew) {
  3584. newRows.push(row.element);
  3585. }
  3586. const tr = row.element;
  3587. empty(tr);
  3588. each$2(row.cells, (cell) => {
  3589. if (cell.isNew) {
  3590. newCells.push(cell.element);
  3591. }
  3592. setIfNot(cell.element, 'colspan', cell.colspan, 1);
  3593. setIfNot(cell.element, 'rowspan', cell.rowspan, 1);
  3594. append$1(tr, cell.element);
  3595. });
  3596. return tr;
  3597. });
  3598. // Assumption we should only ever have 1 colgroup. The spec allows for multiple, however it's currently unsupported
  3599. const syncColGroup = (gridSection) => bind$2(gridSection, (colGroup) => map$1(colGroup.cells, (col) => {
  3600. setIfNot(col.element, 'span', col.colspan, 1);
  3601. return col.element;
  3602. }));
  3603. const renderSection = (gridSection, sectionName) => {
  3604. const section = generateSection(table, sectionName);
  3605. const sync = sectionName === 'colgroup' ? syncColGroup : syncRows;
  3606. const sectionElems = sync(gridSection);
  3607. append(section, sectionElems);
  3608. };
  3609. const removeSection = (sectionName) => {
  3610. child(table, sectionName).each(remove$5);
  3611. };
  3612. const renderOrRemoveSection = (gridSection, sectionName) => {
  3613. if (gridSection.length > 0) {
  3614. renderSection(gridSection, sectionName);
  3615. }
  3616. else {
  3617. removeSection(sectionName);
  3618. }
  3619. };
  3620. const headSection = [];
  3621. const bodySection = [];
  3622. const footSection = [];
  3623. const columnGroupsSection = [];
  3624. each$2(grid, (row) => {
  3625. switch (row.section) {
  3626. case 'thead':
  3627. headSection.push(row);
  3628. break;
  3629. case 'tbody':
  3630. bodySection.push(row);
  3631. break;
  3632. case 'tfoot':
  3633. footSection.push(row);
  3634. break;
  3635. case 'colgroup':
  3636. columnGroupsSection.push(row);
  3637. break;
  3638. }
  3639. });
  3640. renderOrRemoveSection(columnGroupsSection, 'colgroup');
  3641. renderOrRemoveSection(headSection, 'thead');
  3642. renderOrRemoveSection(bodySection, 'tbody');
  3643. renderOrRemoveSection(footSection, 'tfoot');
  3644. return {
  3645. newRows,
  3646. newCells
  3647. };
  3648. };
  3649. const copy = (grid) => map$1(grid, (row) => {
  3650. // Shallow copy the row element
  3651. const tr = shallow(row.element);
  3652. each$2(row.cells, (cell) => {
  3653. const clonedCell = deep(cell.element);
  3654. setIfNot(clonedCell, 'colspan', cell.colspan, 1);
  3655. setIfNot(clonedCell, 'rowspan', cell.rowspan, 1);
  3656. append$1(tr, clonedCell);
  3657. });
  3658. return tr;
  3659. });
  3660. const getColumn = (grid, index) => {
  3661. return map$1(grid, (row) => {
  3662. return getCell(row, index);
  3663. });
  3664. };
  3665. const getRow = (grid, index) => {
  3666. return grid[index];
  3667. };
  3668. const findDiff = (xs, comp) => {
  3669. if (xs.length === 0) {
  3670. return 0;
  3671. }
  3672. const first = xs[0];
  3673. const index = findIndex(xs, (x) => {
  3674. return !comp(first.element, x.element);
  3675. });
  3676. return index.getOr(xs.length);
  3677. };
  3678. /*
  3679. * grid is the grid
  3680. * row is the row index into the grid
  3681. * column in the column index into the grid
  3682. *
  3683. * Return
  3684. * colspan: column span of the cell at (row, column)
  3685. * rowspan: row span of the cell at (row, column)
  3686. */
  3687. const subgrid = (grid, row, column, comparator) => {
  3688. const gridRow = getRow(grid, row);
  3689. const isColRow = gridRow.section === 'colgroup';
  3690. const colspan = findDiff(gridRow.cells.slice(column), comparator);
  3691. const rowspan = isColRow ? 1 : findDiff(getColumn(grid.slice(row), column), comparator);
  3692. return {
  3693. colspan,
  3694. rowspan
  3695. };
  3696. };
  3697. const toDetails = (grid, comparator) => {
  3698. const seen = map$1(grid, (row) => map$1(row.cells, never));
  3699. const updateSeen = (rowIndex, columnIndex, rowspan, colspan) => {
  3700. for (let row = rowIndex; row < rowIndex + rowspan; row++) {
  3701. for (let column = columnIndex; column < columnIndex + colspan; column++) {
  3702. seen[row][column] = true;
  3703. }
  3704. }
  3705. };
  3706. return map$1(grid, (row, rowIndex) => {
  3707. const details = bind$2(row.cells, (cell, columnIndex) => {
  3708. // if we have seen this one, then skip it.
  3709. if (seen[rowIndex][columnIndex] === false) {
  3710. const result = subgrid(grid, rowIndex, columnIndex, comparator);
  3711. updateSeen(rowIndex, columnIndex, result.rowspan, result.colspan);
  3712. return [detailnew(cell.element, result.rowspan, result.colspan, cell.isNew)];
  3713. }
  3714. else {
  3715. return [];
  3716. }
  3717. });
  3718. return rowdetailnew(row.element, details, row.section, row.isNew);
  3719. });
  3720. };
  3721. const toGrid = (warehouse, generators, isNew) => {
  3722. const grid = [];
  3723. each$2(warehouse.colgroups, (colgroup) => {
  3724. const colgroupCols = [];
  3725. // This will add missing cols as well as clamp the number of cols to the max number of actual columns
  3726. // Note: Spans on cols are unsupported so clamping cols may result in a span on a col element being incorrect
  3727. for (let columnIndex = 0; columnIndex < warehouse.grid.columns; columnIndex++) {
  3728. const element = Warehouse.getColumnAt(warehouse, columnIndex)
  3729. .map((column) => elementnew(column.element, isNew, false))
  3730. .getOrThunk(() => elementnew(generators.colGap(), true, false));
  3731. colgroupCols.push(element);
  3732. }
  3733. grid.push(rowcells(colgroup.element, colgroupCols, 'colgroup', isNew));
  3734. });
  3735. for (let rowIndex = 0; rowIndex < warehouse.grid.rows; rowIndex++) {
  3736. const rowCells = [];
  3737. for (let columnIndex = 0; columnIndex < warehouse.grid.columns; columnIndex++) {
  3738. // The element is going to be the element at that position, or a newly generated gap.
  3739. const element = Warehouse.getAt(warehouse, rowIndex, columnIndex).map((item) => elementnew(item.element, isNew, item.isLocked)).getOrThunk(() => elementnew(generators.gap(), true, false));
  3740. rowCells.push(element);
  3741. }
  3742. const rowDetail = warehouse.all[rowIndex];
  3743. const row = rowcells(rowDetail.element, rowCells, rowDetail.section, isNew);
  3744. grid.push(row);
  3745. }
  3746. return grid;
  3747. };
  3748. const fromWarehouse = (warehouse, generators) => toGrid(warehouse, generators, false);
  3749. const toDetailList = (grid) => toDetails(grid, eq$1);
  3750. const findInWarehouse = (warehouse, element) => findMap(warehouse.all, (r) => find$1(r.cells, (e) => eq$1(element, e.element)));
  3751. const extractCells = (warehouse, target, predicate) => {
  3752. const details = map$1(target.selection, (cell$1) => {
  3753. return cell(cell$1)
  3754. .bind((lc) => findInWarehouse(warehouse, lc))
  3755. .filter(predicate);
  3756. });
  3757. const cells = cat(details);
  3758. return someIf(cells.length > 0, cells);
  3759. };
  3760. const run = (operation, extract, adjustment, postAction, genWrappers, table, target, generators, behaviours) => {
  3761. const warehouse = Warehouse.fromTable(table);
  3762. const tableSection = Optional.from(behaviours === null || behaviours === void 0 ? void 0 : behaviours.section).getOrThunk(TableSection.fallback);
  3763. const output = extract(warehouse, target).map((info) => {
  3764. const model = fromWarehouse(warehouse, generators);
  3765. const result = operation(model, info, eq$1, genWrappers(generators), tableSection);
  3766. const lockedColumns = getLockedColumnsFromGrid(result.grid);
  3767. const grid = toDetailList(result.grid);
  3768. return {
  3769. info,
  3770. grid,
  3771. cursor: result.cursor,
  3772. lockedColumns
  3773. };
  3774. });
  3775. return output.bind((out) => {
  3776. const newElements = render$1(table, out.grid);
  3777. const tableSizing = Optional.from(behaviours === null || behaviours === void 0 ? void 0 : behaviours.sizing).getOrThunk(() => TableSize.getTableSize(table));
  3778. const resizing = Optional.from(behaviours === null || behaviours === void 0 ? void 0 : behaviours.resize).getOrThunk(preserveTable);
  3779. adjustment(table, out.grid, out.info, { sizing: tableSizing, resize: resizing, section: tableSection });
  3780. postAction(table);
  3781. // Update locked cols attribute
  3782. remove$6(table, LOCKED_COL_ATTR);
  3783. if (out.lockedColumns.length > 0) {
  3784. set$2(table, LOCKED_COL_ATTR, out.lockedColumns.join(','));
  3785. }
  3786. return Optional.some({
  3787. cursor: out.cursor,
  3788. newRows: newElements.newRows,
  3789. newCells: newElements.newCells
  3790. });
  3791. });
  3792. };
  3793. const onPaste = (warehouse, target) => cell(target.element).bind((cell) => findInWarehouse(warehouse, cell).map((details) => {
  3794. const value = {
  3795. ...details,
  3796. generators: target.generators,
  3797. clipboard: target.clipboard
  3798. };
  3799. return value;
  3800. }));
  3801. const onPasteByEditor = (warehouse, target) => extractCells(warehouse, target, always).map((cells) => ({
  3802. cells,
  3803. generators: target.generators,
  3804. clipboard: target.clipboard
  3805. }));
  3806. const onMergable = (_warehouse, target) => target.mergable;
  3807. const onUnmergable = (_warehouse, target) => target.unmergable;
  3808. const onCells = (warehouse, target) => extractCells(warehouse, target, always);
  3809. const onUnlockedCells = (warehouse, target) => extractCells(warehouse, target, (detail) => !detail.isLocked);
  3810. const isUnlockedTableCell = (warehouse, cell) => findInWarehouse(warehouse, cell).exists((detail) => !detail.isLocked);
  3811. const allUnlocked = (warehouse, cells) => forall(cells, (cell) => isUnlockedTableCell(warehouse, cell));
  3812. // If any locked columns are present in the selection, then don't want to be able to merge
  3813. const onUnlockedMergable = (warehouse, target) => onMergable(warehouse, target).filter((mergeable) => allUnlocked(warehouse, mergeable.cells));
  3814. // If any locked columns are present in the selection, then don't want to be able to unmerge
  3815. const onUnlockedUnmergable = (warehouse, target) => onUnmergable(warehouse, target).filter((cells) => allUnlocked(warehouse, cells));
  3816. const adt$3 = Adt.generate([
  3817. { none: [] },
  3818. { only: ['index'] },
  3819. { left: ['index', 'next'] },
  3820. { middle: ['prev', 'index', 'next'] },
  3821. { right: ['prev', 'index'] }
  3822. ]);
  3823. const ColumnContext = {
  3824. ...adt$3
  3825. };
  3826. /*
  3827. * Based on the column index, identify the context
  3828. */
  3829. const neighbours = (input, index) => {
  3830. if (input.length === 0) {
  3831. return ColumnContext.none();
  3832. }
  3833. if (input.length === 1) {
  3834. return ColumnContext.only(0);
  3835. }
  3836. if (index === 0) {
  3837. return ColumnContext.left(0, 1);
  3838. }
  3839. if (index === input.length - 1) {
  3840. return ColumnContext.right(index - 1, index);
  3841. }
  3842. if (index > 0 && index < input.length - 1) {
  3843. return ColumnContext.middle(index - 1, index, index + 1);
  3844. }
  3845. return ColumnContext.none();
  3846. };
  3847. /*
  3848. * Calculate the offsets to apply to each column width (not the absolute widths themselves)
  3849. * based on a resize at column: column of step: step
  3850. */
  3851. const determine = (input, column, step, tableSize, resize) => {
  3852. const result = input.slice(0);
  3853. const context = neighbours(input, column);
  3854. const onNone = constant(map$1(result, constant(0)));
  3855. const onOnly = (index) => tableSize.singleColumnWidth(result[index], step);
  3856. const onLeft = (index, next) => resize.calcLeftEdgeDeltas(result, index, next, step, tableSize.minCellWidth(), tableSize.isRelative);
  3857. const onMiddle = (prev, index, next) => resize.calcMiddleDeltas(result, prev, index, next, step, tableSize.minCellWidth(), tableSize.isRelative);
  3858. // Applies to the last column bar
  3859. const onRight = (prev, index) => resize.calcRightEdgeDeltas(result, prev, index, step, tableSize.minCellWidth(), tableSize.isRelative);
  3860. return context.fold(onNone, onOnly, onLeft, onMiddle, onRight);
  3861. };
  3862. // Returns the sum of elements of measures in the half-open range [start, end)
  3863. // Measures is in pixels, treated as an array of integers or integers in string format.
  3864. // NOTE: beware of accumulated rounding errors over multiple columns - could result in noticeable table width changes
  3865. const total = (start, end, measures) => {
  3866. let r = 0;
  3867. for (let i = start; i < end; i++) {
  3868. r += measures[i] !== undefined ? measures[i] : 0;
  3869. }
  3870. return r;
  3871. };
  3872. // Returns an array of all cells in warehouse with updated cell-widths, using
  3873. // the array 'widths' of the representative widths of each column of the table 'warehouse'
  3874. const recalculateWidthForCells = (warehouse, widths) => {
  3875. const all = Warehouse.justCells(warehouse);
  3876. return map$1(all, (cell) => {
  3877. // width of a spanning cell is sum of widths of representative columns it spans
  3878. const width = total(cell.column, cell.column + cell.colspan, widths);
  3879. return {
  3880. element: cell.element,
  3881. width,
  3882. colspan: cell.colspan
  3883. };
  3884. });
  3885. };
  3886. const recalculateWidthForColumns = (warehouse, widths) => {
  3887. const groups = Warehouse.justColumns(warehouse);
  3888. return map$1(groups, (column, index) => ({
  3889. element: column.element,
  3890. width: widths[index],
  3891. colspan: column.colspan
  3892. }));
  3893. };
  3894. const matchRowHeight = (warehouse, heights) => {
  3895. return map$1(warehouse.all, (row, i) => {
  3896. return {
  3897. element: row.element,
  3898. height: heights[i]
  3899. };
  3900. });
  3901. };
  3902. const sumUp = (newSize) => foldr(newSize, (b, a) => b + a, 0);
  3903. const recalculate = (warehouse, widths) => {
  3904. if (Warehouse.hasColumns(warehouse)) {
  3905. return recalculateWidthForColumns(warehouse, widths);
  3906. }
  3907. else {
  3908. return recalculateWidthForCells(warehouse, widths);
  3909. }
  3910. };
  3911. const recalculateAndApply = (warehouse, widths, tableSize) => {
  3912. // Set the width of each cell based on the column widths
  3913. const newSizes = recalculate(warehouse, widths);
  3914. each$2(newSizes, (cell) => {
  3915. tableSize.setElementWidth(cell.element, cell.width);
  3916. });
  3917. };
  3918. const adjustWidth = (table, delta, index, resizing, tableSize) => {
  3919. const warehouse = Warehouse.fromTable(table);
  3920. const step = tableSize.getCellDelta(delta);
  3921. const widths = tableSize.getWidths(warehouse, tableSize);
  3922. const isLastColumn = index === warehouse.grid.columns - 1;
  3923. const clampedStep = resizing.clampTableDelta(widths, index, step, tableSize.minCellWidth(), isLastColumn);
  3924. // Calculate all of the new widths for columns
  3925. const deltas = determine(widths, index, clampedStep, tableSize, resizing);
  3926. const newWidths = map$1(deltas, (dx, i) => dx + widths[i]);
  3927. recalculateAndApply(warehouse, newWidths, tableSize);
  3928. resizing.resizeTable(tableSize.adjustTableWidth, clampedStep, isLastColumn);
  3929. };
  3930. const adjustHeight = (table, delta, index) => {
  3931. const warehouse = Warehouse.fromTable(table);
  3932. const heights = getPixelHeights(warehouse, table);
  3933. const newHeights = map$1(heights, (dy, i) => index === i ? Math.max(delta + dy, minHeight()) : dy);
  3934. const newRowSizes = matchRowHeight(warehouse, newHeights);
  3935. each$2(newRowSizes, (row) => {
  3936. setHeight(row.element, row.height);
  3937. });
  3938. each$2(Warehouse.justCells(warehouse), (cell) => {
  3939. removeHeight(cell.element);
  3940. });
  3941. const total = sumUp(newHeights);
  3942. setHeight(table, total);
  3943. };
  3944. // Using the width of the added/removed columns gathered on extraction (pixelDelta), get and apply the new column sizes and overall table width delta
  3945. const adjustAndRedistributeWidths$1 = (_table, list, details, tableSize, resizeBehaviour) => {
  3946. const warehouse = Warehouse.generate(list);
  3947. const sizes = tableSize.getWidths(warehouse, tableSize);
  3948. const tablePixelWidth = tableSize.pixelWidth();
  3949. const { newSizes, delta } = resizeBehaviour.calcRedestributedWidths(sizes, tablePixelWidth, details.pixelDelta, tableSize.isRelative);
  3950. recalculateAndApply(warehouse, newSizes, tableSize);
  3951. tableSize.adjustTableWidth(delta);
  3952. };
  3953. // Ensure that the width of table cells match the passed in table information.
  3954. const adjustWidthTo = (_table, list, _info, tableSize) => {
  3955. const warehouse = Warehouse.generate(list);
  3956. const widths = tableSize.getWidths(warehouse, tableSize);
  3957. recalculateAndApply(warehouse, widths, tableSize);
  3958. };
  3959. const halve = (main, other) => {
  3960. // Only set width on the new cell if we have a colspan of 1 (or no colspan) as we can only safely do that for cells
  3961. // that are a single column, since we don't know the individual column widths for a cell with a colspan.
  3962. // Instead, we'll rely on the adjustments/postAction logic to set the widths based on other cells in the column
  3963. if (!hasColspan(main)) {
  3964. const width = getGenericWidth(main);
  3965. width.each((w) => {
  3966. const newWidth = w.value / 2;
  3967. setGenericWidth(main, newWidth, w.unit);
  3968. setGenericWidth(other, newWidth, w.unit);
  3969. });
  3970. }
  3971. };
  3972. const constrainSpan = (element, property, value) => {
  3973. const currentColspan = getAttrValue(element, property, 1);
  3974. if (value === 1 || currentColspan <= 1) {
  3975. remove$6(element, property);
  3976. }
  3977. else {
  3978. set$2(element, property, Math.min(value, currentColspan));
  3979. }
  3980. };
  3981. const isColInRange = (minColRange, maxColRange) => (cell) => {
  3982. const endCol = cell.column + cell.colspan - 1;
  3983. const startCol = cell.column;
  3984. return endCol >= minColRange && startCol < maxColRange;
  3985. };
  3986. const generateColGroup = (house, minColRange, maxColRange) => {
  3987. if (Warehouse.hasColumns(house)) {
  3988. const colsToCopy = filter$2(Warehouse.justColumns(house), isColInRange(minColRange, maxColRange));
  3989. const copiedCols = map$1(colsToCopy, (c) => {
  3990. const clonedCol = deep(c.element);
  3991. constrainSpan(clonedCol, 'span', maxColRange - minColRange);
  3992. return clonedCol;
  3993. });
  3994. const fakeColgroup = SugarElement.fromTag('colgroup');
  3995. append(fakeColgroup, copiedCols);
  3996. return [fakeColgroup];
  3997. }
  3998. else {
  3999. return [];
  4000. }
  4001. };
  4002. const generateRows = (house, minColRange, maxColRange) => map$1(house.all, (row) => {
  4003. const cellsToCopy = filter$2(row.cells, isColInRange(minColRange, maxColRange));
  4004. const copiedCells = map$1(cellsToCopy, (cell) => {
  4005. const clonedCell = deep(cell.element);
  4006. constrainSpan(clonedCell, 'colspan', maxColRange - minColRange);
  4007. return clonedCell;
  4008. });
  4009. const fakeTR = SugarElement.fromTag('tr');
  4010. append(fakeTR, copiedCells);
  4011. return fakeTR;
  4012. });
  4013. const copyCols = (table, target) => {
  4014. const house = Warehouse.fromTable(table);
  4015. const details = onUnlockedCells(house, target);
  4016. return details.map((selectedCells) => {
  4017. const lastSelectedCell = selectedCells[selectedCells.length - 1];
  4018. const minColRange = selectedCells[0].column;
  4019. const maxColRange = lastSelectedCell.column + lastSelectedCell.colspan;
  4020. const fakeColGroups = generateColGroup(house, minColRange, maxColRange);
  4021. const fakeRows = generateRows(house, minColRange, maxColRange);
  4022. return [...fakeColGroups, ...fakeRows];
  4023. });
  4024. };
  4025. const copyRows = (table, target, generators) => {
  4026. const warehouse = Warehouse.fromTable(table);
  4027. // Cannot use onUnlockedCells like extractor here as if only cells in a locked column are selected, then this will be Optional.none and
  4028. // there is now no way of knowing which rows are selected
  4029. const details = onCells(warehouse, target);
  4030. return details.bind((selectedCells) => {
  4031. const grid = toGrid(warehouse, generators, false);
  4032. const rows = extractGridDetails(grid).rows;
  4033. const slicedGrid = rows.slice(selectedCells[0].row, selectedCells[selectedCells.length - 1].row + selectedCells[selectedCells.length - 1].rowspan);
  4034. // Remove any locked cells from the copied grid rows
  4035. const filteredGrid = bind$2(slicedGrid, (row) => {
  4036. const newCells = filter$2(row.cells, (cell) => !cell.isLocked);
  4037. return newCells.length > 0 ? [{ ...row, cells: newCells }] : [];
  4038. });
  4039. const slicedDetails = toDetailList(filteredGrid);
  4040. return someIf(slicedDetails.length > 0, slicedDetails);
  4041. }).map((slicedDetails) => copy(slicedDetails));
  4042. };
  4043. const statsStruct = (minRow, minCol, maxRow, maxCol, allCells, selectedCells) => ({
  4044. minRow,
  4045. minCol,
  4046. maxRow,
  4047. maxCol,
  4048. allCells,
  4049. selectedCells,
  4050. });
  4051. const findSelectedStats = (house, isSelected) => {
  4052. const totalColumns = house.grid.columns;
  4053. const totalRows = house.grid.rows;
  4054. /* Refactor into a method returning a struct to hide the mutation */
  4055. let minRow = totalRows;
  4056. let minCol = totalColumns;
  4057. let maxRow = 0;
  4058. let maxCol = 0;
  4059. const allCells = [];
  4060. const selectedCells = [];
  4061. each$1(house.access, (detail) => {
  4062. allCells.push(detail);
  4063. if (isSelected(detail)) {
  4064. selectedCells.push(detail);
  4065. const startRow = detail.row;
  4066. const endRow = startRow + detail.rowspan - 1;
  4067. const startCol = detail.column;
  4068. const endCol = startCol + detail.colspan - 1;
  4069. if (startRow < minRow) {
  4070. minRow = startRow;
  4071. }
  4072. else if (endRow > maxRow) {
  4073. maxRow = endRow;
  4074. }
  4075. if (startCol < minCol) {
  4076. minCol = startCol;
  4077. }
  4078. else if (endCol > maxCol) {
  4079. maxCol = endCol;
  4080. }
  4081. }
  4082. });
  4083. return statsStruct(minRow, minCol, maxRow, maxCol, allCells, selectedCells);
  4084. };
  4085. const makeCell = (list, seenSelected, rowIndex) => {
  4086. // no need to check bounds, as anything outside this index is removed in the nested for loop
  4087. const row = list[rowIndex].element;
  4088. const td = SugarElement.fromTag('td');
  4089. append$1(td, SugarElement.fromTag('br'));
  4090. const f = seenSelected ? append$1 : prepend;
  4091. f(row, td);
  4092. };
  4093. const fillInGaps = (list, house, stats, isSelected) => {
  4094. const rows = filter$2(list, (row) => row.section !== 'colgroup');
  4095. const totalColumns = house.grid.columns;
  4096. const totalRows = house.grid.rows;
  4097. // unselected cells have been deleted, now fill in the gaps in the model
  4098. for (let i = 0; i < totalRows; i++) {
  4099. let seenSelected = false;
  4100. for (let j = 0; j < totalColumns; j++) {
  4101. if (!(i < stats.minRow || i > stats.maxRow || j < stats.minCol || j > stats.maxCol)) {
  4102. // if there is a hole in the table itself, or it's an unselected position, we need a cell
  4103. const needCell = Warehouse.getAt(house, i, j).filter(isSelected).isNone();
  4104. if (needCell) {
  4105. makeCell(rows, seenSelected, i);
  4106. }
  4107. else {
  4108. seenSelected = true;
  4109. }
  4110. }
  4111. }
  4112. }
  4113. };
  4114. const clean = (replica, stats, house, widthDelta) => {
  4115. // remove columns that are not in the new table
  4116. each$1(house.columns, (col) => {
  4117. if (col.column < stats.minCol || col.column > stats.maxCol) {
  4118. remove$5(col.element);
  4119. }
  4120. });
  4121. // can't use :empty selector as that will not include TRs made up of whitespace
  4122. const emptyRows = filter$2(firstLayer(replica, 'tr'), (row) =>
  4123. // there is no sugar method for this, and Traverse.children() does too much processing
  4124. row.dom.childElementCount === 0);
  4125. each$2(emptyRows, remove$5);
  4126. // If there is only one column, or only one row, delete all the colspan/rowspan
  4127. if (stats.minCol === stats.maxCol || stats.minRow === stats.maxRow) {
  4128. each$2(firstLayer(replica, 'th,td'), (cell) => {
  4129. remove$6(cell, 'rowspan');
  4130. remove$6(cell, 'colspan');
  4131. });
  4132. }
  4133. // Remove any attributes that should not be in the replicated table
  4134. remove$6(replica, LOCKED_COL_ATTR);
  4135. // TODO: TINY-6944 - need to figure out a better way of handling this
  4136. remove$6(replica, 'data-snooker-col-series'); // For advtable series column feature
  4137. const tableSize = TableSize.getTableSize(replica);
  4138. tableSize.adjustTableWidth(widthDelta);
  4139. // TODO TINY-6863: If using relative widths, ensure cell and column widths are redistributed
  4140. };
  4141. const getTableWidthDelta = (table, warehouse, tableSize, stats) => {
  4142. // short circuit entire table selected
  4143. if (stats.minCol === 0 && warehouse.grid.columns === stats.maxCol + 1) {
  4144. return 0;
  4145. }
  4146. const colWidths = getPixelWidths(warehouse, table, tableSize);
  4147. const allColsWidth = foldl(colWidths, (acc, width) => acc + width, 0);
  4148. const selectedColsWidth = foldl(colWidths.slice(stats.minCol, stats.maxCol + 1), (acc, width) => acc + width, 0);
  4149. const newWidth = (selectedColsWidth / allColsWidth) * tableSize.pixelWidth();
  4150. const delta = newWidth - tableSize.pixelWidth();
  4151. return tableSize.getCellDelta(delta);
  4152. };
  4153. const extract$1 = (table, selectedSelector) => {
  4154. const isSelected = (detail) => is$1(detail.element, selectedSelector);
  4155. const replica = deep(table);
  4156. const list = fromTable$1(replica);
  4157. const tableSize = TableSize.getTableSize(table);
  4158. const replicaHouse = Warehouse.generate(list);
  4159. const replicaStats = findSelectedStats(replicaHouse, isSelected);
  4160. // remove unselected cells
  4161. const selector = 'th:not(' + selectedSelector + ')' + ',td:not(' + selectedSelector + ')';
  4162. const unselectedCells = filterFirstLayer(replica, 'th,td', (cell) => is$1(cell, selector));
  4163. each$2(unselectedCells, remove$5);
  4164. fillInGaps(list, replicaHouse, replicaStats, isSelected);
  4165. const house = Warehouse.fromTable(table);
  4166. const widthDelta = getTableWidthDelta(table, house, tableSize, replicaStats);
  4167. clean(replica, replicaStats, replicaHouse, widthDelta);
  4168. return replica;
  4169. };
  4170. const isCol = isTag('col');
  4171. const isColgroup = isTag('colgroup');
  4172. const isRow$1 = (element) => name(element) === 'tr' || isColgroup(element);
  4173. const elementToData = (element) => {
  4174. const colspan = getAttrValue(element, 'colspan', 1);
  4175. const rowspan = getAttrValue(element, 'rowspan', 1);
  4176. return {
  4177. element,
  4178. colspan,
  4179. rowspan
  4180. };
  4181. };
  4182. // note that `toData` seems to be only for testing
  4183. const modification = (generators, toData = elementToData) => {
  4184. const nuCell = (data) => isCol(data.element) ? generators.col(data) : generators.cell(data);
  4185. const nuRow = (data) => isColgroup(data.element) ? generators.colgroup(data) : generators.row(data);
  4186. const add = (element) => {
  4187. if (isRow$1(element)) {
  4188. return nuRow({ element });
  4189. }
  4190. else {
  4191. const cell = element;
  4192. const replacement = nuCell(toData(cell));
  4193. recent = Optional.some({ item: cell, replacement });
  4194. return replacement;
  4195. }
  4196. };
  4197. let recent = Optional.none();
  4198. const getOrInit = (element, comparator) => {
  4199. return recent.fold(() => {
  4200. return add(element);
  4201. }, (p) => {
  4202. return comparator(element, p.item) ? p.replacement : add(element);
  4203. });
  4204. };
  4205. return {
  4206. getOrInit
  4207. };
  4208. };
  4209. const transform$1 = (tag) => {
  4210. return (generators) => {
  4211. const list = [];
  4212. const find = (element, comparator) => {
  4213. return find$1(list, (x) => {
  4214. return comparator(x.item, element);
  4215. });
  4216. };
  4217. const makeNew = (element) => {
  4218. // Ensure scope is never set on a td element as it's a deprecated attribute
  4219. const attrs = tag === 'td' ? { scope: null } : {};
  4220. const cell = generators.replace(element, tag, attrs);
  4221. list.push({
  4222. item: element,
  4223. sub: cell
  4224. });
  4225. return cell;
  4226. };
  4227. const replaceOrInit = (element, comparator) => {
  4228. if (isRow$1(element) || isCol(element)) {
  4229. return element;
  4230. }
  4231. else {
  4232. const cell = element;
  4233. return find(cell, comparator).fold(() => {
  4234. return makeNew(cell);
  4235. }, (p) => {
  4236. return comparator(element, p.item) ? p.sub : makeNew(cell);
  4237. });
  4238. }
  4239. };
  4240. return {
  4241. replaceOrInit
  4242. };
  4243. };
  4244. };
  4245. const getScopeAttribute = (cell) => getOpt(cell, 'scope').map(
  4246. // Attribute can be col, colgroup, row, and rowgroup.
  4247. // As col and colgroup are to be treated as if they are the same, lob off everything after the first three characters and there is no difference.
  4248. (attribute) => attribute.substr(0, 3));
  4249. const merging = (generators) => {
  4250. const unmerge = (cell) => {
  4251. const scope = getScopeAttribute(cell);
  4252. scope.each((attribute) => set$2(cell, 'scope', attribute));
  4253. return () => {
  4254. const raw = generators.cell({
  4255. element: cell,
  4256. colspan: 1,
  4257. rowspan: 1
  4258. });
  4259. // Remove any width calculations because they are no longer relevant.
  4260. remove$4(raw, 'width');
  4261. remove$4(cell, 'width');
  4262. scope.each((attribute) => set$2(raw, 'scope', attribute));
  4263. return raw;
  4264. };
  4265. };
  4266. const merge = (cells) => {
  4267. const getScopeProperty = () => {
  4268. const stringAttributes = cat(map$1(cells, getScopeAttribute));
  4269. if (stringAttributes.length === 0) {
  4270. return Optional.none();
  4271. }
  4272. else {
  4273. const baseScope = stringAttributes[0];
  4274. const scopes = ['row', 'col'];
  4275. const isMixed = exists(stringAttributes, (attribute) => {
  4276. return attribute !== baseScope && contains$2(scopes, attribute);
  4277. });
  4278. return isMixed ? Optional.none() : Optional.from(baseScope);
  4279. }
  4280. };
  4281. remove$4(cells[0], 'width');
  4282. getScopeProperty().fold(() => remove$6(cells[0], 'scope'), (attribute) => set$2(cells[0], 'scope', attribute + 'group'));
  4283. return constant(cells[0]);
  4284. };
  4285. return {
  4286. unmerge,
  4287. merge
  4288. };
  4289. };
  4290. const Generators = {
  4291. modification,
  4292. transform: transform$1,
  4293. merging
  4294. };
  4295. const getUpOrLeftCells = (grid, selectedCells) => {
  4296. // Get rows up or at the row of the bottom right cell
  4297. const upGrid = grid.slice(0, selectedCells[selectedCells.length - 1].row + 1);
  4298. const upDetails = toDetailList(upGrid);
  4299. // Get an array of the cells up or to the left of the bottom right cell
  4300. return bind$2(upDetails, (detail) => {
  4301. const slicedCells = detail.cells.slice(0, selectedCells[selectedCells.length - 1].column + 1);
  4302. return map$1(slicedCells, (cell) => cell.element);
  4303. });
  4304. };
  4305. const getDownOrRightCells = (grid, selectedCells) => {
  4306. // Get rows down or at the row of the top left cell (including rowspans)
  4307. const downGrid = grid.slice(selectedCells[0].row + selectedCells[0].rowspan - 1, grid.length);
  4308. const downDetails = toDetailList(downGrid);
  4309. // Get an array of the cells down or to the right of the bottom right cell
  4310. return bind$2(downDetails, (detail) => {
  4311. const slicedCells = detail.cells.slice(selectedCells[0].column + selectedCells[0].colspan - 1, detail.cells.length);
  4312. return map$1(slicedCells, (cell) => cell.element);
  4313. });
  4314. };
  4315. const getOtherCells = (table, target, generators) => {
  4316. const warehouse = Warehouse.fromTable(table);
  4317. const details = onCells(warehouse, target);
  4318. return details.map((selectedCells) => {
  4319. const grid = toGrid(warehouse, generators, false);
  4320. const { rows } = extractGridDetails(grid);
  4321. const upOrLeftCells = getUpOrLeftCells(rows, selectedCells);
  4322. const downOrRightCells = getDownOrRightCells(rows, selectedCells);
  4323. return {
  4324. upOrLeftCells,
  4325. downOrRightCells
  4326. };
  4327. });
  4328. };
  4329. const only = (element, isResizable) => {
  4330. // If element is a 'document', use the document element ('HTML' tag) for appending.
  4331. const parent = isDocument(element) ? documentElement(element) : element;
  4332. return {
  4333. parent: constant(parent),
  4334. view: constant(element),
  4335. dragContainer: constant(parent),
  4336. origin: constant(SugarPosition(0, 0)),
  4337. isResizable
  4338. };
  4339. };
  4340. const detached = (editable, chrome, isResizable) => {
  4341. const origin = () => absolute(chrome);
  4342. return {
  4343. parent: constant(chrome),
  4344. view: constant(editable),
  4345. dragContainer: constant(chrome),
  4346. origin,
  4347. isResizable
  4348. };
  4349. };
  4350. const body = (editable, isResizable) => {
  4351. return {
  4352. parent: constant(editable),
  4353. view: constant(editable),
  4354. dragContainer: constant(editable),
  4355. origin: () => absolute(editable),
  4356. isResizable
  4357. };
  4358. };
  4359. const ResizeWire = {
  4360. only,
  4361. detached,
  4362. body
  4363. };
  4364. const adt$2 = Adt.generate([
  4365. { invalid: ['raw'] },
  4366. { pixels: ['value'] },
  4367. { percent: ['value'] }
  4368. ]);
  4369. const validateFor = (suffix, type, value) => {
  4370. const rawAmount = value.substring(0, value.length - suffix.length);
  4371. const amount = parseFloat(rawAmount);
  4372. return rawAmount === amount.toString() ? type(amount) : adt$2.invalid(value);
  4373. };
  4374. const from = (value) => {
  4375. if (endsWith(value, '%')) {
  4376. return validateFor('%', adt$2.percent, value);
  4377. }
  4378. if (endsWith(value, 'px')) {
  4379. return validateFor('px', adt$2.pixels, value);
  4380. }
  4381. return adt$2.invalid(value);
  4382. };
  4383. const Size = {
  4384. ...adt$2,
  4385. from
  4386. };
  4387. // Convert all column widths to percent.
  4388. const redistributeToPercent = (widths, totalWidth) => {
  4389. return map$1(widths, (w) => {
  4390. const colType = Size.from(w);
  4391. return colType.fold(() => {
  4392. return w;
  4393. }, (px) => {
  4394. const ratio = px / totalWidth * 100;
  4395. return ratio + '%';
  4396. }, (pc) => {
  4397. return pc + '%';
  4398. });
  4399. });
  4400. };
  4401. const redistributeToPx = (widths, totalWidth, newTotalWidth) => {
  4402. const scale = newTotalWidth / totalWidth;
  4403. return map$1(widths, (w) => {
  4404. const colType = Size.from(w);
  4405. return colType.fold(() => {
  4406. return w;
  4407. }, (px) => {
  4408. return (px * scale) + 'px';
  4409. }, (pc) => {
  4410. return (pc / 100 * newTotalWidth) + 'px';
  4411. });
  4412. });
  4413. };
  4414. const redistributeEmpty = (newWidthType, columns) => {
  4415. const f = newWidthType.fold(() => constant(''), (pixels) => {
  4416. const num = pixels / columns;
  4417. return constant(num + 'px');
  4418. }, () => {
  4419. const num = 100 / columns;
  4420. return constant(num + '%');
  4421. });
  4422. return range$1(columns, f);
  4423. };
  4424. const redistributeValues = (newWidthType, widths, totalWidth) => {
  4425. return newWidthType.fold(() => {
  4426. return widths;
  4427. }, (px) => {
  4428. return redistributeToPx(widths, totalWidth, px);
  4429. }, (_pc) => {
  4430. return redistributeToPercent(widths, totalWidth);
  4431. });
  4432. };
  4433. const redistribute$1 = (widths, totalWidth, newWidth) => {
  4434. const newType = Size.from(newWidth);
  4435. const floats = forall(widths, (s) => {
  4436. return s === '0px';
  4437. }) ? redistributeEmpty(newType, widths.length) : redistributeValues(newType, widths, totalWidth);
  4438. return normalize(floats);
  4439. };
  4440. const sum = (values, fallback) => {
  4441. if (values.length === 0) {
  4442. return fallback;
  4443. }
  4444. return foldr(values, (rest, v) => {
  4445. return Size.from(v).fold(constant(0), identity, identity) + rest;
  4446. }, 0);
  4447. };
  4448. const roundDown = (num, unit) => {
  4449. const floored = Math.floor(num);
  4450. return { value: floored + unit, remainder: num - floored };
  4451. };
  4452. const add = (value, amount) => {
  4453. return Size.from(value).fold(constant(value), (px) => {
  4454. return (px + amount) + 'px';
  4455. }, (pc) => {
  4456. return (pc + amount) + '%';
  4457. });
  4458. };
  4459. const normalize = (values) => {
  4460. if (values.length === 0) {
  4461. return values;
  4462. }
  4463. const scan = foldr(values, (rest, value) => {
  4464. const info = Size.from(value).fold(() => ({ value, remainder: 0 }), (num) => roundDown(num, 'px'), (num) => ({ value: num + '%', remainder: 0 }));
  4465. return {
  4466. output: [info.value].concat(rest.output),
  4467. remainder: rest.remainder + info.remainder
  4468. };
  4469. }, { output: [], remainder: 0 });
  4470. const r = scan.output;
  4471. return r.slice(0, r.length - 1).concat([add(r[r.length - 1], Math.round(scan.remainder))]);
  4472. };
  4473. const validate = Size.from;
  4474. const redistributeToW = (newWidths, cells, unit) => {
  4475. each$2(cells, (cell) => {
  4476. const widths = newWidths.slice(cell.column, cell.colspan + cell.column);
  4477. const w = sum(widths, minWidth());
  4478. set$1(cell.element, 'width', w + unit);
  4479. });
  4480. };
  4481. const redistributeToColumns = (newWidths, columns, unit) => {
  4482. each$2(columns, (column, index) => {
  4483. const width = sum([newWidths[index]], minWidth());
  4484. set$1(column.element, 'width', width + unit);
  4485. });
  4486. };
  4487. const redistributeToH = (newHeights, rows, cells) => {
  4488. each$2(cells, (cell) => {
  4489. remove$4(cell.element, 'height');
  4490. });
  4491. each$2(rows, (row, i) => {
  4492. set$1(row.element, 'height', newHeights[i]);
  4493. });
  4494. };
  4495. const getUnit = (newSize) => {
  4496. return validate(newSize).fold(constant('px'), constant('px'), constant('%'));
  4497. };
  4498. // Procedure to resize table dimensions to optWidth x optHeight and redistribute cell and row dimensions.
  4499. // Updates CSS of the table, rows, and cells.
  4500. const redistribute = (table, optWidth, optHeight) => {
  4501. const warehouse = Warehouse.fromTable(table);
  4502. const rows = warehouse.all;
  4503. const cells = Warehouse.justCells(warehouse);
  4504. const columns = Warehouse.justColumns(warehouse);
  4505. optWidth.each((newWidth) => {
  4506. const widthUnit = getUnit(newWidth);
  4507. const totalWidth = get$7(table);
  4508. const oldWidths = getRawWidths(warehouse, table);
  4509. const nuWidths = redistribute$1(oldWidths, totalWidth, newWidth);
  4510. if (Warehouse.hasColumns(warehouse)) {
  4511. redistributeToColumns(nuWidths, columns, widthUnit);
  4512. }
  4513. else {
  4514. redistributeToW(nuWidths, cells, widthUnit);
  4515. }
  4516. set$1(table, 'width', newWidth);
  4517. });
  4518. optHeight.each((newHeight) => {
  4519. const totalHeight = get$8(table);
  4520. const oldHeights = getRawHeights(warehouse, table);
  4521. const nuHeights = redistribute$1(oldHeights, totalHeight, newHeight);
  4522. redistributeToH(nuHeights, rows, cells);
  4523. set$1(table, 'height', newHeight);
  4524. });
  4525. };
  4526. const isPercentSizing = isPercentSizing$1;
  4527. const isPixelSizing = isPixelSizing$1;
  4528. const isNoneSizing = isNoneSizing$1;
  4529. var TagBoundaries = [
  4530. 'body',
  4531. 'p',
  4532. 'div',
  4533. 'article',
  4534. 'aside',
  4535. 'figcaption',
  4536. 'figure',
  4537. 'footer',
  4538. 'header',
  4539. 'nav',
  4540. 'section',
  4541. 'ol',
  4542. 'ul',
  4543. 'li',
  4544. 'table',
  4545. 'thead',
  4546. 'tbody',
  4547. 'tfoot',
  4548. 'caption',
  4549. 'tr',
  4550. 'td',
  4551. 'th',
  4552. 'h1',
  4553. 'h2',
  4554. 'h3',
  4555. 'h4',
  4556. 'h5',
  4557. 'h6',
  4558. 'blockquote',
  4559. 'pre',
  4560. 'address'
  4561. ];
  4562. var DomUniverse = () => {
  4563. const clone = (element) => {
  4564. return SugarElement.fromDom(element.dom.cloneNode(false));
  4565. };
  4566. const document = (element) => documentOrOwner(element).dom;
  4567. const isBoundary = (element) => {
  4568. if (!isElement(element)) {
  4569. return false;
  4570. }
  4571. if (name(element) === 'body') {
  4572. return true;
  4573. }
  4574. return contains$2(TagBoundaries, name(element));
  4575. };
  4576. const isEmptyTag = (element) => {
  4577. if (!isElement(element)) {
  4578. return false;
  4579. }
  4580. return contains$2(['br', 'img', 'hr', 'input'], name(element));
  4581. };
  4582. const isNonEditable = (element) => isElement(element) && get$b(element, 'contenteditable') === 'false';
  4583. const comparePosition = (element, other) => {
  4584. return element.dom.compareDocumentPosition(other.dom);
  4585. };
  4586. const copyAttributesTo = (source, destination) => {
  4587. const as = clone$1(source);
  4588. setAll$1(destination, as);
  4589. };
  4590. const isSpecial = (element) => {
  4591. const tag = name(element);
  4592. return contains$2([
  4593. 'script', 'noscript', 'iframe', 'noframes', 'noembed', 'title', 'style', 'textarea', 'xmp'
  4594. ], tag);
  4595. };
  4596. const getLanguage = (element) => isElement(element) ? getOpt(element, 'lang') : Optional.none();
  4597. return {
  4598. up: constant({
  4599. selector: ancestor$1,
  4600. closest: closest$1,
  4601. predicate: ancestor$2,
  4602. all: parents
  4603. }),
  4604. down: constant({
  4605. selector: descendants,
  4606. predicate: descendants$1
  4607. }),
  4608. styles: constant({
  4609. get: get$9,
  4610. getRaw: getRaw$2,
  4611. set: set$1,
  4612. remove: remove$4
  4613. }),
  4614. attrs: constant({
  4615. get: get$b,
  4616. set: set$2,
  4617. remove: remove$6,
  4618. copyTo: copyAttributesTo
  4619. }),
  4620. insert: constant({
  4621. before: before$3,
  4622. after: after$4,
  4623. afterAll: after$3,
  4624. append: append$1,
  4625. appendAll: append,
  4626. prepend: prepend,
  4627. wrap: wrap
  4628. }),
  4629. remove: constant({
  4630. unwrap: unwrap,
  4631. remove: remove$5
  4632. }),
  4633. create: constant({
  4634. nu: SugarElement.fromTag,
  4635. clone,
  4636. text: SugarElement.fromText
  4637. }),
  4638. query: constant({
  4639. comparePosition,
  4640. prevSibling: prevSibling,
  4641. nextSibling: nextSibling
  4642. }),
  4643. property: constant({
  4644. children: children$2,
  4645. name: name,
  4646. parent: parent,
  4647. document,
  4648. isText: isText,
  4649. isComment: isComment,
  4650. isElement: isElement,
  4651. isSpecial,
  4652. getLanguage,
  4653. getText: get$5,
  4654. setText: set,
  4655. isBoundary,
  4656. isEmptyTag,
  4657. isNonEditable
  4658. }),
  4659. eq: eq$1,
  4660. is: is
  4661. };
  4662. };
  4663. const traverse = (item, mode) => ({
  4664. item,
  4665. mode
  4666. });
  4667. const backtrack = (universe, item, _direction, transition = sidestep) => {
  4668. return universe.property().parent(item).map((p) => {
  4669. return traverse(p, transition);
  4670. });
  4671. };
  4672. const sidestep = (universe, item, direction, transition = advance) => {
  4673. return direction.sibling(universe, item).map((p) => {
  4674. return traverse(p, transition);
  4675. });
  4676. };
  4677. const advance = (universe, item, direction, transition = advance) => {
  4678. const children = universe.property().children(item);
  4679. const result = direction.first(children);
  4680. return result.map((r) => {
  4681. return traverse(r, transition);
  4682. });
  4683. };
  4684. /*
  4685. * Rule breakdown:
  4686. *
  4687. * current: the traversal that we are applying.
  4688. * next: the next traversal to apply if the current traversal succeeds (e.g. advance after sidestepping)
  4689. * fallback: the traversal to fallback to when the current traversal does not find a node
  4690. */
  4691. const successors = [
  4692. { current: backtrack, next: sidestep, fallback: Optional.none() },
  4693. { current: sidestep, next: advance, fallback: Optional.some(backtrack) },
  4694. { current: advance, next: advance, fallback: Optional.some(sidestep) }
  4695. ];
  4696. const go = (universe, item, mode, direction, rules = successors) => {
  4697. // INVESTIGATE: Find a way which doesn't require an array search first to identify the current mode.
  4698. const ruleOpt = find$1(rules, (succ) => {
  4699. return succ.current === mode;
  4700. });
  4701. return ruleOpt.bind((rule) => {
  4702. // Attempt the current mode. If not, use the fallback and try again.
  4703. return rule.current(universe, item, direction, rule.next).orThunk(() => {
  4704. return rule.fallback.bind((fb) => {
  4705. return go(universe, item, fb, direction);
  4706. });
  4707. });
  4708. });
  4709. };
  4710. const left$1 = () => {
  4711. const sibling = (universe, item) => {
  4712. return universe.query().prevSibling(item);
  4713. };
  4714. const first = (children) => {
  4715. return children.length > 0 ? Optional.some(children[children.length - 1]) : Optional.none();
  4716. };
  4717. return {
  4718. sibling,
  4719. first
  4720. };
  4721. };
  4722. const right$1 = () => {
  4723. const sibling = (universe, item) => {
  4724. return universe.query().nextSibling(item);
  4725. };
  4726. const first = (children) => {
  4727. return children.length > 0 ? Optional.some(children[0]) : Optional.none();
  4728. };
  4729. return {
  4730. sibling,
  4731. first
  4732. };
  4733. };
  4734. const Walkers = {
  4735. left: left$1,
  4736. right: right$1
  4737. };
  4738. const hone = (universe, item, predicate, mode, direction, isRoot) => {
  4739. const next = go(universe, item, mode, direction);
  4740. return next.bind((n) => {
  4741. if (isRoot(n.item)) {
  4742. return Optional.none();
  4743. }
  4744. else {
  4745. return predicate(n.item) ? Optional.some(n.item) : hone(universe, n.item, predicate, n.mode, direction, isRoot);
  4746. }
  4747. });
  4748. };
  4749. const left = (universe, item, predicate, isRoot) => {
  4750. return hone(universe, item, predicate, sidestep, Walkers.left(), isRoot);
  4751. };
  4752. const right = (universe, item, predicate, isRoot) => {
  4753. return hone(universe, item, predicate, sidestep, Walkers.right(), isRoot);
  4754. };
  4755. const point = (element, offset) => ({
  4756. element,
  4757. offset
  4758. });
  4759. const scan$1 = (universe, element, direction) => {
  4760. // if a comment or zero-length text, scan the siblings
  4761. if ((universe.property().isText(element) && universe.property().getText(element).trim().length === 0)
  4762. || universe.property().isComment(element)) {
  4763. return direction(element).bind((elem) => {
  4764. return scan$1(universe, elem, direction).orThunk(() => {
  4765. return Optional.some(elem);
  4766. });
  4767. });
  4768. }
  4769. else {
  4770. return Optional.none();
  4771. }
  4772. };
  4773. const toEnd = (universe, element) => {
  4774. if (universe.property().isText(element)) {
  4775. return universe.property().getText(element).length;
  4776. }
  4777. const children = universe.property().children(element);
  4778. return children.length;
  4779. };
  4780. const freefallRtl$2 = (universe, element) => {
  4781. const candidate = scan$1(universe, element, universe.query().prevSibling).getOr(element);
  4782. if (universe.property().isText(candidate)) {
  4783. return point(candidate, toEnd(universe, candidate));
  4784. }
  4785. const children = universe.property().children(candidate);
  4786. return children.length > 0 ? freefallRtl$2(universe, children[children.length - 1]) : point(candidate, toEnd(universe, candidate));
  4787. };
  4788. const freefallRtl$1 = freefallRtl$2;
  4789. const universe$3 = DomUniverse();
  4790. const freefallRtl = (element) => {
  4791. return freefallRtl$1(universe$3, element);
  4792. };
  4793. const isLeaf = (universe) => (element) => universe.property().children(element).length === 0;
  4794. const before$1 = (universe, item, isRoot) => {
  4795. return seekLeft$1(universe, item, isLeaf(universe), isRoot);
  4796. };
  4797. const after$1 = (universe, item, isRoot) => {
  4798. return seekRight$1(universe, item, isLeaf(universe), isRoot);
  4799. };
  4800. const seekLeft$1 = left;
  4801. const seekRight$1 = right;
  4802. go;
  4803. const universe$2 = DomUniverse();
  4804. const before = (element, isRoot) => {
  4805. return before$1(universe$2, element, isRoot);
  4806. };
  4807. const after = (element, isRoot) => {
  4808. return after$1(universe$2, element, isRoot);
  4809. };
  4810. const seekLeft = (element, predicate, isRoot) => {
  4811. return seekLeft$1(universe$2, element, predicate, isRoot);
  4812. };
  4813. const seekRight = (element, predicate, isRoot) => {
  4814. return seekRight$1(universe$2, element, predicate, isRoot);
  4815. };
  4816. const blockList = [
  4817. 'body',
  4818. 'p',
  4819. 'div',
  4820. 'article',
  4821. 'aside',
  4822. 'figcaption',
  4823. 'figure',
  4824. 'footer',
  4825. 'header',
  4826. 'nav',
  4827. 'section',
  4828. 'ol',
  4829. 'ul',
  4830. // --- NOTE, TagBoundaries has li here. That means universe.isBoundary => true for li tags.
  4831. 'table',
  4832. 'thead',
  4833. 'tfoot',
  4834. 'tbody',
  4835. 'caption',
  4836. 'tr',
  4837. 'td',
  4838. 'th',
  4839. 'h1',
  4840. 'h2',
  4841. 'h3',
  4842. 'h4',
  4843. 'h5',
  4844. 'h6',
  4845. 'blockquote',
  4846. 'pre',
  4847. 'address'
  4848. ];
  4849. const isList$1 = (universe, item) => {
  4850. const tagName = universe.property().name(item);
  4851. return contains$2(['ol', 'ul'], tagName);
  4852. };
  4853. const isBlock$1 = (universe, item) => {
  4854. const tagName = universe.property().name(item);
  4855. return contains$2(blockList, tagName);
  4856. };
  4857. const isEmptyTag$1 = (universe, item) => {
  4858. return contains$2(['br', 'img', 'hr', 'input'], universe.property().name(item));
  4859. };
  4860. const leftRight = (left, right) => ({
  4861. left,
  4862. right
  4863. });
  4864. const brokenPath = (first, second, splits) => ({
  4865. first,
  4866. second,
  4867. splits
  4868. });
  4869. const bisect = (universe, parent, child) => {
  4870. const children = universe.property().children(parent);
  4871. const index = findIndex(children, curry(universe.eq, child));
  4872. return index.map((ind) => {
  4873. return {
  4874. before: children.slice(0, ind),
  4875. after: children.slice(ind + 1)
  4876. };
  4877. });
  4878. };
  4879. /**
  4880. * Clone parent to the RIGHT and move everything after child in the parent element into
  4881. * a clone of the parent (placed after parent).
  4882. */
  4883. const breakToRight = (universe, parent, child) => {
  4884. return bisect(universe, parent, child).map((parts) => {
  4885. const second = universe.create().clone(parent);
  4886. universe.insert().appendAll(second, parts.after);
  4887. universe.insert().after(parent, second);
  4888. return leftRight(parent, second);
  4889. });
  4890. };
  4891. /**
  4892. * Clone parent to the LEFT and move everything before and including child into
  4893. * the a clone of the parent (placed before parent)
  4894. */
  4895. const breakToLeft = (universe, parent, child) => {
  4896. return bisect(universe, parent, child).map((parts) => {
  4897. const prior = universe.create().clone(parent);
  4898. universe.insert().appendAll(prior, parts.before.concat([child]));
  4899. universe.insert().appendAll(parent, parts.after);
  4900. universe.insert().before(parent, prior);
  4901. return leftRight(prior, parent);
  4902. });
  4903. };
  4904. /*
  4905. * Using the breaker, break from the child up to the top element defined by the predicate.
  4906. * It returns three values:
  4907. * first: the top level element that completed the break
  4908. * second: the optional element representing second part of the top-level split if the breaking completed successfully to the top
  4909. * splits: a list of (Element, Element) pairs that represent the splits that have occurred on the way to the top.
  4910. */
  4911. const breakPath = (universe, item, isTop, breaker) => {
  4912. const next = (child, group, splits) => {
  4913. const fallback = brokenPath(child, Optional.none(), splits);
  4914. // Found the top, so stop.
  4915. if (isTop(child)) {
  4916. return brokenPath(child, group, splits);
  4917. }
  4918. else {
  4919. // Split the child at parent, and keep going
  4920. return universe.property().parent(child).bind((parent) => {
  4921. return breaker(universe, parent, child).map((breakage) => {
  4922. const extra = [{ first: breakage.left, second: breakage.right }];
  4923. // Our isTop is based on the left-side parent, so keep it regardless of split.
  4924. const nextChild = isTop(parent) ? parent : breakage.left;
  4925. return next(nextChild, Optional.some(breakage.right), splits.concat(extra));
  4926. });
  4927. }).getOr(fallback);
  4928. }
  4929. };
  4930. return next(item, Optional.none(), []);
  4931. };
  4932. const all = (universe, look, elements, f) => {
  4933. const head = elements[0];
  4934. const tail = elements.slice(1);
  4935. return f(universe, look, head, tail);
  4936. };
  4937. /**
  4938. * Check if look returns the same element for all elements, and return it if it exists.
  4939. */
  4940. const oneAll = (universe, look, elements) => {
  4941. return elements.length > 0 ?
  4942. all(universe, look, elements, unsafeOne) :
  4943. Optional.none();
  4944. };
  4945. const unsafeOne = (universe, look, head, tail) => {
  4946. const start = look(universe, head);
  4947. return foldr(tail, (b, a) => {
  4948. const current = look(universe, a);
  4949. return commonElement(universe, b, current);
  4950. }, start);
  4951. };
  4952. const commonElement = (universe, start, end) => {
  4953. return start.bind((s) => {
  4954. return end.filter(curry(universe.eq, s));
  4955. });
  4956. };
  4957. const eq = (universe, item) => {
  4958. return curry(universe.eq, item);
  4959. };
  4960. // Note: this can be exported if it is required in the future.
  4961. const ancestors$2 = (universe, start, end, isRoot = never) => {
  4962. // Inefficient if no isRoot is supplied.
  4963. // TODO: Andy knows there is a graph-based algorithm to find a common parent, but can't remember it
  4964. // This also includes something to get the subset after finding the common parent
  4965. const ps1 = [start].concat(universe.up().all(start));
  4966. const ps2 = [end].concat(universe.up().all(end));
  4967. const prune = (path) => {
  4968. const index = findIndex(path, isRoot);
  4969. return index.fold(() => {
  4970. return path;
  4971. }, (ind) => {
  4972. return path.slice(0, ind + 1);
  4973. });
  4974. };
  4975. const pruned1 = prune(ps1);
  4976. const pruned2 = prune(ps2);
  4977. const shared = find$1(pruned1, (x) => {
  4978. return exists(pruned2, eq(universe, x));
  4979. });
  4980. return {
  4981. firstpath: pruned1,
  4982. secondpath: pruned2,
  4983. shared
  4984. };
  4985. };
  4986. const sharedOne$1 = oneAll;
  4987. const ancestors$1 = ancestors$2;
  4988. breakToLeft;
  4989. breakToRight;
  4990. breakPath;
  4991. const universe$1 = DomUniverse();
  4992. const sharedOne = (look, elements) => {
  4993. return sharedOne$1(universe$1, (_universe, element) => {
  4994. return look(element);
  4995. }, elements);
  4996. };
  4997. const ancestors = (start, finish, isRoot) => {
  4998. return ancestors$1(universe$1, start, finish, isRoot);
  4999. };
  5000. const universe = DomUniverse();
  5001. const isBlock = (element) => {
  5002. return isBlock$1(universe, element);
  5003. };
  5004. const isList = (element) => {
  5005. return isList$1(universe, element);
  5006. };
  5007. const isEmptyTag = (element) => {
  5008. return isEmptyTag$1(universe, element);
  5009. };
  5010. const merge$2 = (cells) => {
  5011. const isBr = isTag('br');
  5012. const advancedBr = (children) => {
  5013. return forall(children, (c) => {
  5014. return isBr(c) || (isText(c) && get$5(c).trim().length === 0);
  5015. });
  5016. };
  5017. const isListItem = (el) => {
  5018. return name(el) === 'li' || ancestor$2(el, isList).isSome();
  5019. };
  5020. const siblingIsBlock = (el) => {
  5021. return nextSibling(el).map((rightSibling) => {
  5022. if (isBlock(rightSibling)) {
  5023. return true;
  5024. }
  5025. if (isEmptyTag(rightSibling)) {
  5026. return name(rightSibling) === 'img' ? false : true;
  5027. }
  5028. return false;
  5029. }).getOr(false);
  5030. };
  5031. const markCell = (cell) => {
  5032. return last(cell).bind((rightEdge) => {
  5033. const rightSiblingIsBlock = siblingIsBlock(rightEdge);
  5034. return parent(rightEdge).map((parent) => {
  5035. return rightSiblingIsBlock === true || isListItem(parent) || isBr(rightEdge) || (isBlock(parent) && !eq$1(cell, parent)) ? [] : [SugarElement.fromTag('br')];
  5036. });
  5037. }).getOr([]);
  5038. };
  5039. const markContent = () => {
  5040. const content = bind$2(cells, (cell) => {
  5041. const children = children$2(cell);
  5042. return advancedBr(children) ? [] : children.concat(markCell(cell));
  5043. });
  5044. return content.length === 0 ? [SugarElement.fromTag('br')] : content;
  5045. };
  5046. const contents = markContent();
  5047. empty(cells[0]);
  5048. append(cells[0], contents);
  5049. };
  5050. // Remove legacy sizing attributes such as "width"
  5051. const cleanupLegacyAttributes = (element) => {
  5052. remove$6(element, 'width');
  5053. remove$6(element, 'height');
  5054. };
  5055. const convertToPercentSizeWidth = (table) => {
  5056. const newWidth = getPercentTableWidth(table);
  5057. redistribute(table, Optional.some(newWidth), Optional.none());
  5058. cleanupLegacyAttributes(table);
  5059. };
  5060. const convertToPixelSizeWidth = (table) => {
  5061. const newWidth = getPixelTableWidth(table);
  5062. redistribute(table, Optional.some(newWidth), Optional.none());
  5063. cleanupLegacyAttributes(table);
  5064. };
  5065. const convertToPixelSizeHeight = (table) => {
  5066. const newHeight = getPixelTableHeight(table);
  5067. redistribute(table, Optional.none(), Optional.some(newHeight));
  5068. cleanupLegacyAttributes(table);
  5069. };
  5070. const convertToNoneSizeWidth = (table) => {
  5071. remove$4(table, 'width');
  5072. const columns = columns$1(table);
  5073. const rowElements = columns.length > 0 ? columns : cells$1(table);
  5074. each$2(rowElements, (cell) => {
  5075. remove$4(cell, 'width');
  5076. cleanupLegacyAttributes(cell);
  5077. });
  5078. cleanupLegacyAttributes(table);
  5079. };
  5080. const transferableAttributes = {
  5081. scope: [
  5082. 'row',
  5083. 'col'
  5084. ]
  5085. };
  5086. // NOTE: This may create a td instead of a th, but it is for irregular table handling.
  5087. const createCell = (doc) => () => {
  5088. const td = SugarElement.fromTag('td', doc.dom);
  5089. append$1(td, SugarElement.fromTag('br', doc.dom));
  5090. return td;
  5091. };
  5092. const createCol = (doc) => () => {
  5093. return SugarElement.fromTag('col', doc.dom);
  5094. };
  5095. const createColgroup = (doc) => () => {
  5096. return SugarElement.fromTag('colgroup', doc.dom);
  5097. };
  5098. const createRow$1 = (doc) => () => {
  5099. return SugarElement.fromTag('tr', doc.dom);
  5100. };
  5101. const replace$1 = (cell, tag, attrs) => {
  5102. const replica = copy$2(cell, tag);
  5103. // TODO: Snooker passes null to indicate 'remove attribute'
  5104. each$1(attrs, (v, k) => {
  5105. if (v === null) {
  5106. remove$6(replica, k);
  5107. }
  5108. else {
  5109. set$2(replica, k, v);
  5110. }
  5111. });
  5112. return replica;
  5113. };
  5114. // eslint-disable-next-line @tinymce/prefer-fun
  5115. const pasteReplace = (cell) => {
  5116. // TODO: check for empty content and don't return anything
  5117. return cell;
  5118. };
  5119. const cloneFormats = (oldCell, newCell, formats) => {
  5120. const first$1 = first(oldCell);
  5121. return first$1.map((firstText) => {
  5122. const formatSelector = formats.join(',');
  5123. // Find the ancestors of the first text node that match the given formats.
  5124. const parents = ancestors$3(firstText, formatSelector, (element) => {
  5125. return eq$1(element, oldCell);
  5126. });
  5127. // Add the matched ancestors to the new cell, then return the new cell.
  5128. return foldr(parents, (last, parent) => {
  5129. const clonedFormat = shallow(parent);
  5130. append$1(last, clonedFormat);
  5131. return clonedFormat;
  5132. }, newCell);
  5133. }).getOr(newCell);
  5134. };
  5135. const cloneAppropriateAttributes = (original, clone) => {
  5136. each$1(transferableAttributes, (validAttributes, attributeName) => getOpt(original, attributeName)
  5137. .filter((attribute) => contains$2(validAttributes, attribute))
  5138. .each((attribute) => set$2(clone, attributeName, attribute)));
  5139. };
  5140. const cellOperations = (mutate, doc, formatsToClone) => {
  5141. const cloneCss = (prev, clone) => {
  5142. // inherit the style and width, dont inherit the row height
  5143. copy$1(prev.element, clone);
  5144. remove$4(clone, 'height');
  5145. // dont inherit the width of spanning columns
  5146. if (prev.colspan !== 1) {
  5147. remove$4(clone, 'width');
  5148. }
  5149. };
  5150. const newCell = (prev) => {
  5151. const td = SugarElement.fromTag(name(prev.element), doc.dom);
  5152. const formats = formatsToClone.getOr(['strong', 'em', 'b', 'i', 'span', 'font', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'div']);
  5153. // If we aren't cloning the child formatting, we can just give back the new td immediately.
  5154. const lastNode = formats.length > 0 ? cloneFormats(prev.element, td, formats) : td;
  5155. append$1(lastNode, SugarElement.fromTag('br'));
  5156. cloneCss(prev, td);
  5157. cloneAppropriateAttributes(prev.element, td);
  5158. mutate(prev.element, td);
  5159. return td;
  5160. };
  5161. const newCol = (prev) => {
  5162. const col = SugarElement.fromTag(name(prev.element), doc.dom);
  5163. cloneCss(prev, col);
  5164. mutate(prev.element, col);
  5165. return col;
  5166. };
  5167. return {
  5168. col: newCol,
  5169. colgroup: createColgroup(doc),
  5170. row: createRow$1(doc),
  5171. cell: newCell,
  5172. replace: replace$1,
  5173. colGap: createCol(doc),
  5174. gap: createCell(doc)
  5175. };
  5176. };
  5177. const paste$1 = (doc) => {
  5178. return {
  5179. col: createCol(doc),
  5180. colgroup: createColgroup(doc),
  5181. row: createRow$1(doc),
  5182. cell: createCell(doc),
  5183. replace: pasteReplace,
  5184. colGap: createCol(doc),
  5185. gap: createCell(doc)
  5186. };
  5187. };
  5188. const getGridSize = (table) => {
  5189. const warehouse = Warehouse.fromTable(table);
  5190. return warehouse.grid;
  5191. };
  5192. // substitution: () -> item
  5193. const merge$1 = (grid, bounds, comparator, substitution) => {
  5194. const rows = extractGridDetails(grid).rows;
  5195. // Mutating. Do we care about the efficiency gain?
  5196. if (rows.length === 0) {
  5197. return grid;
  5198. }
  5199. for (let i = bounds.startRow; i <= bounds.finishRow; i++) {
  5200. for (let j = bounds.startCol; j <= bounds.finishCol; j++) {
  5201. // We can probably simplify this again now that we aren't reusing merge.
  5202. const row = rows[i];
  5203. const isLocked = getCell(row, j).isLocked;
  5204. mutateCell(row, j, elementnew(substitution(), false, isLocked));
  5205. }
  5206. }
  5207. return grid;
  5208. };
  5209. // substitution: () -> item
  5210. const unmerge = (grid, target, comparator, substitution) => {
  5211. const rows = extractGridDetails(grid).rows;
  5212. // Mutating. Do we care about the efficiency gain?
  5213. let first = true;
  5214. // tslint:disable-next-line:prefer-for-of
  5215. for (let i = 0; i < rows.length; i++) {
  5216. for (let j = 0; j < cellLength(rows[0]); j++) {
  5217. const row = rows[i];
  5218. const currentCell = getCell(row, j);
  5219. const currentCellElm = currentCell.element;
  5220. const isToReplace = comparator(currentCellElm, target);
  5221. if (isToReplace && !first) {
  5222. mutateCell(row, j, elementnew(substitution(), true, currentCell.isLocked));
  5223. }
  5224. else if (isToReplace) {
  5225. first = false;
  5226. }
  5227. }
  5228. }
  5229. return grid;
  5230. };
  5231. const uniqueCells = (row, comparator) => {
  5232. return foldl(row, (rest, cell) => {
  5233. return exists(rest, (currentCell) => {
  5234. return comparator(currentCell.element, cell.element);
  5235. }) ? rest : rest.concat([cell]);
  5236. }, []);
  5237. };
  5238. const splitCols = (grid, index, comparator, substitution) => {
  5239. // We don't need to split rows if we're inserting at the first or last row of the old table
  5240. if (index > 0 && index < grid[0].cells.length) {
  5241. each$2(grid, (row) => {
  5242. const prevCell = row.cells[index - 1];
  5243. let offset = 0;
  5244. const substitute = substitution();
  5245. while (row.cells.length > index + offset && comparator(prevCell.element, row.cells[index + offset].element)) {
  5246. mutateCell(row, index + offset, elementnew(substitute, true, row.cells[index + offset].isLocked));
  5247. offset++;
  5248. }
  5249. });
  5250. }
  5251. return grid;
  5252. };
  5253. const splitRows = (grid, index, comparator, substitution) => {
  5254. // We don't need to split rows if we're inserting at the first or last row of the old table
  5255. const rows = extractGridDetails(grid).rows;
  5256. if (index > 0 && index < rows.length) {
  5257. const rowPrevCells = rows[index - 1].cells;
  5258. const cells = uniqueCells(rowPrevCells, comparator);
  5259. each$2(cells, (cell) => {
  5260. // only make a sub when we have to
  5261. let replacement = Optional.none();
  5262. for (let i = index; i < rows.length; i++) {
  5263. for (let j = 0; j < cellLength(rows[0]); j++) {
  5264. const row = rows[i];
  5265. const current = getCell(row, j);
  5266. const isToReplace = comparator(current.element, cell.element);
  5267. if (isToReplace) {
  5268. if (replacement.isNone()) {
  5269. replacement = Optional.some(substitution());
  5270. }
  5271. replacement.each((sub) => {
  5272. mutateCell(row, j, elementnew(sub, true, current.isLocked));
  5273. });
  5274. }
  5275. }
  5276. }
  5277. });
  5278. }
  5279. return grid;
  5280. };
  5281. /*
  5282. Fitment, is a module used to ensure that the Inserted table (gridB) can fit squareley within the Host table (gridA).
  5283. - measure returns a delta of rows and cols, eg:
  5284. - col: 3 means gridB can fit with 3 spaces to spare
  5285. - row: -5 means gridB can needs 5 more rows to completely fit into gridA
  5286. - col: 0, row: 0 depics perfect fitment
  5287. - tailor, requires a delta and returns grid that is built to match the delta, tailored to fit.
  5288. eg: 3x3 gridA, with a delta col: -3, row: 2 returns a new grid 3 rows x 6 cols
  5289. - assumptions: All grids used by this module should be rectangular
  5290. */
  5291. const measure = (startAddress, gridA, gridB) => {
  5292. if (startAddress.row >= gridA.length || startAddress.column > cellLength(gridA[0])) {
  5293. return Result.error('invalid start address out of table bounds, row: ' + startAddress.row + ', column: ' + startAddress.column);
  5294. }
  5295. const rowRemainder = gridA.slice(startAddress.row);
  5296. const colRemainder = rowRemainder[0].cells.slice(startAddress.column);
  5297. const colRequired = cellLength(gridB[0]);
  5298. const rowRequired = gridB.length;
  5299. return Result.value({
  5300. rowDelta: rowRemainder.length - rowRequired,
  5301. colDelta: colRemainder.length - colRequired
  5302. });
  5303. };
  5304. const measureWidth = (gridA, gridB) => {
  5305. const colLengthA = cellLength(gridA[0]);
  5306. const colLengthB = cellLength(gridB[0]);
  5307. return {
  5308. rowDelta: 0,
  5309. colDelta: colLengthA - colLengthB
  5310. };
  5311. };
  5312. const measureHeight = (gridA, gridB) => {
  5313. const rowLengthA = gridA.length;
  5314. const rowLengthB = gridB.length;
  5315. return {
  5316. rowDelta: rowLengthA - rowLengthB,
  5317. colDelta: 0
  5318. };
  5319. };
  5320. const generateElements = (amount, row, generators, isLocked) => {
  5321. const generator = row.section === 'colgroup' ? generators.col : generators.cell;
  5322. return range$1(amount, (idx) => elementnew(generator(), true, isLocked(idx)));
  5323. };
  5324. const rowFill = (grid, amount, generators, lockedColumns) => {
  5325. const exampleRow = grid[grid.length - 1];
  5326. return grid.concat(range$1(amount, () => {
  5327. const generator = exampleRow.section === 'colgroup' ? generators.colgroup : generators.row;
  5328. const row = clone$2(exampleRow, generator, identity);
  5329. const elements = generateElements(row.cells.length, row, generators, (idx) => has$1(lockedColumns, idx.toString()));
  5330. return setCells(row, elements);
  5331. }));
  5332. };
  5333. const colFill = (grid, amount, generators, startIndex) => map$1(grid, (row) => {
  5334. const newChildren = generateElements(amount, row, generators, never);
  5335. return addCells(row, startIndex, newChildren);
  5336. });
  5337. const lockedColFill = (grid, generators, lockedColumns) => map$1(grid, (row) => {
  5338. return foldl(lockedColumns, (acc, colNum) => {
  5339. const newChild = generateElements(1, row, generators, always)[0];
  5340. return addCell(acc, colNum, newChild);
  5341. }, row);
  5342. });
  5343. const tailor = (gridA, delta, generators) => {
  5344. const fillCols = delta.colDelta < 0 ? colFill : identity;
  5345. const fillRows = delta.rowDelta < 0 ? rowFill : identity;
  5346. const lockedColumns = getLockedColumnsFromGrid(gridA);
  5347. const gridWidth = cellLength(gridA[0]);
  5348. const isLastColLocked = exists(lockedColumns, (locked) => locked === gridWidth - 1);
  5349. const modifiedCols = fillCols(gridA, Math.abs(delta.colDelta), generators, isLastColLocked ? gridWidth - 1 : gridWidth);
  5350. // Need to recalculate locked column positions
  5351. const newLockedColumns = getLockedColumnsFromGrid(modifiedCols);
  5352. return fillRows(modifiedCols, Math.abs(delta.rowDelta), generators, mapToObject(newLockedColumns, always));
  5353. };
  5354. const isSpanning = (grid, row, col, comparator) => {
  5355. const candidate = getCell(grid[row], col);
  5356. const matching = curry(comparator, candidate.element);
  5357. const currentRow = grid[row];
  5358. // sanity check, 1x1 has no spans
  5359. return grid.length > 1 && cellLength(currentRow) > 1 &&
  5360. (
  5361. // search left, if we're not on the left edge
  5362. // search down, if we're not on the bottom edge
  5363. (col > 0 && matching(getCellElement(currentRow, col - 1))) ||
  5364. // search right, if we're not on the right edge
  5365. (col < currentRow.cells.length - 1 && matching(getCellElement(currentRow, col + 1))) ||
  5366. // search up, if we're not on the top edge
  5367. (row > 0 && matching(getCellElement(grid[row - 1], col))) ||
  5368. (row < grid.length - 1 && matching(getCellElement(grid[row + 1], col))));
  5369. };
  5370. const mergeTables = (startAddress, gridA, gridBRows, generator, comparator, lockedColumns) => {
  5371. // Assumes
  5372. // - gridA is square and gridB is square
  5373. const startRow = startAddress.row;
  5374. const startCol = startAddress.column;
  5375. const mergeHeight = gridBRows.length;
  5376. const mergeWidth = cellLength(gridBRows[0]);
  5377. const endRow = startRow + mergeHeight;
  5378. const endCol = startCol + mergeWidth + lockedColumns.length;
  5379. const lockedColumnObj = mapToObject(lockedColumns, always);
  5380. // embrace the mutation - I think this is easier to follow? To discuss.
  5381. for (let r = startRow; r < endRow; r++) {
  5382. let skippedCol = 0;
  5383. for (let c = startCol; c < endCol; c++) {
  5384. if (lockedColumnObj[c]) {
  5385. skippedCol++;
  5386. continue;
  5387. }
  5388. if (isSpanning(gridA, r, c, comparator)) {
  5389. // mutation within mutation, it's mutatception
  5390. unmerge(gridA, getCellElement(gridA[r], c), comparator, generator.cell);
  5391. }
  5392. const gridBColIndex = c - startCol - skippedCol;
  5393. const newCell = getCell(gridBRows[r - startRow], gridBColIndex);
  5394. // This can't be a col element at this point so we can cast it to a cell
  5395. const newCellElm = newCell.element;
  5396. const replacement = generator.replace(newCellElm);
  5397. mutateCell(gridA[r], c, elementnew(replacement, true, newCell.isLocked));
  5398. }
  5399. }
  5400. return gridA;
  5401. };
  5402. const getValidStartAddress = (currentStartAddress, grid, lockedColumns) => {
  5403. const gridColLength = cellLength(grid[0]);
  5404. /*
  5405. When we paste from a table without colgroups to a table that has them, we need to ensure we are inserting them at
  5406. the correct row index (the `col`s are treated as cells in the Structs.RowCells array).
  5407. To do this, we get the number of `col`s in the destination table and add that to the startAddress row.
  5408. */
  5409. const adjustedRowAddress = extractGridDetails(grid).cols.length + currentStartAddress.row;
  5410. const possibleColAddresses = range$1(gridColLength - currentStartAddress.column, (num) => num + currentStartAddress.column);
  5411. // Find a starting column address that isn't a locked column
  5412. const validColAddress = find$1(possibleColAddresses, (num) => forall(lockedColumns, (col) => col !== num)).getOr(gridColLength - 1);
  5413. return {
  5414. row: adjustedRowAddress,
  5415. column: validColAddress
  5416. };
  5417. };
  5418. const getLockedColumnsWithinBounds = (startAddress, rows, lockedColumns) => filter$2(lockedColumns, (colNum) => colNum >= startAddress.column && colNum <= cellLength(rows[0]) + startAddress.column);
  5419. const merge = (startAddress, gridA, gridB, generator, comparator) => {
  5420. const lockedColumns = getLockedColumnsFromGrid(gridA);
  5421. const validStartAddress = getValidStartAddress(startAddress, gridA, lockedColumns);
  5422. /*
  5423. We always remove the cols (extract the rows) from the table being pasted. This ensures that if we are pasting from a table with colgroups into a table
  5424. without them, we don't insert the `col` elements as if they were `td`s
  5425. */
  5426. const gridBRows = extractGridDetails(gridB).rows;
  5427. const lockedColumnsWithinBounds = getLockedColumnsWithinBounds(validStartAddress, gridBRows, lockedColumns);
  5428. const result = measure(validStartAddress, gridA, gridBRows);
  5429. /*
  5430. Need to subtract extra delta for locked columns between startAddress and the startAddress + gridB column count as
  5431. locked column cells cannot be merged into. Therefore, extra column cells need to be added to gridA to allow gridB cells to be merged
  5432. */
  5433. return result.map((diff) => {
  5434. const delta = {
  5435. ...diff,
  5436. colDelta: diff.colDelta - lockedColumnsWithinBounds.length
  5437. };
  5438. const fittedGrid = tailor(gridA, delta, generator);
  5439. // Need to recalculate lockedColumnsWithinBounds as tailoring may have inserted columns before last locked column which changes the locked index
  5440. const newLockedColumns = getLockedColumnsFromGrid(fittedGrid);
  5441. const newLockedColumnsWithinBounds = getLockedColumnsWithinBounds(validStartAddress, gridBRows, newLockedColumns);
  5442. return mergeTables(validStartAddress, fittedGrid, gridBRows, generator, comparator, newLockedColumnsWithinBounds);
  5443. });
  5444. };
  5445. const insertCols = (index, gridA, gridB, generator, comparator) => {
  5446. splitCols(gridA, index, comparator, generator.cell);
  5447. const delta = measureHeight(gridB, gridA);
  5448. const fittedNewGrid = tailor(gridB, delta, generator);
  5449. const secondDelta = measureHeight(gridA, fittedNewGrid);
  5450. const fittedOldGrid = tailor(gridA, secondDelta, generator);
  5451. return map$1(fittedOldGrid, (gridRow, i) => {
  5452. return addCells(gridRow, index, fittedNewGrid[i].cells);
  5453. });
  5454. };
  5455. /*
  5456. Inserting rows with locked columns
  5457. - Tailor gridA first (this needs to be done first as the position of the locked columns may change when tailoring gridA and the location of the locked columns needs to be stable before tailoring gridB)
  5458. - measure delta between gridA and gridB (pasted rows) - if negative colDelta, gridA needs extra columns added to match gridB
  5459. - need to calculate how many columns in gridB cannot be directly inserted into gridA - this is how many extra columns need to be added to gridA (this consideres the fact locked column cannot be inserted into)
  5460. - nonLockedGridA + lockedGridA - gridB = colDelta (By subtracting locked column count, can get required diff)
  5461. - tailor gridA by adding the required extra columns if necessary either at the end of gridA or before the last column depending on whether it is locked
  5462. - Recalculate where the locked columns are in gridA after tailoring
  5463. - Measure and determine if extra columns need to be added to gridB (locked columns should not count towards the delta as colFilling (adding extra columns) for locked columns is handled separately)
  5464. - Do a lockedColFill on gridB
  5465. - Tailor gridB by adding extra columns to end of gridB if required
  5466. */
  5467. const insertRows = (index, gridA, gridB, generator, comparator) => {
  5468. splitRows(gridA, index, comparator, generator.cell);
  5469. const locked = getLockedColumnsFromGrid(gridA);
  5470. const diff = measureWidth(gridA, gridB);
  5471. const delta = {
  5472. ...diff,
  5473. colDelta: diff.colDelta - locked.length
  5474. };
  5475. const fittedOldGrid = tailor(gridA, delta, generator);
  5476. const { cols: oldCols, rows: oldRows } = extractGridDetails(fittedOldGrid);
  5477. const newLocked = getLockedColumnsFromGrid(fittedOldGrid);
  5478. const secondDiff = measureWidth(gridB, gridA);
  5479. // Don't want the locked columns to count towards to the colDelta as column filling for locked columns is handled separately
  5480. const secondDelta = {
  5481. ...secondDiff,
  5482. colDelta: secondDiff.colDelta + newLocked.length
  5483. };
  5484. const fittedGridB = lockedColFill(gridB, generator, newLocked);
  5485. const fittedNewGrid = tailor(fittedGridB, secondDelta, generator);
  5486. return [
  5487. ...oldCols,
  5488. ...oldRows.slice(0, index),
  5489. ...fittedNewGrid,
  5490. ...oldRows.slice(index, oldRows.length)
  5491. ];
  5492. };
  5493. const cloneRow = (row, cloneCell, comparator, substitution) => clone$2(row, (elem) => substitution(elem, comparator), cloneCell);
  5494. // substitution :: (item, comparator) -> item
  5495. // example is the location of the cursor (the row index)
  5496. // index is the insert position (at - or after - example) (the row index)
  5497. const insertRowAt = (grid, index, example, comparator, substitution) => {
  5498. const { rows, cols } = extractGridDetails(grid);
  5499. const before = rows.slice(0, index);
  5500. const after = rows.slice(index);
  5501. const newRow = cloneRow(rows[example], (ex, c) => {
  5502. const withinSpan = index > 0 && index < rows.length && comparator(getCellElement(rows[index - 1], c), getCellElement(rows[index], c));
  5503. const ret = withinSpan ? getCell(rows[index], c) : elementnew(substitution(ex.element, comparator), true, ex.isLocked);
  5504. return ret;
  5505. }, comparator, substitution);
  5506. return [
  5507. ...cols,
  5508. ...before,
  5509. newRow,
  5510. ...after
  5511. ];
  5512. };
  5513. const getElementFor = (row, column, section, withinSpan, example, comparator, substitution) => {
  5514. if (section === 'colgroup' || !withinSpan) {
  5515. const cell = getCell(row, example);
  5516. // locked is explicitly set to false so the newly inserted column doesn't inherit example column locked state
  5517. return elementnew(substitution(cell.element, comparator), true, false);
  5518. }
  5519. else {
  5520. return getCell(row, column);
  5521. }
  5522. };
  5523. // substitution :: (item, comparator) -> item
  5524. // example is the location of the cursor (the column index)
  5525. // index is the insert position (at - or after - example) (the column index)
  5526. const insertColumnAt = (grid, index, example, comparator, substitution) => map$1(grid, (row) => {
  5527. const withinSpan = index > 0 && index < cellLength(row) && comparator(getCellElement(row, index - 1), getCellElement(row, index));
  5528. const sub = getElementFor(row, index, row.section, withinSpan, example, comparator, substitution);
  5529. return addCell(row, index, sub);
  5530. });
  5531. const deleteColumnsAt = (grid, columns) => bind$2(grid, (row) => {
  5532. const existingCells = row.cells;
  5533. const cells = foldr(columns, (acc, column) => column >= 0 && column < acc.length ? acc.slice(0, column).concat(acc.slice(column + 1)) : acc, existingCells);
  5534. return cells.length > 0 ? [rowcells(row.element, cells, row.section, row.isNew)] : [];
  5535. });
  5536. const deleteRowsAt = (grid, start, finish) => {
  5537. const { rows, cols } = extractGridDetails(grid);
  5538. return [
  5539. ...cols,
  5540. ...rows.slice(0, start),
  5541. ...rows.slice(finish + 1)
  5542. ];
  5543. };
  5544. const notInStartRow = (grid, rowIndex, colIndex, comparator) => getCellElement(grid[rowIndex], colIndex) !== undefined && (rowIndex > 0 && comparator(getCellElement(grid[rowIndex - 1], colIndex), getCellElement(grid[rowIndex], colIndex)));
  5545. const notInStartColumn = (row, index, comparator) => index > 0 && comparator(getCellElement(row, index - 1), getCellElement(row, index));
  5546. // This checks for cells that aren't in the "start" position as the model will create duplicate element references for
  5547. // each column/row that the cell spans. As an example, for a merged cell with rowspan="2", the cell in the second row is a duplicate
  5548. // of the cell in the first row.
  5549. const isDuplicatedCell = (grid, rowIndex, colIndex, comparator) => notInStartRow(grid, rowIndex, colIndex, comparator) || notInStartColumn(grid[rowIndex], colIndex, comparator);
  5550. const rowReplacerPredicate = (targetRow, columnHeaders) => {
  5551. const entireTableIsHeader = forall(columnHeaders, identity) && isHeaderCells(targetRow.cells);
  5552. return entireTableIsHeader ? always : (cell, _rowIndex, colIndex) => {
  5553. const type = name(cell.element);
  5554. return !(type === 'th' && columnHeaders[colIndex]);
  5555. };
  5556. };
  5557. const columnReplacePredicate = (targetColumn, rowHeaders) => {
  5558. const entireTableIsHeader = forall(rowHeaders, identity) && isHeaderCells(targetColumn);
  5559. return entireTableIsHeader ? always : (cell, rowIndex, _colIndex) => {
  5560. const type = name(cell.element);
  5561. return !(type === 'th' && rowHeaders[rowIndex]);
  5562. };
  5563. };
  5564. const determineScope = (applyScope, cell, newScope, isInHeader) => {
  5565. const hasSpan = (scope) => scope === 'row' ? hasRowspan(cell) : hasColspan(cell);
  5566. const getScope = (scope) => hasSpan(scope) ? `${scope}group` : scope;
  5567. if (applyScope) {
  5568. return isHeaderCell(cell) ? getScope(newScope) : null;
  5569. }
  5570. else if (isInHeader && isHeaderCell(cell)) {
  5571. // The cell is still in a header row/column so ensure the right scope is reverted to
  5572. const oppositeScope = newScope === 'row' ? 'col' : 'row';
  5573. return getScope(oppositeScope);
  5574. }
  5575. else {
  5576. // No longer a header so ensure the scope is removed
  5577. return null;
  5578. }
  5579. };
  5580. const rowScopeGenerator = (applyScope, columnHeaders) => (cell, rowIndex, columnIndex) => Optional.some(determineScope(applyScope, cell.element, 'col', columnHeaders[columnIndex]));
  5581. const columnScopeGenerator = (applyScope, rowHeaders) => (cell, rowIndex) => Optional.some(determineScope(applyScope, cell.element, 'row', rowHeaders[rowIndex]));
  5582. const replace = (cell, comparator, substitute) => elementnew(substitute(cell.element, comparator), true, cell.isLocked);
  5583. const replaceIn = (grid, targets, comparator, substitute, replacer, genScope, shouldReplace) => {
  5584. const isTarget = (cell) => {
  5585. return exists(targets, (target) => {
  5586. return comparator(cell.element, target.element);
  5587. });
  5588. };
  5589. return map$1(grid, (row, rowIndex) => {
  5590. return mapCells(row, (cell, colIndex) => {
  5591. if (isTarget(cell)) {
  5592. const newCell = shouldReplace(cell, rowIndex, colIndex) ? replacer(cell, comparator, substitute) : cell;
  5593. // Update the scope
  5594. genScope(newCell, rowIndex, colIndex).each((scope) => {
  5595. setOptions(newCell.element, { scope: Optional.from(scope) });
  5596. });
  5597. return newCell;
  5598. }
  5599. else {
  5600. return cell;
  5601. }
  5602. });
  5603. });
  5604. };
  5605. const getColumnCells = (rows, columnIndex, comparator) => bind$2(rows, (row, i) => {
  5606. // check if already added.
  5607. return isDuplicatedCell(rows, i, columnIndex, comparator) ? [] : [getCell(row, columnIndex)];
  5608. });
  5609. const getRowCells = (rows, rowIndex, comparator) => {
  5610. const targetRow = rows[rowIndex];
  5611. return bind$2(targetRow.cells, (item, i) => {
  5612. // Check that we haven't already added this one.
  5613. return isDuplicatedCell(rows, rowIndex, i, comparator) ? [] : [item];
  5614. });
  5615. };
  5616. const replaceColumns = (grid, indexes, applyScope, comparator, substitution) => {
  5617. // Make this efficient later.
  5618. const rows = extractGridDetails(grid).rows;
  5619. const targets = bind$2(indexes, (index) => getColumnCells(rows, index, comparator));
  5620. const rowHeaders = map$1(rows, (row) => isHeaderCells(row.cells));
  5621. const shouldReplaceCell = columnReplacePredicate(targets, rowHeaders);
  5622. const scopeGenerator = columnScopeGenerator(applyScope, rowHeaders);
  5623. return replaceIn(grid, targets, comparator, substitution, replace, scopeGenerator, shouldReplaceCell);
  5624. };
  5625. const replaceRows = (grid, indexes, section, applyScope, comparator, substitution, tableSection) => {
  5626. const { cols, rows } = extractGridDetails(grid);
  5627. const targetRow = rows[indexes[0]];
  5628. const targets = bind$2(indexes, (index) => getRowCells(rows, index, comparator));
  5629. const columnHeaders = map$1(targetRow.cells, (_cell, index) => isHeaderCells(getColumnCells(rows, index, comparator)));
  5630. // Transform and replace the target row
  5631. // TODO: TINY-7776: This doesn't deal with rowspans which can break the layout when moving to a new section
  5632. const newRows = [...rows];
  5633. each$2(indexes, (index) => {
  5634. newRows[index] = tableSection.transformRow(rows[index], section);
  5635. });
  5636. const newGrid = [...cols, ...newRows];
  5637. const shouldReplaceCell = rowReplacerPredicate(targetRow, columnHeaders);
  5638. const scopeGenerator = rowScopeGenerator(applyScope, columnHeaders);
  5639. return replaceIn(newGrid, targets, comparator, substitution, tableSection.transformCell, scopeGenerator, shouldReplaceCell);
  5640. };
  5641. const replaceCells = (grid, details, comparator, substitution) => {
  5642. const rows = extractGridDetails(grid).rows;
  5643. const targetCells = map$1(details, (detail) => getCell(rows[detail.row], detail.column));
  5644. return replaceIn(grid, targetCells, comparator, substitution, replace, Optional.none, always);
  5645. };
  5646. const uniqueColumns = (details) => {
  5647. const uniqueCheck = (rest, detail) => {
  5648. const columnExists = exists(rest, (currentDetail) => currentDetail.column === detail.column);
  5649. return columnExists ? rest : rest.concat([detail]);
  5650. };
  5651. return foldl(details, uniqueCheck, []).sort((detailA, detailB) => detailA.column - detailB.column);
  5652. };
  5653. // This uses a slight variation to the default `ContentEditable.isEditable` behaviour,
  5654. // as when the element is detached we assume it is editable because it is a new cell.
  5655. const isEditable = (elem) => isEditable$1(elem, true);
  5656. const prune = (table) => {
  5657. const cells = cells$1(table);
  5658. if (cells.length === 0) {
  5659. remove$5(table);
  5660. }
  5661. };
  5662. const outcome = (grid, cursor) => ({
  5663. grid,
  5664. cursor
  5665. });
  5666. const findEditableCursorPosition = (rows) => findMap(rows, (row) => findMap(row.cells, (cell) => {
  5667. const elem = cell.element;
  5668. return someIf(isEditable(elem), elem);
  5669. }));
  5670. const elementFromGrid = (grid, row, column) => {
  5671. var _a, _b;
  5672. const rows = extractGridDetails(grid).rows;
  5673. return Optional.from((_b = (_a = rows[row]) === null || _a === void 0 ? void 0 : _a.cells[column]) === null || _b === void 0 ? void 0 : _b.element)
  5674. .filter(isEditable)
  5675. // Fallback to the first valid position in the table
  5676. .orThunk(() => findEditableCursorPosition(rows));
  5677. };
  5678. const bundle = (grid, row, column) => {
  5679. const cursorElement = elementFromGrid(grid, row, column);
  5680. return outcome(grid, cursorElement);
  5681. };
  5682. const uniqueRows = (details) => {
  5683. const rowCompilation = (rest, detail) => {
  5684. const rowExists = exists(rest, (currentDetail) => currentDetail.row === detail.row);
  5685. return rowExists ? rest : rest.concat([detail]);
  5686. };
  5687. return foldl(details, rowCompilation, []).sort((detailA, detailB) => detailA.row - detailB.row);
  5688. };
  5689. const opInsertRowsBefore = (grid, details, comparator, genWrappers) => {
  5690. const targetIndex = details[0].row;
  5691. const rows = uniqueRows(details);
  5692. const newGrid = foldr(rows, (acc, row) => {
  5693. const newG = insertRowAt(acc.grid, targetIndex, row.row + acc.delta, comparator, genWrappers.getOrInit);
  5694. return { grid: newG, delta: acc.delta + 1 };
  5695. }, { grid, delta: 0 }).grid;
  5696. return bundle(newGrid, targetIndex, details[0].column);
  5697. };
  5698. const opInsertRowsAfter = (grid, details, comparator, genWrappers) => {
  5699. const rows = uniqueRows(details);
  5700. const target = rows[rows.length - 1];
  5701. const targetIndex = target.row + target.rowspan;
  5702. const newGrid = foldr(rows, (newG, row) => {
  5703. return insertRowAt(newG, targetIndex, row.row, comparator, genWrappers.getOrInit);
  5704. }, grid);
  5705. return bundle(newGrid, targetIndex, details[0].column);
  5706. };
  5707. const opInsertColumnsBefore = (grid, extractDetail, comparator, genWrappers) => {
  5708. const details = extractDetail.details;
  5709. const columns = uniqueColumns(details);
  5710. const targetIndex = columns[0].column;
  5711. const newGrid = foldr(columns, (acc, col) => {
  5712. const newG = insertColumnAt(acc.grid, targetIndex, col.column + acc.delta, comparator, genWrappers.getOrInit);
  5713. return { grid: newG, delta: acc.delta + 1 };
  5714. }, { grid, delta: 0 }).grid;
  5715. return bundle(newGrid, details[0].row, targetIndex);
  5716. };
  5717. const opInsertColumnsAfter = (grid, extractDetail, comparator, genWrappers) => {
  5718. const details = extractDetail.details;
  5719. const target = details[details.length - 1];
  5720. const targetIndex = target.column + target.colspan;
  5721. const columns = uniqueColumns(details);
  5722. const newGrid = foldr(columns, (newG, col) => {
  5723. return insertColumnAt(newG, targetIndex, col.column, comparator, genWrappers.getOrInit);
  5724. }, grid);
  5725. return bundle(newGrid, details[0].row, targetIndex);
  5726. };
  5727. const opMakeColumnsHeader = (initialGrid, details, comparator, genWrappers) => {
  5728. const columns = uniqueColumns(details);
  5729. const columnIndexes = map$1(columns, (detail) => detail.column);
  5730. const newGrid = replaceColumns(initialGrid, columnIndexes, true, comparator, genWrappers.replaceOrInit);
  5731. return bundle(newGrid, details[0].row, details[0].column);
  5732. };
  5733. const opMakeCellsHeader = (initialGrid, details, comparator, genWrappers) => {
  5734. const newGrid = replaceCells(initialGrid, details, comparator, genWrappers.replaceOrInit);
  5735. return bundle(newGrid, details[0].row, details[0].column);
  5736. };
  5737. const opUnmakeColumnsHeader = (initialGrid, details, comparator, genWrappers) => {
  5738. const columns = uniqueColumns(details);
  5739. const columnIndexes = map$1(columns, (detail) => detail.column);
  5740. const newGrid = replaceColumns(initialGrid, columnIndexes, false, comparator, genWrappers.replaceOrInit);
  5741. return bundle(newGrid, details[0].row, details[0].column);
  5742. };
  5743. const opUnmakeCellsHeader = (initialGrid, details, comparator, genWrappers) => {
  5744. const newGrid = replaceCells(initialGrid, details, comparator, genWrappers.replaceOrInit);
  5745. return bundle(newGrid, details[0].row, details[0].column);
  5746. };
  5747. const makeRowsSection = (section, applyScope) => (initialGrid, details, comparator, genWrappers, tableSection) => {
  5748. const rows = uniqueRows(details);
  5749. const rowIndexes = map$1(rows, (detail) => detail.row);
  5750. const newGrid = replaceRows(initialGrid, rowIndexes, section, applyScope, comparator, genWrappers.replaceOrInit, tableSection);
  5751. return bundle(newGrid, details[0].row, details[0].column);
  5752. };
  5753. const opMakeRowsHeader = makeRowsSection('thead', true);
  5754. const opMakeRowsBody = makeRowsSection('tbody', false);
  5755. const opMakeRowsFooter = makeRowsSection('tfoot', false);
  5756. const opEraseColumns = (grid, extractDetail, _comparator, _genWrappers) => {
  5757. const columns = uniqueColumns(extractDetail.details);
  5758. const newGrid = deleteColumnsAt(grid, map$1(columns, (column) => column.column));
  5759. const maxColIndex = newGrid.length > 0 ? newGrid[0].cells.length - 1 : 0;
  5760. return bundle(newGrid, columns[0].row, Math.min(columns[0].column, maxColIndex));
  5761. };
  5762. const opEraseRows = (grid, details, _comparator, _genWrappers) => {
  5763. const rows = uniqueRows(details);
  5764. const newGrid = deleteRowsAt(grid, rows[0].row, rows[rows.length - 1].row);
  5765. const maxRowIndex = Math.max(extractGridDetails(newGrid).rows.length - 1, 0);
  5766. return bundle(newGrid, Math.min(details[0].row, maxRowIndex), details[0].column);
  5767. };
  5768. const opMergeCells = (grid, mergable, comparator, genWrappers) => {
  5769. const cells = mergable.cells;
  5770. merge$2(cells);
  5771. const newGrid = merge$1(grid, mergable.bounds, comparator, genWrappers.merge(cells));
  5772. return outcome(newGrid, Optional.from(cells[0]));
  5773. };
  5774. const opUnmergeCells = (grid, unmergable, comparator, genWrappers) => {
  5775. const unmerge$1 = (b, cell) => unmerge(b, cell, comparator, genWrappers.unmerge(cell));
  5776. const newGrid = foldr(unmergable, unmerge$1, grid);
  5777. return outcome(newGrid, Optional.from(unmergable[0]));
  5778. };
  5779. const opPasteCells = (grid, pasteDetails, comparator, _genWrappers) => {
  5780. const gridify = (table, generators) => {
  5781. const wh = Warehouse.fromTable(table);
  5782. return toGrid(wh, generators, true);
  5783. };
  5784. const gridB = gridify(pasteDetails.clipboard, pasteDetails.generators);
  5785. const startAddress = address(pasteDetails.row, pasteDetails.column);
  5786. const mergedGrid = merge(startAddress, grid, gridB, pasteDetails.generators, comparator);
  5787. return mergedGrid.fold(() => outcome(grid, Optional.some(pasteDetails.element)), (newGrid) => {
  5788. return bundle(newGrid, pasteDetails.row, pasteDetails.column);
  5789. });
  5790. };
  5791. const gridifyRows = (rows, generators, context) => {
  5792. const pasteDetails = fromPastedRows(rows, context.section);
  5793. const wh = Warehouse.generate(pasteDetails);
  5794. return toGrid(wh, generators, true);
  5795. };
  5796. const opPasteColsBefore = (grid, pasteDetails, comparator, _genWrappers) => {
  5797. const rows = extractGridDetails(grid).rows;
  5798. const index = pasteDetails.cells[0].column;
  5799. const context = rows[pasteDetails.cells[0].row];
  5800. const gridB = gridifyRows(pasteDetails.clipboard, pasteDetails.generators, context);
  5801. const mergedGrid = insertCols(index, grid, gridB, pasteDetails.generators, comparator);
  5802. return bundle(mergedGrid, pasteDetails.cells[0].row, pasteDetails.cells[0].column);
  5803. };
  5804. const opPasteColsAfter = (grid, pasteDetails, comparator, _genWrappers) => {
  5805. const rows = extractGridDetails(grid).rows;
  5806. const index = pasteDetails.cells[pasteDetails.cells.length - 1].column + pasteDetails.cells[pasteDetails.cells.length - 1].colspan;
  5807. const context = rows[pasteDetails.cells[0].row];
  5808. const gridB = gridifyRows(pasteDetails.clipboard, pasteDetails.generators, context);
  5809. const mergedGrid = insertCols(index, grid, gridB, pasteDetails.generators, comparator);
  5810. return bundle(mergedGrid, pasteDetails.cells[0].row, index);
  5811. };
  5812. const opPasteRowsBefore = (grid, pasteDetails, comparator, _genWrappers) => {
  5813. const rows = extractGridDetails(grid).rows;
  5814. const index = pasteDetails.cells[0].row;
  5815. const context = rows[index];
  5816. const gridB = gridifyRows(pasteDetails.clipboard, pasteDetails.generators, context);
  5817. const mergedGrid = insertRows(index, grid, gridB, pasteDetails.generators, comparator);
  5818. return bundle(mergedGrid, pasteDetails.cells[0].row, pasteDetails.cells[0].column);
  5819. };
  5820. const opPasteRowsAfter = (grid, pasteDetails, comparator, _genWrappers) => {
  5821. const rows = extractGridDetails(grid).rows;
  5822. const index = pasteDetails.cells[pasteDetails.cells.length - 1].row + pasteDetails.cells[pasteDetails.cells.length - 1].rowspan;
  5823. const context = rows[pasteDetails.cells[0].row];
  5824. const gridB = gridifyRows(pasteDetails.clipboard, pasteDetails.generators, context);
  5825. const mergedGrid = insertRows(index, grid, gridB, pasteDetails.generators, comparator);
  5826. return bundle(mergedGrid, index, pasteDetails.cells[0].column);
  5827. };
  5828. const opGetColumnsType = (table, target) => {
  5829. const house = Warehouse.fromTable(table);
  5830. const details = onCells(house, target);
  5831. return details.bind((selectedCells) => {
  5832. const lastSelectedCell = selectedCells[selectedCells.length - 1];
  5833. const minColRange = selectedCells[0].column;
  5834. const maxColRange = lastSelectedCell.column + lastSelectedCell.colspan;
  5835. const selectedColumnCells = flatten(map$1(house.all, (row) => filter$2(row.cells, (cell) => cell.column >= minColRange && cell.column < maxColRange)));
  5836. return findCommonCellType(selectedColumnCells);
  5837. }).getOr('');
  5838. };
  5839. const opGetCellsType = (table, target) => {
  5840. const house = Warehouse.fromTable(table);
  5841. const details = onCells(house, target);
  5842. return details.bind(findCommonCellType).getOr('');
  5843. };
  5844. const opGetRowsType = (table, target) => {
  5845. const house = Warehouse.fromTable(table);
  5846. const details = onCells(house, target);
  5847. return details.bind((selectedCells) => {
  5848. const lastSelectedCell = selectedCells[selectedCells.length - 1];
  5849. const minRowRange = selectedCells[0].row;
  5850. const maxRowRange = lastSelectedCell.row + lastSelectedCell.rowspan;
  5851. const selectedRows = house.all.slice(minRowRange, maxRowRange);
  5852. return findCommonRowType(selectedRows);
  5853. }).getOr('');
  5854. };
  5855. // Only column modifications force a resizing. Everything else just tries to preserve the table as is.
  5856. const resize = (table, list, details, behaviours) => adjustWidthTo(table, list, details, behaviours.sizing);
  5857. const adjustAndRedistributeWidths = (table, list, details, behaviours) => adjustAndRedistributeWidths$1(table, list, details, behaviours.sizing, behaviours.resize);
  5858. // Custom selection extractors
  5859. const firstColumnIsLocked = (_warehouse, details) => exists(details, (detail) => detail.column === 0 && detail.isLocked);
  5860. // TODO: Maybe have an Arr.existsR which would be more efficient for most cases below
  5861. const lastColumnIsLocked = (warehouse, details) => exists(details, (detail) => detail.column + detail.colspan >= warehouse.grid.columns && detail.isLocked);
  5862. const getColumnsWidth = (warehouse, details) => {
  5863. const columns$1 = columns(warehouse);
  5864. const uniqueCols = uniqueColumns(details);
  5865. return foldl(uniqueCols, (acc, detail) => {
  5866. const column = columns$1[detail.column];
  5867. const colWidth = column.map(getOuter).getOr(0);
  5868. return acc + colWidth;
  5869. }, 0);
  5870. };
  5871. const insertColumnsExtractor = (before) => (warehouse, target) => onCells(warehouse, target).filter((details) => {
  5872. const checkLocked = before ? firstColumnIsLocked : lastColumnIsLocked;
  5873. return !checkLocked(warehouse, details);
  5874. }).map((details) => ({
  5875. details,
  5876. pixelDelta: getColumnsWidth(warehouse, details),
  5877. }));
  5878. const eraseColumnsExtractor = (warehouse, target) => onUnlockedCells(warehouse, target).map((details) => ({
  5879. details,
  5880. pixelDelta: -getColumnsWidth(warehouse, details), // needs to be negative as we are removing columns
  5881. }));
  5882. const pasteColumnsExtractor = (before) => (warehouse, target) => onPasteByEditor(warehouse, target).filter((details) => {
  5883. const checkLocked = before ? firstColumnIsLocked : lastColumnIsLocked;
  5884. return !checkLocked(warehouse, details.cells);
  5885. });
  5886. const headerCellGenerator = Generators.transform('th');
  5887. const bodyCellGenerator = Generators.transform('td');
  5888. const insertRowsBefore = (table, target, generators, behaviours) => run(opInsertRowsBefore, onCells, noop, noop, Generators.modification, table, target, generators, behaviours);
  5889. const insertRowsAfter = (table, target, generators, behaviours) => run(opInsertRowsAfter, onCells, noop, noop, Generators.modification, table, target, generators, behaviours);
  5890. const insertColumnsBefore = (table, target, generators, behaviours) => run(opInsertColumnsBefore, insertColumnsExtractor(true), adjustAndRedistributeWidths, noop, Generators.modification, table, target, generators, behaviours);
  5891. const insertColumnsAfter = (table, target, generators, behaviours) => run(opInsertColumnsAfter, insertColumnsExtractor(false), adjustAndRedistributeWidths, noop, Generators.modification, table, target, generators, behaviours);
  5892. const eraseColumns = (table, target, generators, behaviours) => run(opEraseColumns, eraseColumnsExtractor, adjustAndRedistributeWidths, prune, Generators.modification, table, target, generators, behaviours);
  5893. const eraseRows = (table, target, generators, behaviours) => run(opEraseRows, onCells, noop, prune, Generators.modification, table, target, generators, behaviours);
  5894. const makeColumnsHeader = (table, target, generators, behaviours) => run(opMakeColumnsHeader, onUnlockedCells, noop, noop, headerCellGenerator, table, target, generators, behaviours);
  5895. const unmakeColumnsHeader = (table, target, generators, behaviours) => run(opUnmakeColumnsHeader, onUnlockedCells, noop, noop, bodyCellGenerator, table, target, generators, behaviours);
  5896. const makeRowsHeader = (table, target, generators, behaviours) => run(opMakeRowsHeader, onCells, noop, noop, headerCellGenerator, table, target, generators, behaviours);
  5897. const makeRowsBody = (table, target, generators, behaviours) => run(opMakeRowsBody, onCells, noop, noop, bodyCellGenerator, table, target, generators, behaviours);
  5898. const makeRowsFooter = (table, target, generators, behaviours) => run(opMakeRowsFooter, onCells, noop, noop, bodyCellGenerator, table, target, generators, behaviours);
  5899. const makeCellsHeader = (table, target, generators, behaviours) => run(opMakeCellsHeader, onUnlockedCells, noop, noop, headerCellGenerator, table, target, generators, behaviours);
  5900. const unmakeCellsHeader = (table, target, generators, behaviours) => run(opUnmakeCellsHeader, onUnlockedCells, noop, noop, bodyCellGenerator, table, target, generators, behaviours);
  5901. const mergeCells = (table, target, generators, behaviours) => run(opMergeCells, onUnlockedMergable, resize, noop, Generators.merging, table, target, generators, behaviours);
  5902. const unmergeCells = (table, target, generators, behaviours) => run(opUnmergeCells, onUnlockedUnmergable, resize, noop, Generators.merging, table, target, generators, behaviours);
  5903. const pasteCells = (table, target, generators, behaviours) => run(opPasteCells, onPaste, resize, noop, Generators.modification, table, target, generators, behaviours);
  5904. const pasteColsBefore = (table, target, generators, behaviours) => run(opPasteColsBefore, pasteColumnsExtractor(true), noop, noop, Generators.modification, table, target, generators, behaviours);
  5905. const pasteColsAfter = (table, target, generators, behaviours) => run(opPasteColsAfter, pasteColumnsExtractor(false), noop, noop, Generators.modification, table, target, generators, behaviours);
  5906. const pasteRowsBefore = (table, target, generators, behaviours) => run(opPasteRowsBefore, onPasteByEditor, noop, noop, Generators.modification, table, target, generators, behaviours);
  5907. const pasteRowsAfter = (table, target, generators, behaviours) => run(opPasteRowsAfter, onPasteByEditor, noop, noop, Generators.modification, table, target, generators, behaviours);
  5908. const getColumnsType = opGetColumnsType;
  5909. const getCellsType = opGetCellsType;
  5910. const getRowsType = opGetRowsType;
  5911. const inSelection = (bounds, detail) => {
  5912. const leftEdge = detail.column;
  5913. const rightEdge = detail.column + detail.colspan - 1;
  5914. const topEdge = detail.row;
  5915. const bottomEdge = detail.row + detail.rowspan - 1;
  5916. return (leftEdge <= bounds.finishCol && rightEdge >= bounds.startCol) && (topEdge <= bounds.finishRow && bottomEdge >= bounds.startRow);
  5917. };
  5918. // Note, something is *within* if it is completely contained within the bounds.
  5919. const isWithin = (bounds, detail) => {
  5920. return (detail.column >= bounds.startCol &&
  5921. (detail.column + detail.colspan - 1) <= bounds.finishCol &&
  5922. detail.row >= bounds.startRow &&
  5923. (detail.row + detail.rowspan - 1) <= bounds.finishRow);
  5924. };
  5925. const isRectangular = (warehouse, bounds) => {
  5926. let isRect = true;
  5927. const detailIsWithin = curry(isWithin, bounds);
  5928. for (let i = bounds.startRow; i <= bounds.finishRow; i++) {
  5929. for (let j = bounds.startCol; j <= bounds.finishCol; j++) {
  5930. isRect = isRect && Warehouse.getAt(warehouse, i, j).exists(detailIsWithin);
  5931. }
  5932. }
  5933. return isRect ? Optional.some(bounds) : Optional.none();
  5934. };
  5935. const getBounds = (detailA, detailB) => {
  5936. return bounds(Math.min(detailA.row, detailB.row), Math.min(detailA.column, detailB.column), Math.max(detailA.row + detailA.rowspan - 1, detailB.row + detailB.rowspan - 1), Math.max(detailA.column + detailA.colspan - 1, detailB.column + detailB.colspan - 1));
  5937. };
  5938. const getAnyBox = (warehouse, startCell, finishCell) => {
  5939. const startCoords = Warehouse.findItem(warehouse, startCell, eq$1);
  5940. const finishCoords = Warehouse.findItem(warehouse, finishCell, eq$1);
  5941. return startCoords.bind((sc) => {
  5942. return finishCoords.map((fc) => {
  5943. return getBounds(sc, fc);
  5944. });
  5945. });
  5946. };
  5947. const getBox$1 = (warehouse, startCell, finishCell) => {
  5948. return getAnyBox(warehouse, startCell, finishCell).bind((bounds) => {
  5949. return isRectangular(warehouse, bounds);
  5950. });
  5951. };
  5952. const moveBy$1 = (warehouse, cell, row, column) => {
  5953. return Warehouse.findItem(warehouse, cell, eq$1).bind((detail) => {
  5954. const startRow = row > 0 ? detail.row + detail.rowspan - 1 : detail.row;
  5955. const startCol = column > 0 ? detail.column + detail.colspan - 1 : detail.column;
  5956. const dest = Warehouse.getAt(warehouse, startRow + row, startCol + column);
  5957. return dest.map((d) => {
  5958. return d.element;
  5959. });
  5960. });
  5961. };
  5962. const intercepts$1 = (warehouse, start, finish) => {
  5963. return getAnyBox(warehouse, start, finish).map((bounds) => {
  5964. const inside = Warehouse.filterItems(warehouse, curry(inSelection, bounds));
  5965. return map$1(inside, (detail) => {
  5966. return detail.element;
  5967. });
  5968. });
  5969. };
  5970. const parentCell = (warehouse, innerCell) => {
  5971. const isContainedBy = (c1, c2) => {
  5972. return contains(c2, c1);
  5973. };
  5974. return Warehouse.findItem(warehouse, innerCell, isContainedBy).map((detail) => {
  5975. return detail.element;
  5976. });
  5977. };
  5978. const moveBy = (cell, deltaRow, deltaColumn) => {
  5979. return table(cell).bind((table) => {
  5980. const warehouse = getWarehouse(table);
  5981. return moveBy$1(warehouse, cell, deltaRow, deltaColumn);
  5982. });
  5983. };
  5984. const intercepts = (table, first, last) => {
  5985. const warehouse = getWarehouse(table);
  5986. return intercepts$1(warehouse, first, last);
  5987. };
  5988. const nestedIntercepts = (table, first, firstTable, last, lastTable) => {
  5989. const warehouse = getWarehouse(table);
  5990. const optStartCell = eq$1(table, firstTable) ? Optional.some(first) : parentCell(warehouse, first);
  5991. const optLastCell = eq$1(table, lastTable) ? Optional.some(last) : parentCell(warehouse, last);
  5992. return optStartCell.bind((startCell) => optLastCell.bind((lastCell) => intercepts$1(warehouse, startCell, lastCell)));
  5993. };
  5994. const getBox = (table, first, last) => {
  5995. const warehouse = getWarehouse(table);
  5996. return getBox$1(warehouse, first, last);
  5997. };
  5998. // Private method ... keep warehouse in snooker, please.
  5999. const getWarehouse = Warehouse.fromTable;
  6000. const DefaultRenderOptions = {
  6001. styles: {
  6002. 'border-collapse': 'collapse',
  6003. 'width': '100%'
  6004. },
  6005. attributes: {
  6006. border: '1'
  6007. },
  6008. colGroups: false
  6009. };
  6010. const tableHeaderCell = () => SugarElement.fromTag('th');
  6011. const tableCell = () => SugarElement.fromTag('td');
  6012. const tableColumn = () => SugarElement.fromTag('col');
  6013. const createRow = (columns, rowHeaders, columnHeaders, rowIndex) => {
  6014. const tr = SugarElement.fromTag('tr');
  6015. for (let j = 0; j < columns; j++) {
  6016. const td = rowIndex < rowHeaders || j < columnHeaders ? tableHeaderCell() : tableCell();
  6017. if (j < columnHeaders) {
  6018. set$2(td, 'scope', 'row');
  6019. }
  6020. if (rowIndex < rowHeaders) {
  6021. set$2(td, 'scope', 'col');
  6022. }
  6023. // Note, this is a placeholder so that the cells have height. The unicode character didn't work in IE10.
  6024. append$1(td, SugarElement.fromTag('br'));
  6025. append$1(tr, td);
  6026. }
  6027. return tr;
  6028. };
  6029. const createGroupRow = (columns) => {
  6030. const columnGroup = SugarElement.fromTag('colgroup');
  6031. range$1(columns, () => append$1(columnGroup, tableColumn()));
  6032. return columnGroup;
  6033. };
  6034. const createRows = (rows, columns, rowHeaders, columnHeaders) => range$1(rows, (r) => createRow(columns, rowHeaders, columnHeaders, r));
  6035. const render = (rows, columns, rowHeaders, columnHeaders, headerType, renderOpts = DefaultRenderOptions) => {
  6036. const table = SugarElement.fromTag('table');
  6037. const rowHeadersGoInThead = headerType !== 'cells';
  6038. setAll(table, renderOpts.styles);
  6039. setAll$1(table, renderOpts.attributes);
  6040. if (renderOpts.colGroups) {
  6041. append$1(table, createGroupRow(columns));
  6042. }
  6043. const actualRowHeaders = Math.min(rows, rowHeaders);
  6044. if (rowHeadersGoInThead && rowHeaders > 0) {
  6045. const thead = SugarElement.fromTag('thead');
  6046. append$1(table, thead);
  6047. const theadRowHeaders = headerType === 'sectionCells' ? actualRowHeaders : 0;
  6048. const theadRows = createRows(rowHeaders, columns, theadRowHeaders, columnHeaders);
  6049. append(thead, theadRows);
  6050. }
  6051. const tbody = SugarElement.fromTag('tbody');
  6052. append$1(table, tbody);
  6053. const numRows = rowHeadersGoInThead ? rows - actualRowHeaders : rows;
  6054. const numRowHeaders = rowHeadersGoInThead ? 0 : rowHeaders;
  6055. const tbodyRows = createRows(numRows, columns, numRowHeaders, columnHeaders);
  6056. append(tbody, tbodyRows);
  6057. return table;
  6058. };
  6059. const Event = (fields) => {
  6060. let handlers = [];
  6061. const bind = (handler) => {
  6062. if (handler === undefined) {
  6063. throw new Error('Event bind error: undefined handler');
  6064. }
  6065. handlers.push(handler);
  6066. };
  6067. const unbind = (handler) => {
  6068. // This is quite a bit slower than handlers.splice() but we hate mutation.
  6069. // Unbind isn't used very often so it should be ok.
  6070. handlers = filter$2(handlers, (h) => {
  6071. return h !== handler;
  6072. });
  6073. };
  6074. const trigger = (...args) => {
  6075. const event = {};
  6076. each$2(fields, (name, i) => {
  6077. event[name] = args[i];
  6078. });
  6079. each$2(handlers, (handler) => {
  6080. handler(event);
  6081. });
  6082. };
  6083. return {
  6084. bind,
  6085. unbind,
  6086. trigger
  6087. };
  6088. };
  6089. /** :: {name : Event} -> Events */
  6090. const create$3 = (typeDefs) => {
  6091. const registry = map(typeDefs, (event) => {
  6092. return {
  6093. bind: event.bind,
  6094. unbind: event.unbind
  6095. };
  6096. });
  6097. const trigger = map(typeDefs, (event) => {
  6098. return event.trigger;
  6099. });
  6100. return {
  6101. registry,
  6102. trigger
  6103. };
  6104. };
  6105. const DragMode = exactly([
  6106. 'compare',
  6107. 'extract',
  6108. 'mutate',
  6109. 'sink'
  6110. ]);
  6111. const DragSink = exactly([
  6112. 'element',
  6113. 'start',
  6114. 'stop',
  6115. 'destroy'
  6116. ]);
  6117. const DragApi = exactly([
  6118. 'forceDrop',
  6119. 'drop',
  6120. 'move',
  6121. 'delayDrop'
  6122. ]);
  6123. const InDrag = () => {
  6124. let previous = Optional.none();
  6125. const reset = () => {
  6126. previous = Optional.none();
  6127. };
  6128. // Return position delta between previous position and nu position,
  6129. // or None if this is the first. Set the previous position to nu.
  6130. const update = (mode, nu) => {
  6131. const result = previous.map((old) => {
  6132. return mode.compare(old, nu);
  6133. });
  6134. previous = Optional.some(nu);
  6135. return result;
  6136. };
  6137. const onEvent = (event, mode) => {
  6138. const dataOption = mode.extract(event);
  6139. // Dragster move events require a position delta. The moveevent is only triggered
  6140. // on the second and subsequent dragster move events. The first is dropped.
  6141. dataOption.each((data) => {
  6142. const offset = update(mode, data);
  6143. offset.each((d) => {
  6144. events.trigger.move(d);
  6145. });
  6146. });
  6147. };
  6148. const events = create$3({
  6149. move: Event(['info'])
  6150. });
  6151. return {
  6152. onEvent,
  6153. reset,
  6154. events: events.registry
  6155. };
  6156. };
  6157. const NoDrag = () => {
  6158. const events = create$3({
  6159. move: Event(['info'])
  6160. });
  6161. return {
  6162. onEvent: noop,
  6163. reset: noop,
  6164. events: events.registry
  6165. };
  6166. };
  6167. const Movement = () => {
  6168. const noDragState = NoDrag();
  6169. const inDragState = InDrag();
  6170. let dragState = noDragState;
  6171. const on = () => {
  6172. dragState.reset();
  6173. dragState = inDragState;
  6174. };
  6175. const off = () => {
  6176. dragState.reset();
  6177. dragState = noDragState;
  6178. };
  6179. const onEvent = (event, mode) => {
  6180. dragState.onEvent(event, mode);
  6181. };
  6182. const isOn = () => {
  6183. return dragState === inDragState;
  6184. };
  6185. return {
  6186. on,
  6187. off,
  6188. isOn,
  6189. onEvent,
  6190. events: inDragState.events
  6191. };
  6192. };
  6193. const setup = (mutation, mode, settings) => {
  6194. let active = false;
  6195. const events = create$3({
  6196. start: Event([]),
  6197. stop: Event([])
  6198. });
  6199. const movement = Movement();
  6200. const drop = () => {
  6201. sink.stop();
  6202. if (movement.isOn()) {
  6203. movement.off();
  6204. events.trigger.stop();
  6205. }
  6206. };
  6207. const throttledDrop = last$1(drop, 200);
  6208. const go = (parent) => {
  6209. sink.start(parent);
  6210. movement.on();
  6211. events.trigger.start();
  6212. };
  6213. const mousemove = (event) => {
  6214. throttledDrop.cancel();
  6215. movement.onEvent(event, mode);
  6216. };
  6217. movement.events.move.bind((event) => {
  6218. mode.mutate(mutation, event.info);
  6219. });
  6220. const on = () => {
  6221. active = true;
  6222. };
  6223. const off = () => {
  6224. active = false;
  6225. // acivate some events here?
  6226. };
  6227. const isActive = () => active;
  6228. const runIfActive = (f) => {
  6229. return (...args) => {
  6230. if (active) {
  6231. f.apply(null, args);
  6232. }
  6233. };
  6234. };
  6235. const sink = mode.sink(DragApi({
  6236. // ASSUMPTION: runIfActive is not needed for mousedown. This is pretty much a safety measure for
  6237. // inconsistent situations so that we don't block input.
  6238. forceDrop: drop,
  6239. drop: runIfActive(drop),
  6240. move: runIfActive(mousemove),
  6241. delayDrop: runIfActive(throttledDrop.throttle)
  6242. }), settings);
  6243. const destroy = () => {
  6244. sink.destroy();
  6245. };
  6246. return {
  6247. element: sink.element,
  6248. go,
  6249. on,
  6250. off,
  6251. isActive,
  6252. destroy,
  6253. events: events.registry
  6254. };
  6255. };
  6256. const styles$1 = css('ephox-dragster');
  6257. const resolve$1 = styles$1.resolve;
  6258. const Blocker = (options) => {
  6259. const settings = {
  6260. layerClass: resolve$1('blocker'),
  6261. ...options
  6262. };
  6263. const div = SugarElement.fromTag('div');
  6264. set$2(div, 'role', 'presentation');
  6265. setAll(div, {
  6266. position: 'fixed',
  6267. left: '0px',
  6268. top: '0px',
  6269. width: '100%',
  6270. height: '100%'
  6271. });
  6272. add$1(div, resolve$1('blocker'));
  6273. add$1(div, settings.layerClass);
  6274. const element = constant(div);
  6275. const destroy = () => {
  6276. remove$5(div);
  6277. };
  6278. return {
  6279. element,
  6280. destroy
  6281. };
  6282. };
  6283. const compare = (old, nu) => {
  6284. return SugarPosition(nu.left - old.left, nu.top - old.top);
  6285. };
  6286. const extract = (event) => {
  6287. return Optional.some(SugarPosition(event.x, event.y));
  6288. };
  6289. const mutate = (mutation, info) => {
  6290. mutation.mutate(info.left, info.top);
  6291. };
  6292. const sink = (dragApi, settings) => {
  6293. const blocker = Blocker(settings);
  6294. // Included for safety. If the blocker has stayed on the screen, get rid of it on a click.
  6295. const mdown = bind(blocker.element(), 'mousedown', dragApi.forceDrop);
  6296. const mup = bind(blocker.element(), 'mouseup', dragApi.drop);
  6297. const mmove = bind(blocker.element(), 'mousemove', dragApi.move);
  6298. const mout = bind(blocker.element(), 'mouseout', dragApi.delayDrop);
  6299. const destroy = () => {
  6300. blocker.destroy();
  6301. mup.unbind();
  6302. mmove.unbind();
  6303. mout.unbind();
  6304. mdown.unbind();
  6305. };
  6306. const start = (parent) => {
  6307. append$1(parent, blocker.element());
  6308. };
  6309. const stop = () => {
  6310. remove$5(blocker.element());
  6311. };
  6312. return DragSink({
  6313. element: blocker.element,
  6314. start,
  6315. stop,
  6316. destroy
  6317. });
  6318. };
  6319. var MouseDrag = DragMode({
  6320. compare,
  6321. extract,
  6322. sink,
  6323. mutate
  6324. });
  6325. const transform = (mutation, settings = {}) => {
  6326. var _a;
  6327. const mode = (_a = settings.mode) !== null && _a !== void 0 ? _a : MouseDrag;
  6328. return setup(mutation, mode, settings);
  6329. };
  6330. const styles = css('ephox-snooker');
  6331. const resolve = styles.resolve;
  6332. const Mutation = () => {
  6333. const events = create$3({
  6334. drag: Event(['xDelta', 'yDelta'])
  6335. });
  6336. const mutate = (x, y) => {
  6337. events.trigger.drag(x, y);
  6338. };
  6339. return {
  6340. mutate,
  6341. events: events.registry
  6342. };
  6343. };
  6344. const BarMutation = () => {
  6345. const events = create$3({
  6346. drag: Event(['xDelta', 'yDelta', 'target'])
  6347. });
  6348. let target = Optional.none();
  6349. const delegate = Mutation();
  6350. delegate.events.drag.bind((event) => {
  6351. target.each((t) => {
  6352. // There is always going to be this padding / border collapse / margin problem with widths. I'll have to resolve that.
  6353. events.trigger.drag(event.xDelta, event.yDelta, t);
  6354. });
  6355. });
  6356. const assign = (t) => {
  6357. target = Optional.some(t);
  6358. };
  6359. const get = () => {
  6360. return target;
  6361. };
  6362. return {
  6363. assign,
  6364. get,
  6365. mutate: delegate.mutate,
  6366. events: events.registry
  6367. };
  6368. };
  6369. const col = (column, x, y, w, h) => {
  6370. const bar = SugarElement.fromTag('div');
  6371. setAll(bar, {
  6372. position: 'absolute',
  6373. left: x - w / 2 + 'px',
  6374. top: y + 'px',
  6375. height: h + 'px',
  6376. width: w + 'px'
  6377. });
  6378. setAll$1(bar, { 'data-mce-bogus': 'all', 'data-column': column, 'role': 'presentation' });
  6379. return bar;
  6380. };
  6381. const row = (r, x, y, w, h) => {
  6382. const bar = SugarElement.fromTag('div');
  6383. setAll(bar, {
  6384. position: 'absolute',
  6385. left: x + 'px',
  6386. top: y - h / 2 + 'px',
  6387. height: h + 'px',
  6388. width: w + 'px'
  6389. });
  6390. setAll$1(bar, { 'data-mce-bogus': 'all', 'data-row': r, 'role': 'presentation' });
  6391. return bar;
  6392. };
  6393. const resizeBar = resolve('resizer-bar');
  6394. const resizeRowBar = resolve('resizer-rows');
  6395. const resizeColBar = resolve('resizer-cols');
  6396. const BAR_THICKNESS = 7;
  6397. const resizableRows = (warehouse, isResizable) => bind$2(warehouse.all, (row, i) => isResizable(row.element) ? [i] : []);
  6398. const resizableColumns = (warehouse, isResizable) => {
  6399. const resizableCols = [];
  6400. // Check col elements and see if they are resizable
  6401. range$1(warehouse.grid.columns, (index) => {
  6402. // With use of forall, index will be included if col doesn't exist meaning the column cells will be checked below
  6403. const colElmOpt = Warehouse.getColumnAt(warehouse, index).map((col) => col.element);
  6404. if (colElmOpt.forall(isResizable)) {
  6405. resizableCols.push(index);
  6406. }
  6407. });
  6408. // Check cells of the resizable columns and make sure they are resizable
  6409. return filter$2(resizableCols, (colIndex) => {
  6410. const columnCells = Warehouse.filterItems(warehouse, (cell) => cell.column === colIndex);
  6411. return forall(columnCells, (cell) => isResizable(cell.element));
  6412. });
  6413. };
  6414. const destroy = (wire) => {
  6415. const previous = descendants(wire.parent(), '.' + resizeBar);
  6416. each$2(previous, remove$5);
  6417. };
  6418. const drawBar = (wire, positions, create) => {
  6419. const origin = wire.origin();
  6420. each$2(positions, (cpOption) => {
  6421. cpOption.each((cp) => {
  6422. const bar = create(origin, cp);
  6423. add$1(bar, resizeBar);
  6424. append$1(wire.parent(), bar);
  6425. });
  6426. });
  6427. };
  6428. const refreshCol = (wire, colPositions, position, tableHeight) => {
  6429. drawBar(wire, colPositions, (origin, cp) => {
  6430. const colBar = col(cp.col, cp.x - origin.left, position.top - origin.top, BAR_THICKNESS, tableHeight);
  6431. add$1(colBar, resizeColBar);
  6432. return colBar;
  6433. });
  6434. };
  6435. const refreshRow = (wire, rowPositions, position, tableWidth) => {
  6436. drawBar(wire, rowPositions, (origin, cp) => {
  6437. const rowBar = row(cp.row, position.left - origin.left, cp.y - origin.top, tableWidth, BAR_THICKNESS);
  6438. add$1(rowBar, resizeRowBar);
  6439. return rowBar;
  6440. });
  6441. };
  6442. const refreshGrid = (warhouse, wire, table, rows, cols) => {
  6443. const position = absolute(table);
  6444. const isResizable = wire.isResizable;
  6445. const rowPositions = rows.length > 0 ? height.positions(rows, table) : [];
  6446. const resizableRowBars = rowPositions.length > 0 ? resizableRows(warhouse, isResizable) : [];
  6447. const resizableRowPositions = filter$2(rowPositions, (_pos, i) => exists(resizableRowBars, (barIndex) => i === barIndex));
  6448. refreshRow(wire, resizableRowPositions, position, getOuter(table));
  6449. const colPositions = cols.length > 0 ? width.positions(cols, table) : [];
  6450. const resizableColBars = colPositions.length > 0 ? resizableColumns(warhouse, isResizable) : [];
  6451. const resizableColPositions = filter$2(colPositions, (_pos, i) => exists(resizableColBars, (barIndex) => i === barIndex));
  6452. refreshCol(wire, resizableColPositions, position, getOuter$1(table));
  6453. };
  6454. const refresh = (wire, table) => {
  6455. destroy(wire);
  6456. if (wire.isResizable(table)) {
  6457. const warehouse = Warehouse.fromTable(table);
  6458. const rows$1 = rows(warehouse);
  6459. const cols = columns(warehouse);
  6460. refreshGrid(warehouse, wire, table, rows$1, cols);
  6461. }
  6462. };
  6463. const each = (wire, f) => {
  6464. const bars = descendants(wire.parent(), '.' + resizeBar);
  6465. each$2(bars, f);
  6466. };
  6467. const hide = (wire) => {
  6468. each(wire, (bar) => {
  6469. set$1(bar, 'display', 'none');
  6470. });
  6471. };
  6472. const show = (wire) => {
  6473. each(wire, (bar) => {
  6474. set$1(bar, 'display', 'block');
  6475. });
  6476. };
  6477. const isRowBar = (element) => {
  6478. return has(element, resizeRowBar);
  6479. };
  6480. const isColBar = (element) => {
  6481. return has(element, resizeColBar);
  6482. };
  6483. const resizeBarDragging = resolve('resizer-bar-dragging');
  6484. const BarManager = (wire) => {
  6485. const mutation = BarMutation();
  6486. const resizing = transform(mutation, {});
  6487. let hoverTable = Optional.none();
  6488. const getResizer = (element, type) => {
  6489. return Optional.from(get$b(element, type));
  6490. };
  6491. /* Reposition the bar as the user drags */
  6492. mutation.events.drag.bind((event) => {
  6493. getResizer(event.target, 'data-row').each((_dataRow) => {
  6494. const currentRow = getCssValue(event.target, 'top');
  6495. set$1(event.target, 'top', currentRow + event.yDelta + 'px');
  6496. });
  6497. getResizer(event.target, 'data-column').each((_dataCol) => {
  6498. const currentCol = getCssValue(event.target, 'left');
  6499. set$1(event.target, 'left', currentCol + event.xDelta + 'px');
  6500. });
  6501. });
  6502. const getDelta = (target, dir) => {
  6503. const newX = getCssValue(target, dir);
  6504. const oldX = getAttrValue(target, 'data-initial-' + dir, 0);
  6505. return newX - oldX;
  6506. };
  6507. /* Resize the column once the user releases the mouse */
  6508. resizing.events.stop.bind(() => {
  6509. mutation.get().each((target) => {
  6510. hoverTable.each((table) => {
  6511. getResizer(target, 'data-row').each((row) => {
  6512. const delta = getDelta(target, 'top');
  6513. remove$6(target, 'data-initial-top');
  6514. events.trigger.adjustHeight(table, delta, parseInt(row, 10));
  6515. });
  6516. getResizer(target, 'data-column').each((column) => {
  6517. const delta = getDelta(target, 'left');
  6518. remove$6(target, 'data-initial-left');
  6519. events.trigger.adjustWidth(table, delta, parseInt(column, 10));
  6520. });
  6521. refresh(wire, table);
  6522. });
  6523. });
  6524. });
  6525. const handler = (target, dir) => {
  6526. events.trigger.startAdjust();
  6527. mutation.assign(target);
  6528. set$2(target, 'data-initial-' + dir, getCssValue(target, dir));
  6529. add$1(target, resizeBarDragging);
  6530. set$1(target, 'opacity', '0.2');
  6531. resizing.go(wire.dragContainer());
  6532. };
  6533. /* mousedown on resize bar: start dragging when the bar is clicked, storing the initial position. */
  6534. const mousedown = bind(wire.parent(), 'mousedown', (event) => {
  6535. if (isRowBar(event.target)) {
  6536. handler(event.target, 'top');
  6537. }
  6538. if (isColBar(event.target)) {
  6539. handler(event.target, 'left');
  6540. }
  6541. });
  6542. const isRoot = (e) => {
  6543. return eq$1(e, wire.view());
  6544. };
  6545. const findClosestEditableTable = (target) => closest$1(target, 'table', isRoot).filter(isEditable$1);
  6546. const isResizer = (target) => has(target, 'ephox-snooker-resizer-bar') || has(target, 'ephox-dragster-blocker');
  6547. /* mouseover on table: When the mouse moves within the CONTENT AREA (NOT THE TABLE), refresh the bars. */
  6548. const mouseover = bind(wire.view(), 'mouseover', (event) => {
  6549. findClosestEditableTable(event.target).fold(() => {
  6550. /*
  6551. * mouseout is not reliable within ContentEditable, so for all other mouseover events we clear bars.
  6552. * This is fairly safe to do frequently; it's a single querySelectorAll() on the content and Arr.map on the result.
  6553. * If we _really_ need to optimise it further, we can start caching the bar references in the wire somehow.
  6554. *
  6555. * Because the resizers were moved into the editor for inline mode, we need to check if the event target is not a resizer.
  6556. */
  6557. if (inBody(event.target) && !isResizer(event.target)) {
  6558. destroy(wire);
  6559. }
  6560. }, (table) => {
  6561. if (resizing.isActive()) {
  6562. hoverTable = Optional.some(table);
  6563. refresh(wire, table);
  6564. }
  6565. });
  6566. });
  6567. const destroy$1 = () => {
  6568. mousedown.unbind();
  6569. mouseover.unbind();
  6570. resizing.destroy();
  6571. destroy(wire);
  6572. };
  6573. const refresh$1 = (tbl) => {
  6574. refresh(wire, tbl);
  6575. };
  6576. const events = create$3({
  6577. adjustHeight: Event(['table', 'delta', 'row']),
  6578. adjustWidth: Event(['table', 'delta', 'column']),
  6579. startAdjust: Event([])
  6580. });
  6581. return {
  6582. destroy: destroy$1,
  6583. refresh: refresh$1,
  6584. on: resizing.on,
  6585. off: resizing.off,
  6586. hideBars: curry(hide, wire),
  6587. showBars: curry(show, wire),
  6588. events: events.registry
  6589. };
  6590. };
  6591. const create$2 = (wire, resizing, lazySizing) => {
  6592. const hdirection = height;
  6593. const vdirection = width;
  6594. const manager = BarManager(wire);
  6595. const events = create$3({
  6596. beforeResize: Event(['table', 'type']),
  6597. afterResize: Event(['table', 'type']),
  6598. startDrag: Event([]),
  6599. });
  6600. manager.events.adjustHeight.bind((event) => {
  6601. const table = event.table;
  6602. events.trigger.beforeResize(table, 'row');
  6603. const delta = hdirection.delta(event.delta, table);
  6604. // TODO: Use the resizing behaviour for heights as well
  6605. adjustHeight(table, delta, event.row);
  6606. events.trigger.afterResize(table, 'row');
  6607. });
  6608. manager.events.startAdjust.bind((_event) => {
  6609. events.trigger.startDrag();
  6610. });
  6611. manager.events.adjustWidth.bind((event) => {
  6612. const table = event.table;
  6613. events.trigger.beforeResize(table, 'col');
  6614. const delta = vdirection.delta(event.delta, table);
  6615. const tableSize = lazySizing(table);
  6616. adjustWidth(table, delta, event.column, resizing, tableSize);
  6617. events.trigger.afterResize(table, 'col');
  6618. });
  6619. return {
  6620. on: manager.on,
  6621. off: manager.off,
  6622. refreshBars: manager.refresh,
  6623. hideBars: manager.hideBars,
  6624. showBars: manager.showBars,
  6625. destroy: manager.destroy,
  6626. events: events.registry
  6627. };
  6628. };
  6629. const TableResize = {
  6630. create: create$2
  6631. };
  6632. const option = (name) => (editor) => editor.options.get(name);
  6633. // Note: This is also contained in the table plugin Options.ts file
  6634. const defaultWidth = '100%';
  6635. const getPixelForcedWidth = (editor) => {
  6636. var _a;
  6637. // Determine the inner size of the parent block element where the table will be inserted
  6638. const dom = editor.dom;
  6639. const parentBlock = (_a = dom.getParent(editor.selection.getStart(), dom.isBlock)) !== null && _a !== void 0 ? _a : editor.getBody();
  6640. return getInner(SugarElement.fromDom(parentBlock)) + 'px';
  6641. };
  6642. // Note: This is also contained in the table plugin Options.ts file
  6643. const determineDefaultTableStyles = (editor, defaultStyles) => {
  6644. if (isTableResponsiveForced(editor) || !shouldStyleWithCss(editor)) {
  6645. return defaultStyles;
  6646. }
  6647. else if (isTablePixelsForced(editor)) {
  6648. return { ...defaultStyles, width: getPixelForcedWidth(editor) };
  6649. }
  6650. else {
  6651. return { ...defaultStyles, width: defaultWidth };
  6652. }
  6653. };
  6654. // Note: This is also contained in the table plugin Options.ts file
  6655. const determineDefaultTableAttributes = (editor, defaultAttributes) => {
  6656. if (isTableResponsiveForced(editor) || shouldStyleWithCss(editor)) {
  6657. return defaultAttributes;
  6658. }
  6659. else if (isTablePixelsForced(editor)) {
  6660. return { ...defaultAttributes, width: getPixelForcedWidth(editor) };
  6661. }
  6662. else {
  6663. return { ...defaultAttributes, width: defaultWidth };
  6664. }
  6665. };
  6666. const register = (editor) => {
  6667. const registerOption = editor.options.register;
  6668. registerOption('table_clone_elements', {
  6669. processor: 'string[]'
  6670. });
  6671. registerOption('table_use_colgroups', {
  6672. processor: 'boolean',
  6673. default: true
  6674. });
  6675. registerOption('table_header_type', {
  6676. processor: (value) => {
  6677. const valid = contains$2(['section', 'cells', 'sectionCells', 'auto'], value);
  6678. return valid ? { value, valid } : { valid: false, message: 'Must be one of: section, cells, sectionCells or auto.' };
  6679. },
  6680. default: 'section'
  6681. });
  6682. registerOption('table_sizing_mode', {
  6683. processor: 'string',
  6684. default: 'auto'
  6685. });
  6686. registerOption('table_default_attributes', {
  6687. processor: 'object',
  6688. default: {
  6689. border: '1'
  6690. }
  6691. });
  6692. registerOption('table_default_styles', {
  6693. processor: 'object',
  6694. default: {
  6695. 'border-collapse': 'collapse',
  6696. }
  6697. });
  6698. registerOption('table_column_resizing', {
  6699. processor: (value) => {
  6700. const valid = contains$2(['preservetable', 'resizetable'], value);
  6701. return valid ? { value, valid } : { valid: false, message: 'Must be preservetable, or resizetable.' };
  6702. },
  6703. default: 'preservetable'
  6704. });
  6705. registerOption('table_resize_bars', {
  6706. processor: 'boolean',
  6707. default: true
  6708. });
  6709. registerOption('table_style_by_css', {
  6710. processor: 'boolean',
  6711. default: true
  6712. });
  6713. registerOption('table_merge_content_on_paste', {
  6714. processor: 'boolean',
  6715. default: true
  6716. });
  6717. };
  6718. const getTableCloneElements = (editor) => {
  6719. return Optional.from(editor.options.get('table_clone_elements'));
  6720. };
  6721. const hasTableObjectResizing = (editor) => {
  6722. const objectResizing = editor.options.get('object_resizing');
  6723. return contains$2(objectResizing.split(','), 'table');
  6724. };
  6725. const getTableHeaderType = option('table_header_type');
  6726. const getTableColumnResizingBehaviour = option('table_column_resizing');
  6727. const isPreserveTableColumnResizing = (editor) => getTableColumnResizingBehaviour(editor) === 'preservetable';
  6728. const isResizeTableColumnResizing = (editor) => getTableColumnResizingBehaviour(editor) === 'resizetable';
  6729. const getTableSizingMode = option('table_sizing_mode');
  6730. const isTablePercentagesForced = (editor) => getTableSizingMode(editor) === 'relative';
  6731. const isTablePixelsForced = (editor) => getTableSizingMode(editor) === 'fixed';
  6732. const isTableResponsiveForced = (editor) => getTableSizingMode(editor) === 'responsive';
  6733. const hasTableResizeBars = option('table_resize_bars');
  6734. const shouldStyleWithCss = option('table_style_by_css');
  6735. const shouldMergeContentOnPaste = option('table_merge_content_on_paste');
  6736. const getTableDefaultAttributes = (editor) => {
  6737. // Note: The we don't rely on the default here as we need to dynamically lookup the widths based on the current editor state
  6738. const options = editor.options;
  6739. const defaultAttributes = options.get('table_default_attributes');
  6740. return options.isSet('table_default_attributes') ? defaultAttributes : determineDefaultTableAttributes(editor, defaultAttributes);
  6741. };
  6742. const getTableDefaultStyles = (editor) => {
  6743. // Note: The we don't rely on the default here as we need to dynamically lookup the widths based on the current editor state
  6744. const options = editor.options;
  6745. const defaultStyles = options.get('table_default_styles');
  6746. return options.isSet('table_default_styles') ? defaultStyles : determineDefaultTableStyles(editor, defaultStyles);
  6747. };
  6748. const tableUseColumnGroup = option('table_use_colgroups');
  6749. /*
  6750. NOTE: This file is partially duplicated in the following locations:
  6751. - plugins/table/core/Utils.ts
  6752. - advtable
  6753. Make sure that if making changes to this file, the other files are updated as well
  6754. */
  6755. const getBody = (editor) => SugarElement.fromDom(editor.getBody());
  6756. const getIsRoot = (editor) => (element) => eq$1(element, getBody(editor));
  6757. const removeDataStyle = (table) => {
  6758. remove$6(table, 'data-mce-style');
  6759. const removeStyleAttribute = (element) => remove$6(element, 'data-mce-style');
  6760. each$2(cells$1(table), removeStyleAttribute);
  6761. each$2(columns$1(table), removeStyleAttribute);
  6762. each$2(rows$1(table), removeStyleAttribute);
  6763. };
  6764. const getSelectionStart = (editor) => SugarElement.fromDom(editor.selection.getStart());
  6765. const getPixelWidth = (elm) => elm.getBoundingClientRect().width;
  6766. const getPixelHeight = (elm) => elm.getBoundingClientRect().height;
  6767. const getRawValue = (prop) => (editor, elm) => {
  6768. const raw = editor.dom.getStyle(elm, prop) || editor.dom.getAttrib(elm, prop);
  6769. return Optional.from(raw).filter(isNotEmpty);
  6770. };
  6771. const getRawWidth = getRawValue('width');
  6772. const getRawHeight = getRawValue('height');
  6773. const isPercentage$1 = (value) => /^(\d+(\.\d+)?)%$/.test(value);
  6774. const isPixel = (value) => /^(\d+(\.\d+)?)px$/.test(value);
  6775. const isInEditableContext$1 = (cell) => closest$2(cell, isTag('table')).exists(isEditable$1);
  6776. const lookupTable = (container) => {
  6777. return ancestor$1(container, 'table');
  6778. };
  6779. const identify = (start, finish, isRoot) => {
  6780. const getIsRoot = (rootTable) => {
  6781. return (element) => {
  6782. return (isRoot !== undefined && isRoot(element)) || eq$1(element, rootTable);
  6783. };
  6784. };
  6785. // Optimisation: If the cells are equal, it's a single cell array
  6786. if (eq$1(start, finish)) {
  6787. return Optional.some({
  6788. boxes: Optional.some([start]),
  6789. start,
  6790. finish
  6791. });
  6792. }
  6793. else {
  6794. return lookupTable(start).bind((startTable) => {
  6795. return lookupTable(finish).bind((finishTable) => {
  6796. if (eq$1(startTable, finishTable)) { // Selecting from within the same table.
  6797. return Optional.some({
  6798. boxes: intercepts(startTable, start, finish),
  6799. start,
  6800. finish
  6801. });
  6802. }
  6803. else if (contains(startTable, finishTable)) { // Selecting from the parent table to the nested table.
  6804. const ancestorCells = ancestors$3(finish, 'td,th', getIsRoot(startTable));
  6805. const finishCell = ancestorCells.length > 0 ? ancestorCells[ancestorCells.length - 1] : finish;
  6806. return Optional.some({
  6807. boxes: nestedIntercepts(startTable, start, startTable, finish, finishTable),
  6808. start,
  6809. finish: finishCell
  6810. });
  6811. }
  6812. else if (contains(finishTable, startTable)) { // Selecting from the nested table to the parent table.
  6813. const ancestorCells = ancestors$3(start, 'td,th', getIsRoot(finishTable));
  6814. const startCell = ancestorCells.length > 0 ? ancestorCells[ancestorCells.length - 1] : start;
  6815. return Optional.some({
  6816. boxes: nestedIntercepts(finishTable, start, startTable, finish, finishTable),
  6817. start,
  6818. finish: startCell
  6819. });
  6820. }
  6821. else { // Selecting from a nested table to a different nested table.
  6822. return ancestors(start, finish).shared.bind((lca) => {
  6823. return closest$1(lca, 'table', isRoot).bind((lcaTable) => {
  6824. const finishAncestorCells = ancestors$3(finish, 'td,th', getIsRoot(lcaTable));
  6825. const finishCell = finishAncestorCells.length > 0 ? finishAncestorCells[finishAncestorCells.length - 1] : finish;
  6826. const startAncestorCells = ancestors$3(start, 'td,th', getIsRoot(lcaTable));
  6827. const startCell = startAncestorCells.length > 0 ? startAncestorCells[startAncestorCells.length - 1] : start;
  6828. return Optional.some({
  6829. boxes: nestedIntercepts(lcaTable, start, startTable, finish, finishTable),
  6830. start: startCell,
  6831. finish: finishCell
  6832. });
  6833. });
  6834. });
  6835. }
  6836. });
  6837. });
  6838. }
  6839. };
  6840. const retrieve$1 = (container, selector) => {
  6841. const sels = descendants(container, selector);
  6842. return sels.length > 0 ? Optional.some(sels) : Optional.none();
  6843. };
  6844. const getLast = (boxes, lastSelectedSelector) => {
  6845. return find$1(boxes, (box) => {
  6846. return is$1(box, lastSelectedSelector);
  6847. });
  6848. };
  6849. const getEdges = (container, firstSelectedSelector, lastSelectedSelector) => {
  6850. return descendant(container, firstSelectedSelector).bind((first) => {
  6851. return descendant(container, lastSelectedSelector).bind((last) => {
  6852. return sharedOne(lookupTable, [first, last]).map((table) => {
  6853. return {
  6854. first,
  6855. last,
  6856. table
  6857. };
  6858. });
  6859. });
  6860. });
  6861. };
  6862. const expandTo = (finish, firstSelectedSelector) => {
  6863. return ancestor$1(finish, 'table').bind((table) => {
  6864. return descendant(table, firstSelectedSelector).bind((start) => {
  6865. return identify(start, finish).bind((identified) => {
  6866. return identified.boxes.map((boxes) => {
  6867. return {
  6868. boxes,
  6869. start: identified.start,
  6870. finish: identified.finish
  6871. };
  6872. });
  6873. });
  6874. });
  6875. });
  6876. };
  6877. const shiftSelection = (boxes, deltaRow, deltaColumn, firstSelectedSelector, lastSelectedSelector) => {
  6878. return getLast(boxes, lastSelectedSelector).bind((last) => {
  6879. return moveBy(last, deltaRow, deltaColumn).bind((finish) => {
  6880. return expandTo(finish, firstSelectedSelector);
  6881. });
  6882. });
  6883. };
  6884. // Explicitly calling CellSelection.retrieve so that we can see the API signature.
  6885. const retrieve = (container, selector) => {
  6886. return retrieve$1(container, selector);
  6887. };
  6888. const retrieveBox = (container, firstSelectedSelector, lastSelectedSelector) => {
  6889. return getEdges(container, firstSelectedSelector, lastSelectedSelector).bind((edges) => {
  6890. const isRoot = (ancestor) => {
  6891. return eq$1(container, ancestor);
  6892. };
  6893. const sectionSelector = 'thead,tfoot,tbody,table';
  6894. const firstAncestor = ancestor$1(edges.first, sectionSelector, isRoot);
  6895. const lastAncestor = ancestor$1(edges.last, sectionSelector, isRoot);
  6896. return firstAncestor.bind((fA) => {
  6897. return lastAncestor.bind((lA) => {
  6898. return eq$1(fA, lA) ? getBox(edges.table, edges.first, edges.last) : Optional.none();
  6899. });
  6900. });
  6901. });
  6902. };
  6903. const selection = identity;
  6904. const unmergable = (selectedCells) => {
  6905. const hasSpan = (elem, type) => getOpt(elem, type).exists((span) => parseInt(span, 10) > 1);
  6906. const hasRowOrColSpan = (elem) => hasSpan(elem, 'rowspan') || hasSpan(elem, 'colspan');
  6907. return selectedCells.length > 0 && forall(selectedCells, hasRowOrColSpan) ? Optional.some(selectedCells) : Optional.none();
  6908. };
  6909. const mergable = (table, selectedCells, ephemera) => {
  6910. if (selectedCells.length <= 1) {
  6911. return Optional.none();
  6912. }
  6913. else {
  6914. return retrieveBox(table, ephemera.firstSelectedSelector, ephemera.lastSelectedSelector)
  6915. .map((bounds) => ({ bounds, cells: selectedCells }));
  6916. }
  6917. };
  6918. const create$1 = (selection, kill) => ({
  6919. selection,
  6920. kill
  6921. });
  6922. const Response = {
  6923. create: create$1
  6924. };
  6925. const fold = (subject, onNone, onMultiple, onSingle) => {
  6926. switch (subject.tag) {
  6927. case "none" /* SelectionTypeTag.None */:
  6928. return onNone();
  6929. case "single" /* SelectionTypeTag.Single */:
  6930. return onSingle(subject.element);
  6931. case "multiple" /* SelectionTypeTag.Multiple */:
  6932. return onMultiple(subject.elements);
  6933. }
  6934. };
  6935. const none = () => ({ tag: "none" /* SelectionTypeTag.None */ });
  6936. const multiple = (elements) => ({ tag: "multiple" /* SelectionTypeTag.Multiple */, elements });
  6937. const single = (element) => ({ tag: "single" /* SelectionTypeTag.Single */, element });
  6938. const Selections = (lazyRoot, getStart, selectedSelector) => {
  6939. const get = () => retrieve(lazyRoot(), selectedSelector).fold(() => getStart().fold(none, single), multiple);
  6940. return {
  6941. get
  6942. };
  6943. };
  6944. const create = (start, soffset, finish, foffset) => {
  6945. return {
  6946. start: Situ.on(start, soffset),
  6947. finish: Situ.on(finish, foffset)
  6948. };
  6949. };
  6950. const Situs = {
  6951. create
  6952. };
  6953. const convertToRange = (win, selection) => {
  6954. // TODO: Use API packages of sugar
  6955. const rng = asLtrRange(win, selection);
  6956. return SimRange.create(SugarElement.fromDom(rng.startContainer), rng.startOffset, SugarElement.fromDom(rng.endContainer), rng.endOffset);
  6957. };
  6958. const makeSitus = Situs.create;
  6959. // Based on a start and finish, select the appropriate box of cells
  6960. const sync = (container, isRoot, start, soffset, finish, foffset, selectRange) => {
  6961. if (!(eq$1(start, finish) && soffset === foffset)) {
  6962. return closest$1(start, 'td,th', isRoot).bind((s) => {
  6963. return closest$1(finish, 'td,th', isRoot).bind((f) => {
  6964. return detect(container, isRoot, s, f, selectRange);
  6965. });
  6966. });
  6967. }
  6968. else {
  6969. return Optional.none();
  6970. }
  6971. };
  6972. // If the cells are different, and there is a rectangle to connect them, select the cells.
  6973. const detect = (container, isRoot, start, finish, selectRange) => {
  6974. if (!eq$1(start, finish)) {
  6975. return identify(start, finish, isRoot).bind((cellSel) => {
  6976. const boxes = cellSel.boxes.getOr([]);
  6977. if (boxes.length > 1) {
  6978. selectRange(container, boxes, cellSel.start, cellSel.finish);
  6979. return Optional.some(Response.create(Optional.some(makeSitus(start, 0, start, getEnd(start))), true));
  6980. }
  6981. else {
  6982. return Optional.none();
  6983. }
  6984. });
  6985. }
  6986. else {
  6987. return Optional.none();
  6988. }
  6989. };
  6990. const update = (rows, columns, container, selected, annotations) => {
  6991. const updateSelection = (newSels) => {
  6992. annotations.clearBeforeUpdate(container);
  6993. annotations.selectRange(container, newSels.boxes, newSels.start, newSels.finish);
  6994. return newSels.boxes;
  6995. };
  6996. return shiftSelection(selected, rows, columns, annotations.firstSelectedSelector, annotations.lastSelectedSelector).map(updateSelection);
  6997. };
  6998. const adt$1 = Adt.generate([
  6999. { none: ['message'] },
  7000. { success: [] },
  7001. { failedUp: ['cell'] },
  7002. { failedDown: ['cell'] }
  7003. ]);
  7004. // Let's get some bounding rects, and see if they overlap (x-wise)
  7005. const isOverlapping = (bridge, before, after) => {
  7006. const beforeBounds = bridge.getRect(before);
  7007. const afterBounds = bridge.getRect(after);
  7008. return afterBounds.right > beforeBounds.left && afterBounds.left < beforeBounds.right;
  7009. };
  7010. const isRow = (elem) => {
  7011. return closest$1(elem, 'tr');
  7012. };
  7013. const verify = (bridge, before, beforeOffset, after, afterOffset, failure, isRoot) => {
  7014. // Identify the cells that the before and after are in.
  7015. return closest$1(after, 'td,th', isRoot).bind((afterCell) => {
  7016. return closest$1(before, 'td,th', isRoot).map((beforeCell) => {
  7017. // If they are not in the same cell
  7018. if (!eq$1(afterCell, beforeCell)) {
  7019. return sharedOne(isRow, [afterCell, beforeCell]).fold(() => {
  7020. // No shared row, and they overlap x-wise -> success, otherwise: failed
  7021. return isOverlapping(bridge, beforeCell, afterCell) ? adt$1.success() : failure(beforeCell);
  7022. }, (_sharedRow) => {
  7023. // In the same row, so it failed.
  7024. return failure(beforeCell);
  7025. });
  7026. }
  7027. else {
  7028. return eq$1(after, afterCell) && getEnd(afterCell) === afterOffset ? failure(beforeCell) : adt$1.none('in same cell');
  7029. }
  7030. });
  7031. }).getOr(adt$1.none('default'));
  7032. };
  7033. const cata = (subject, onNone, onSuccess, onFailedUp, onFailedDown) => {
  7034. return subject.fold(onNone, onSuccess, onFailedUp, onFailedDown);
  7035. };
  7036. const BeforeAfter = {
  7037. ...adt$1,
  7038. verify,
  7039. cata
  7040. };
  7041. const isBr = isTag('br');
  7042. const gatherer = (cand, gather, isRoot) => {
  7043. return gather(cand, isRoot).bind((target) => {
  7044. return isText(target) && get$5(target).trim().length === 0 ? gatherer(target, gather, isRoot) : Optional.some(target);
  7045. });
  7046. };
  7047. const handleBr = (isRoot, element, direction) => {
  7048. // 1. Has a neighbouring sibling ... position relative to neighbouring element
  7049. // 2. Has no neighbouring sibling ... position relative to gathered element
  7050. return direction.traverse(element).orThunk(() => {
  7051. return gatherer(element, direction.gather, isRoot);
  7052. }).map(direction.relative);
  7053. };
  7054. const findBr = (element, offset) => {
  7055. return child$2(element, offset).filter(isBr).orThunk(() => {
  7056. // Can be either side of the br, and still be a br.
  7057. return child$2(element, offset - 1).filter(isBr);
  7058. });
  7059. };
  7060. const handleParent = (isRoot, element, offset, direction) => {
  7061. // 1. Has no neighbouring sibling, position relative to gathered element
  7062. // 2. Has a neighbouring sibling, position at the neighbouring sibling with respect to parent
  7063. return findBr(element, offset).bind((br) => {
  7064. return direction.traverse(br).fold(() => {
  7065. return gatherer(br, direction.gather, isRoot).map(direction.relative);
  7066. }, (adjacent) => {
  7067. return indexInParent(adjacent).map((info) => {
  7068. return Situ.on(info.parent, info.index);
  7069. });
  7070. });
  7071. });
  7072. };
  7073. const tryBr = (isRoot, element, offset, direction) => {
  7074. // Three different situations
  7075. // 1. the br is the child, and it has a previous sibling. Use parent, index-1)
  7076. // 2. the br is the child and it has no previous sibling, set to before the previous gather result
  7077. // 3. the br is the element and it has a previous sibling, use parent index-1)
  7078. // 4. the br is the element and it has no previous sibling, set to before the previous gather result.
  7079. // 2. the element is the br itself,
  7080. const target = isBr(element) ? handleBr(isRoot, element, direction) : handleParent(isRoot, element, offset, direction);
  7081. return target.map((tgt) => {
  7082. return {
  7083. start: tgt,
  7084. finish: tgt
  7085. };
  7086. });
  7087. };
  7088. const process = (analysis) => {
  7089. return BeforeAfter.cata(analysis, (_message) => {
  7090. return Optional.none();
  7091. }, () => {
  7092. return Optional.none();
  7093. }, (cell) => {
  7094. return Optional.some(point(cell, 0));
  7095. }, (cell) => {
  7096. return Optional.some(point(cell, getEnd(cell)));
  7097. });
  7098. };
  7099. const moveDown = (caret, amount) => {
  7100. return {
  7101. left: caret.left,
  7102. top: caret.top + amount,
  7103. right: caret.right,
  7104. bottom: caret.bottom + amount
  7105. };
  7106. };
  7107. const moveUp = (caret, amount) => {
  7108. return {
  7109. left: caret.left,
  7110. top: caret.top - amount,
  7111. right: caret.right,
  7112. bottom: caret.bottom - amount
  7113. };
  7114. };
  7115. const translate = (caret, xDelta, yDelta) => {
  7116. return {
  7117. left: caret.left + xDelta,
  7118. top: caret.top + yDelta,
  7119. right: caret.right + xDelta,
  7120. bottom: caret.bottom + yDelta
  7121. };
  7122. };
  7123. const getTop = (caret) => {
  7124. return caret.top;
  7125. };
  7126. const getBottom = (caret) => {
  7127. return caret.bottom;
  7128. };
  7129. const getPartialBox = (bridge, element, offset) => {
  7130. if (offset >= 0 && offset < getEnd(element)) {
  7131. return bridge.getRangedRect(element, offset, element, offset + 1);
  7132. }
  7133. else if (offset > 0) {
  7134. return bridge.getRangedRect(element, offset - 1, element, offset);
  7135. }
  7136. return Optional.none();
  7137. };
  7138. const toCaret = (rect) => ({
  7139. left: rect.left,
  7140. top: rect.top,
  7141. right: rect.right,
  7142. bottom: rect.bottom
  7143. });
  7144. const getElemBox = (bridge, element) => {
  7145. return Optional.some(bridge.getRect(element));
  7146. };
  7147. const getBoxAt = (bridge, element, offset) => {
  7148. // Note, we might need to consider this offset and descend.
  7149. if (isElement(element)) {
  7150. return getElemBox(bridge, element).map(toCaret);
  7151. }
  7152. else if (isText(element)) {
  7153. return getPartialBox(bridge, element, offset).map(toCaret);
  7154. }
  7155. else {
  7156. return Optional.none();
  7157. }
  7158. };
  7159. const getEntireBox = (bridge, element) => {
  7160. if (isElement(element)) {
  7161. return getElemBox(bridge, element).map(toCaret);
  7162. }
  7163. else if (isText(element)) {
  7164. return bridge.getRangedRect(element, 0, element, getEnd(element)).map(toCaret);
  7165. }
  7166. else {
  7167. return Optional.none();
  7168. }
  7169. };
  7170. const JUMP_SIZE = 5;
  7171. const NUM_RETRIES = 100;
  7172. const adt = Adt.generate([
  7173. { none: [] },
  7174. { retry: ['caret'] }
  7175. ]);
  7176. const isOutside = (caret, box) => {
  7177. return caret.left < box.left || Math.abs(box.right - caret.left) < 1 || caret.left > box.right;
  7178. };
  7179. // Find the block and determine whether or not that block is outside. If it is outside, move up/down and right.
  7180. const inOutsideBlock = (bridge, element, caret) => {
  7181. return closest$2(element, isBlock).fold(never, (cell) => {
  7182. return getEntireBox(bridge, cell).exists((box) => {
  7183. return isOutside(caret, box);
  7184. });
  7185. });
  7186. };
  7187. /*
  7188. * The approach is as follows.
  7189. *
  7190. * The browser APIs for caret ranges return elements that are the closest text elements to your (x, y) position, even if those
  7191. * closest elements are miles away. This causes problems when you are trying to identify what is immediately above or below
  7192. * a cell, because often the closest text is in a cell that is in a completely different column. Therefore, the approach needs
  7193. * to keep moving down until the thing that we are hitting is likely to be a true positive.
  7194. *
  7195. * Steps:
  7196. *
  7197. * 1. If the y position of the next guess is not different from the original, keep going.
  7198. * 2a. If the guess box doesn't actually include the position looked for, then the browser has returned a node that does not have
  7199. * a rectangle which truly intercepts the point. So, keep going. Note, we used to jump straight away here, but that means that
  7200. * we might skip over something that wasn't considered close enough but was a better guess than just making the y value skip.
  7201. * 2b. If the guess box exactly aligns with the caret, then adjust by 1 and go again. This is to get a more accurate offset.
  7202. * 3. if the guess box does include the caret, but the guess box's parent cell does not *really* contain the caret, try again shifting
  7203. * only the x value. If the guess box's parent cell does *really* contain the caret (i.e. it is horizontally-aligned), then stop
  7204. * because the guess is GOOD.
  7205. */
  7206. const adjustDown = (bridge, element, guessBox, original, caret) => {
  7207. const lowerCaret = moveDown(caret, JUMP_SIZE);
  7208. if (Math.abs(guessBox.bottom - original.bottom) < 1) {
  7209. return adt.retry(lowerCaret);
  7210. }
  7211. else if (guessBox.top > caret.bottom) {
  7212. return adt.retry(lowerCaret);
  7213. }
  7214. else if (guessBox.top === caret.bottom) {
  7215. return adt.retry(moveDown(caret, 1));
  7216. }
  7217. else {
  7218. return inOutsideBlock(bridge, element, caret) ? adt.retry(translate(lowerCaret, JUMP_SIZE, 0)) : adt.none();
  7219. }
  7220. };
  7221. const adjustUp = (bridge, element, guessBox, original, caret) => {
  7222. const higherCaret = moveUp(caret, JUMP_SIZE);
  7223. if (Math.abs(guessBox.top - original.top) < 1) {
  7224. return adt.retry(higherCaret);
  7225. }
  7226. else if (guessBox.bottom < caret.top) {
  7227. return adt.retry(higherCaret);
  7228. }
  7229. else if (guessBox.bottom === caret.top) {
  7230. return adt.retry(moveUp(caret, 1));
  7231. }
  7232. else {
  7233. return inOutsideBlock(bridge, element, caret) ? adt.retry(translate(higherCaret, JUMP_SIZE, 0)) : adt.none();
  7234. }
  7235. };
  7236. const upMovement = {
  7237. point: getTop,
  7238. adjuster: adjustUp,
  7239. move: moveUp,
  7240. gather: before
  7241. };
  7242. const downMovement = {
  7243. point: getBottom,
  7244. adjuster: adjustDown,
  7245. move: moveDown,
  7246. gather: after
  7247. };
  7248. const isAtTable = (bridge, x, y) => {
  7249. return bridge.elementFromPoint(x, y).filter((elm) => {
  7250. return name(elm) === 'table';
  7251. }).isSome();
  7252. };
  7253. const adjustForTable = (bridge, movement, original, caret, numRetries) => {
  7254. return adjustTil(bridge, movement, original, movement.move(caret, JUMP_SIZE), numRetries);
  7255. };
  7256. const adjustTil = (bridge, movement, original, caret, numRetries) => {
  7257. if (numRetries === 0) {
  7258. return Optional.some(caret);
  7259. }
  7260. if (isAtTable(bridge, caret.left, movement.point(caret))) {
  7261. return adjustForTable(bridge, movement, original, caret, numRetries - 1);
  7262. }
  7263. return bridge.situsFromPoint(caret.left, movement.point(caret)).bind((guess) => {
  7264. return guess.start.fold(Optional.none, (element) => {
  7265. return getEntireBox(bridge, element).bind((guessBox) => {
  7266. return movement.adjuster(bridge, element, guessBox, original, caret).fold(Optional.none, (newCaret) => {
  7267. return adjustTil(bridge, movement, original, newCaret, numRetries - 1);
  7268. });
  7269. }).orThunk(() => {
  7270. return Optional.some(caret);
  7271. });
  7272. }, Optional.none);
  7273. });
  7274. };
  7275. const checkScroll = (movement, adjusted, bridge) => {
  7276. // I'm not convinced that this is right. Let's re-examine it later.
  7277. if (movement.point(adjusted) > bridge.getInnerHeight()) {
  7278. return Optional.some(movement.point(adjusted) - bridge.getInnerHeight());
  7279. }
  7280. else if (movement.point(adjusted) < 0) {
  7281. return Optional.some(-movement.point(adjusted));
  7282. }
  7283. else {
  7284. return Optional.none();
  7285. }
  7286. };
  7287. const retry = (movement, bridge, caret) => {
  7288. const moved = movement.move(caret, JUMP_SIZE);
  7289. const adjusted = adjustTil(bridge, movement, caret, moved, NUM_RETRIES).getOr(moved);
  7290. return checkScroll(movement, adjusted, bridge).fold(() => {
  7291. return bridge.situsFromPoint(adjusted.left, movement.point(adjusted));
  7292. }, (delta) => {
  7293. bridge.scrollBy(0, delta);
  7294. return bridge.situsFromPoint(adjusted.left, movement.point(adjusted) - delta);
  7295. });
  7296. };
  7297. const Retries = {
  7298. tryUp: curry(retry, upMovement),
  7299. tryDown: curry(retry, downMovement),
  7300. getJumpSize: constant(JUMP_SIZE)
  7301. };
  7302. const MAX_RETRIES = 20;
  7303. const findSpot = (bridge, isRoot, direction) => {
  7304. return bridge.getSelection().bind((sel) => {
  7305. return tryBr(isRoot, sel.finish, sel.foffset, direction).fold(() => {
  7306. return Optional.some(point(sel.finish, sel.foffset));
  7307. }, (brNeighbour) => {
  7308. const range = bridge.fromSitus(brNeighbour);
  7309. const analysis = BeforeAfter.verify(bridge, sel.finish, sel.foffset, range.finish, range.foffset, direction.failure, isRoot);
  7310. return process(analysis);
  7311. });
  7312. });
  7313. };
  7314. const scan = (bridge, isRoot, element, offset, direction, numRetries) => {
  7315. if (numRetries === 0) {
  7316. return Optional.none();
  7317. }
  7318. // Firstly, move the (x, y) and see what element we end up on.
  7319. return tryCursor(bridge, isRoot, element, offset, direction).bind((situs) => {
  7320. const range = bridge.fromSitus(situs);
  7321. // Now, check to see if the element is a new cell.
  7322. const analysis = BeforeAfter.verify(bridge, element, offset, range.finish, range.foffset, direction.failure, isRoot);
  7323. return BeforeAfter.cata(analysis, () => {
  7324. return Optional.none();
  7325. }, () => {
  7326. // We have a new cell, so we stop looking.
  7327. return Optional.some(situs);
  7328. }, (cell) => {
  7329. if (eq$1(element, cell) && offset === 0) {
  7330. return tryAgain(bridge, element, offset, moveUp, direction);
  7331. }
  7332. else { // We need to look again from the start of our current cell
  7333. return scan(bridge, isRoot, cell, 0, direction, numRetries - 1);
  7334. }
  7335. }, (cell) => {
  7336. // If we were here last time, move and try again.
  7337. if (eq$1(element, cell) && offset === getEnd(cell)) {
  7338. return tryAgain(bridge, element, offset, moveDown, direction);
  7339. }
  7340. else { // We need to look again from the end of our current cell
  7341. return scan(bridge, isRoot, cell, getEnd(cell), direction, numRetries - 1);
  7342. }
  7343. });
  7344. });
  7345. };
  7346. const tryAgain = (bridge, element, offset, move, direction) => {
  7347. return getBoxAt(bridge, element, offset).bind((box) => {
  7348. return tryAt(bridge, direction, move(box, Retries.getJumpSize()));
  7349. });
  7350. };
  7351. const tryAt = (bridge, direction, box) => {
  7352. const browser = detect$2().browser;
  7353. // NOTE: As we attempt to take over selection everywhere, we'll probably need to separate these again.
  7354. if (browser.isChromium() || browser.isSafari() || browser.isFirefox()) {
  7355. return direction.retry(bridge, box);
  7356. }
  7357. else {
  7358. return Optional.none();
  7359. }
  7360. };
  7361. const tryCursor = (bridge, isRoot, element, offset, direction) => {
  7362. return getBoxAt(bridge, element, offset).bind((box) => {
  7363. return tryAt(bridge, direction, box);
  7364. });
  7365. };
  7366. const handle = (bridge, isRoot, direction) => {
  7367. return findSpot(bridge, isRoot, direction).bind((spot) => {
  7368. // There is a point to start doing box-hitting from
  7369. return scan(bridge, isRoot, spot.element, spot.offset, direction, MAX_RETRIES).map(bridge.fromSitus);
  7370. });
  7371. };
  7372. const inSameTable = (elem, table) => {
  7373. return ancestor(elem, (e) => {
  7374. return parent(e).exists((p) => {
  7375. return eq$1(p, table);
  7376. });
  7377. });
  7378. };
  7379. // Note: initial is the finishing element, because that's where the cursor starts from
  7380. // Anchor is the starting element, and is only used to work out if we are in the same table
  7381. const simulate = (bridge, isRoot, direction, initial, anchor) => {
  7382. return closest$1(initial, 'td,th', isRoot).bind((start) => {
  7383. return closest$1(start, 'table', isRoot).bind((table) => {
  7384. if (!inSameTable(anchor, table)) {
  7385. return Optional.none();
  7386. }
  7387. return handle(bridge, isRoot, direction).bind((range) => {
  7388. return closest$1(range.finish, 'td,th', isRoot).map((finish) => {
  7389. return {
  7390. start,
  7391. finish,
  7392. range
  7393. };
  7394. });
  7395. });
  7396. });
  7397. });
  7398. };
  7399. const navigate = (bridge, isRoot, direction, initial, anchor, precheck) => {
  7400. return precheck(initial, isRoot).orThunk(() => {
  7401. return simulate(bridge, isRoot, direction, initial, anchor).map((info) => {
  7402. const range = info.range;
  7403. return Response.create(Optional.some(makeSitus(range.start, range.soffset, range.finish, range.foffset)), true);
  7404. });
  7405. });
  7406. };
  7407. const firstUpCheck = (initial, isRoot) => {
  7408. return closest$1(initial, 'tr', isRoot).bind((startRow) => {
  7409. return closest$1(startRow, 'table', isRoot).bind((table) => {
  7410. const rows = descendants(table, 'tr');
  7411. if (eq$1(startRow, rows[0])) {
  7412. return seekLeft(table, (element) => {
  7413. return last(element).isSome();
  7414. }, isRoot).map((last) => {
  7415. const lastOffset = getEnd(last);
  7416. return Response.create(Optional.some(makeSitus(last, lastOffset, last, lastOffset)), true);
  7417. });
  7418. }
  7419. else {
  7420. return Optional.none();
  7421. }
  7422. });
  7423. });
  7424. };
  7425. const lastDownCheck = (initial, isRoot) => {
  7426. return closest$1(initial, 'tr', isRoot).bind((startRow) => {
  7427. return closest$1(startRow, 'table', isRoot).bind((table) => {
  7428. const rows = descendants(table, 'tr');
  7429. if (eq$1(startRow, rows[rows.length - 1])) {
  7430. return seekRight(table, (element) => {
  7431. return first(element).isSome();
  7432. }, isRoot).map((first) => {
  7433. return Response.create(Optional.some(makeSitus(first, 0, first, 0)), true);
  7434. });
  7435. }
  7436. else {
  7437. return Optional.none();
  7438. }
  7439. });
  7440. });
  7441. };
  7442. const select = (bridge, container, isRoot, direction, initial, anchor, selectRange) => {
  7443. return simulate(bridge, isRoot, direction, initial, anchor).bind((info) => {
  7444. return detect(container, isRoot, info.start, info.finish, selectRange);
  7445. });
  7446. };
  7447. const findCell = (target, isRoot) => closest$1(target, 'td,th', isRoot);
  7448. const isInEditableContext = (cell) => parentElement(cell).exists(isEditable$1);
  7449. const MouseSelection = (bridge, container, isRoot, annotations) => {
  7450. const cursor = value();
  7451. const clearstate = cursor.clear;
  7452. const applySelection = (event) => {
  7453. cursor.on((start) => {
  7454. annotations.clearBeforeUpdate(container);
  7455. findCell(event.target, isRoot).each((finish) => {
  7456. identify(start, finish, isRoot).each((cellSel) => {
  7457. const boxes = cellSel.boxes.getOr([]);
  7458. if (boxes.length === 1) {
  7459. // If a single noneditable cell is selected and the actual selection target within the cell
  7460. // is also noneditable, make sure it is annotated
  7461. const singleCell = boxes[0];
  7462. const isNonEditableCell = getRaw$1(singleCell) === 'false';
  7463. const isCellClosestContentEditable = is$2(closest(event.target), singleCell, eq$1);
  7464. if (isNonEditableCell && isCellClosestContentEditable) {
  7465. // Not selecting the contents or the node of the actual cell as shown below, keeping the selection on the offscreen element.
  7466. annotations.selectRange(container, boxes, singleCell, singleCell);
  7467. }
  7468. }
  7469. else if (boxes.length > 1) {
  7470. // Wait until we have more than one, otherwise you can't do text selection inside a cell.
  7471. annotations.selectRange(container, boxes, cellSel.start, cellSel.finish);
  7472. // stop the browser from creating a big text selection, select the cell where the cursor is
  7473. bridge.selectContents(finish);
  7474. }
  7475. });
  7476. });
  7477. });
  7478. };
  7479. /* Keep this as lightweight as possible when we're not in a table selection, it runs constantly */
  7480. const mousedown = (event) => {
  7481. annotations.clear(container);
  7482. findCell(event.target, isRoot).filter(isInEditableContext).each(cursor.set);
  7483. };
  7484. /* Keep this as lightweight as possible when we're not in a table selection, it runs constantly */
  7485. const mouseover = (event) => {
  7486. applySelection(event);
  7487. };
  7488. /* Keep this as lightweight as possible when we're not in a table selection, it runs constantly */
  7489. const mouseup = (event) => {
  7490. // Needed as Firefox will change the selection between the mouseover and mouseup when selecting
  7491. // just 2 cells as Firefox supports multiple selection ranges
  7492. applySelection(event);
  7493. clearstate();
  7494. };
  7495. return {
  7496. clearstate,
  7497. mousedown,
  7498. mouseover,
  7499. mouseup
  7500. };
  7501. };
  7502. const down = {
  7503. traverse: nextSibling,
  7504. gather: after,
  7505. relative: Situ.before,
  7506. retry: Retries.tryDown,
  7507. failure: BeforeAfter.failedDown
  7508. };
  7509. const up = {
  7510. traverse: prevSibling,
  7511. gather: before,
  7512. relative: Situ.before,
  7513. retry: Retries.tryUp,
  7514. failure: BeforeAfter.failedUp
  7515. };
  7516. const isKey = (key) => {
  7517. return (keycode) => {
  7518. return keycode === key;
  7519. };
  7520. };
  7521. const isUp = isKey(38);
  7522. const isDown = isKey(40);
  7523. const isNavigation = (keycode) => {
  7524. return keycode >= 37 && keycode <= 40;
  7525. };
  7526. const ltr = {
  7527. // We need to move KEYS out of keytar and into something much more low-level.
  7528. isBackward: isKey(37),
  7529. isForward: isKey(39)
  7530. };
  7531. const rtl = {
  7532. isBackward: isKey(39),
  7533. isForward: isKey(37)
  7534. };
  7535. const WindowBridge = (win) => {
  7536. const elementFromPoint = (x, y) => {
  7537. return SugarElement.fromPoint(SugarElement.fromDom(win.document), x, y);
  7538. };
  7539. const getRect = (element) => {
  7540. return element.dom.getBoundingClientRect();
  7541. };
  7542. const getRangedRect = (start, soffset, finish, foffset) => {
  7543. const sel = SimSelection.exact(start, soffset, finish, foffset);
  7544. return getFirstRect(win, sel);
  7545. };
  7546. const getSelection = () => {
  7547. return get$3(win).map((exactAdt) => {
  7548. return convertToRange(win, exactAdt);
  7549. });
  7550. };
  7551. const fromSitus = (situs) => {
  7552. const relative = SimSelection.relative(situs.start, situs.finish);
  7553. return convertToRange(win, relative);
  7554. };
  7555. const situsFromPoint = (x, y) => {
  7556. return getAtPoint(win, x, y).map((exact) => {
  7557. return Situs.create(exact.start, exact.soffset, exact.finish, exact.foffset);
  7558. });
  7559. };
  7560. const clearSelection = () => {
  7561. clear(win);
  7562. };
  7563. const collapseSelection = (toStart = false) => {
  7564. get$3(win).each((sel) => sel.fold((rng) => rng.collapse(toStart), (startSitu, finishSitu) => {
  7565. const situ = toStart ? startSitu : finishSitu;
  7566. setRelative(win, situ, situ);
  7567. }, (start, soffset, finish, foffset) => {
  7568. const node = toStart ? start : finish;
  7569. const offset = toStart ? soffset : foffset;
  7570. setExact(win, node, offset, node, offset);
  7571. }));
  7572. };
  7573. const selectNode = (element) => {
  7574. setToElement(win, element, false);
  7575. };
  7576. const selectContents = (element) => {
  7577. setToElement(win, element);
  7578. };
  7579. const setSelection = (sel) => {
  7580. setExact(win, sel.start, sel.soffset, sel.finish, sel.foffset);
  7581. };
  7582. const setRelativeSelection = (start, finish) => {
  7583. setRelative(win, start, finish);
  7584. };
  7585. const getInnerHeight = () => {
  7586. return win.innerHeight;
  7587. };
  7588. const getScrollY = () => {
  7589. const pos = get$6(SugarElement.fromDom(win.document));
  7590. return pos.top;
  7591. };
  7592. const scrollBy = (x, y) => {
  7593. by(x, y, SugarElement.fromDom(win.document));
  7594. };
  7595. return {
  7596. elementFromPoint,
  7597. getRect,
  7598. getRangedRect,
  7599. getSelection,
  7600. fromSitus,
  7601. situsFromPoint,
  7602. clearSelection,
  7603. collapseSelection,
  7604. setSelection,
  7605. setRelativeSelection,
  7606. selectNode,
  7607. selectContents,
  7608. getInnerHeight,
  7609. getScrollY,
  7610. scrollBy
  7611. };
  7612. };
  7613. const rc = (rows, cols) => ({ rows, cols });
  7614. const mouse = (win, container, isRoot, annotations) => {
  7615. const bridge = WindowBridge(win);
  7616. const handlers = MouseSelection(bridge, container, isRoot, annotations);
  7617. return {
  7618. clearstate: handlers.clearstate,
  7619. mousedown: handlers.mousedown,
  7620. mouseover: handlers.mouseover,
  7621. mouseup: handlers.mouseup
  7622. };
  7623. };
  7624. const isEditableNode = (node) => closest$2(node, isHTMLElement).exists(isEditable$1);
  7625. const isEditableSelection = (start, finish) => isEditableNode(start) || isEditableNode(finish);
  7626. const keyboard = (win, container, isRoot, annotations) => {
  7627. const bridge = WindowBridge(win);
  7628. const clearToNavigate = () => {
  7629. annotations.clear(container);
  7630. return Optional.none();
  7631. };
  7632. const keydown = (event, start, soffset, finish, foffset, direction) => {
  7633. const realEvent = event.raw;
  7634. const keycode = realEvent.which;
  7635. const shiftKey = realEvent.shiftKey === true;
  7636. const handler = retrieve$1(container, annotations.selectedSelector).fold(() => {
  7637. // Make sure any possible lingering annotations are cleared
  7638. if (isNavigation(keycode) && !shiftKey) {
  7639. annotations.clearBeforeUpdate(container);
  7640. }
  7641. // Shift down should predict the movement and set the selection.
  7642. if (isNavigation(keycode) && shiftKey && !isEditableSelection(start, finish)) {
  7643. return Optional.none;
  7644. }
  7645. else if (isDown(keycode) && shiftKey) {
  7646. return curry(select, bridge, container, isRoot, down, finish, start, annotations.selectRange);
  7647. }
  7648. else if (isUp(keycode) && shiftKey) { // Shift up should predict the movement and set the selection.
  7649. return curry(select, bridge, container, isRoot, up, finish, start, annotations.selectRange);
  7650. }
  7651. else if (isDown(keycode)) { // Down should predict the movement and set the cursor
  7652. return curry(navigate, bridge, isRoot, down, finish, start, lastDownCheck);
  7653. }
  7654. else if (isUp(keycode)) { // Up should predict the movement and set the cursor
  7655. return curry(navigate, bridge, isRoot, up, finish, start, firstUpCheck);
  7656. }
  7657. else {
  7658. return Optional.none;
  7659. }
  7660. }, (selected) => {
  7661. const update$1 = (attempts) => {
  7662. return () => {
  7663. const navigation = findMap(attempts, (delta) => {
  7664. return update(delta.rows, delta.cols, container, selected, annotations);
  7665. });
  7666. // Shift the selected rows and update the selection.
  7667. return navigation.fold(() => {
  7668. // The cell selection went outside the table, so clear it and bridge from the first box to before/after
  7669. // the table
  7670. return getEdges(container, annotations.firstSelectedSelector, annotations.lastSelectedSelector).map((edges) => {
  7671. const relative = isDown(keycode) || direction.isForward(keycode) ? Situ.after : Situ.before;
  7672. bridge.setRelativeSelection(Situ.on(edges.first, 0), relative(edges.table));
  7673. annotations.clear(container);
  7674. return Response.create(Optional.none(), true);
  7675. });
  7676. }, (_) => {
  7677. return Optional.some(Response.create(Optional.none(), true));
  7678. });
  7679. };
  7680. };
  7681. if (isNavigation(keycode) && shiftKey && !isEditableSelection(start, finish)) {
  7682. return Optional.none;
  7683. }
  7684. else if (isDown(keycode) && shiftKey) {
  7685. return update$1([rc(+1, 0)]);
  7686. }
  7687. else if (isUp(keycode) && shiftKey) {
  7688. return update$1([rc(-1, 0)]);
  7689. }
  7690. else if (direction.isBackward(keycode) && shiftKey) { // Left and right should try up/down respectively if they fail.
  7691. return update$1([rc(0, -1), rc(-1, 0)]);
  7692. }
  7693. else if (direction.isForward(keycode) && shiftKey) {
  7694. return update$1([rc(0, +1), rc(+1, 0)]);
  7695. }
  7696. else if (isNavigation(keycode) && !shiftKey) { // Clear the selection on normal arrow keys.
  7697. return clearToNavigate;
  7698. }
  7699. else {
  7700. return Optional.none;
  7701. }
  7702. });
  7703. return handler();
  7704. };
  7705. const keyup = (event, start, soffset, finish, foffset) => {
  7706. return retrieve$1(container, annotations.selectedSelector).fold(() => {
  7707. const realEvent = event.raw;
  7708. const keycode = realEvent.which;
  7709. const shiftKey = realEvent.shiftKey === true;
  7710. if (!shiftKey) {
  7711. return Optional.none();
  7712. }
  7713. if (isNavigation(keycode) && isEditableSelection(start, finish)) {
  7714. return sync(container, isRoot, start, soffset, finish, foffset, annotations.selectRange);
  7715. }
  7716. else {
  7717. return Optional.none();
  7718. }
  7719. }, Optional.none);
  7720. };
  7721. return {
  7722. keydown,
  7723. keyup
  7724. };
  7725. };
  7726. const external = (win, container, isRoot, annotations) => {
  7727. const bridge = WindowBridge(win);
  7728. return (start, finish) => {
  7729. annotations.clearBeforeUpdate(container);
  7730. identify(start, finish, isRoot).each((cellSel) => {
  7731. const boxes = cellSel.boxes.getOr([]);
  7732. annotations.selectRange(container, boxes, cellSel.start, cellSel.finish);
  7733. // stop the browser from creating a big text selection, place the selection at the end of the cell where the cursor is
  7734. bridge.selectContents(finish);
  7735. bridge.collapseSelection();
  7736. });
  7737. };
  7738. };
  7739. const byClass = (ephemera) => {
  7740. const addSelectionClass = addClass(ephemera.selected);
  7741. const removeSelectionClasses = removeClasses([ephemera.selected, ephemera.lastSelected, ephemera.firstSelected]);
  7742. const clear = (container) => {
  7743. const sels = descendants(container, ephemera.selectedSelector);
  7744. each$2(sels, removeSelectionClasses);
  7745. };
  7746. const selectRange = (container, cells, start, finish) => {
  7747. clear(container);
  7748. each$2(cells, addSelectionClass);
  7749. add$1(start, ephemera.firstSelected);
  7750. add$1(finish, ephemera.lastSelected);
  7751. };
  7752. return {
  7753. clearBeforeUpdate: clear,
  7754. clear,
  7755. selectRange,
  7756. selectedSelector: ephemera.selectedSelector,
  7757. firstSelectedSelector: ephemera.firstSelectedSelector,
  7758. lastSelectedSelector: ephemera.lastSelectedSelector
  7759. };
  7760. };
  7761. const byAttr = (ephemera, onSelection, onClear) => {
  7762. const removeSelectionAttributes = (element) => {
  7763. remove$6(element, ephemera.selected);
  7764. remove$6(element, ephemera.firstSelected);
  7765. remove$6(element, ephemera.lastSelected);
  7766. };
  7767. const addSelectionAttribute = (element) => {
  7768. set$2(element, ephemera.selected, '1');
  7769. };
  7770. const clear = (container) => {
  7771. clearBeforeUpdate(container);
  7772. onClear();
  7773. };
  7774. const clearBeforeUpdate = (container) => {
  7775. const sels = descendants(container, `${ephemera.selectedSelector},${ephemera.firstSelectedSelector},${ephemera.lastSelectedSelector}`);
  7776. each$2(sels, removeSelectionAttributes);
  7777. };
  7778. const selectRange = (container, cells, start, finish) => {
  7779. clear(container);
  7780. each$2(cells, addSelectionAttribute);
  7781. set$2(start, ephemera.firstSelected, '1');
  7782. set$2(finish, ephemera.lastSelected, '1');
  7783. onSelection(cells, start, finish);
  7784. };
  7785. return {
  7786. clearBeforeUpdate,
  7787. clear,
  7788. selectRange,
  7789. selectedSelector: ephemera.selectedSelector,
  7790. firstSelectedSelector: ephemera.firstSelectedSelector,
  7791. lastSelectedSelector: ephemera.lastSelectedSelector
  7792. };
  7793. };
  7794. const SelectionAnnotation = {
  7795. byClass,
  7796. byAttr
  7797. };
  7798. /*
  7799. NOTE: This file is duplicated in the following locations:
  7800. - plugins/table/selection/Ephemera.ts
  7801. - advtable
  7802. Make sure that if making changes to this file, the other files are updated as well
  7803. */
  7804. const strSelected = 'data-mce-selected';
  7805. const strSelectedSelector = 'td[' + strSelected + '],th[' + strSelected + ']';
  7806. // used with not selectors
  7807. const strAttributeSelector = '[' + strSelected + ']';
  7808. const strFirstSelected = 'data-mce-first-selected';
  7809. const strFirstSelectedSelector = 'td[' + strFirstSelected + '],th[' + strFirstSelected + ']';
  7810. const strLastSelected = 'data-mce-last-selected';
  7811. const strLastSelectedSelector = 'td[' + strLastSelected + '],th[' + strLastSelected + ']';
  7812. const attributeSelector = strAttributeSelector;
  7813. const ephemera = {
  7814. selected: strSelected,
  7815. selectedSelector: strSelectedSelector,
  7816. firstSelected: strFirstSelected,
  7817. firstSelectedSelector: strFirstSelectedSelector,
  7818. lastSelected: strLastSelected,
  7819. lastSelectedSelector: strLastSelectedSelector
  7820. };
  7821. /*
  7822. NOTE: This file is partially duplicated in the following locations:
  7823. - plugins/table/queries/TableTargets.ts
  7824. - advtable
  7825. Make sure that if making changes to this file, the other files are updated as well
  7826. */
  7827. const forMenu = (selectedCells, table, cell) => ({
  7828. element: cell,
  7829. mergable: mergable(table, selectedCells, ephemera),
  7830. unmergable: unmergable(selectedCells),
  7831. selection: selection(selectedCells)
  7832. });
  7833. const paste = (element, clipboard, generators) => ({
  7834. element,
  7835. clipboard,
  7836. generators
  7837. });
  7838. const pasteRows = (selectedCells, _cell, clipboard, generators) => ({
  7839. selection: selection(selectedCells),
  7840. clipboard,
  7841. generators
  7842. });
  7843. /*
  7844. NOTE: This file is partially duplicated in the following locations:
  7845. - plugins/table/selection/TableSelection.ts
  7846. - advtable
  7847. Make sure that if making changes to this file, the other files are updated as well
  7848. */
  7849. const getSelectionCellFallback = (element) => table(element).bind((table) => retrieve(table, ephemera.firstSelectedSelector)).fold(constant(element), (cells) => cells[0]);
  7850. const getSelectionFromSelector = (selector) => (initCell, isRoot) => {
  7851. const cellName = name(initCell);
  7852. const cell = cellName === 'col' || cellName === 'colgroup' ? getSelectionCellFallback(initCell) : initCell;
  7853. return closest$1(cell, selector, isRoot);
  7854. };
  7855. const getSelectionCellOrCaption = getSelectionFromSelector('th,td,caption');
  7856. const getSelectionCell = getSelectionFromSelector('th,td');
  7857. // Note: Includes single cell if the start of the selection whether collapsed or ranged is within a table cell
  7858. const getCellsFromSelection = (editor) => fromDom(editor.model.table.getSelectedCells());
  7859. const getCellsFromFakeSelection = (editor) => filter$2(getCellsFromSelection(editor), (cell) => is$1(cell, ephemera.selectedSelector));
  7860. const extractSelected = (cells) => {
  7861. // Assume for now that we only have one table (also handles the case where we multi select outside a table)
  7862. return table(cells[0]).map((table) => {
  7863. const replica = extract$1(table, attributeSelector);
  7864. removeDataStyle(replica);
  7865. return [replica];
  7866. });
  7867. };
  7868. const serializeElements = (editor, elements) => map$1(elements, (elm) => editor.selection.serializer.serialize(elm.dom, {})).join('');
  7869. const getTextContent = (editor, replicaElements) => {
  7870. const doc = editor.getDoc();
  7871. const dos = getRootNode(SugarElement.fromDom(editor.getBody()));
  7872. // Set up offscreen div so that the extracted table element can be inserted into the DOM
  7873. // TINY-10847: If the table element is detached from the DOM, calling innerText is equivalent to calling
  7874. // textContent which does not include '\n' and '\t' characters to separate rows and cells respectively
  7875. const offscreenDiv = SugarElement.fromTag('div', doc);
  7876. set$2(offscreenDiv, 'data-mce-bogus', 'all');
  7877. setAll(offscreenDiv, {
  7878. position: 'fixed',
  7879. left: '-9999999px',
  7880. top: '0',
  7881. overflow: 'hidden',
  7882. opacity: '0'
  7883. });
  7884. const root = getContentContainer(dos);
  7885. append(offscreenDiv, replicaElements);
  7886. append$1(root, offscreenDiv);
  7887. const textContent = offscreenDiv.dom.innerText;
  7888. remove$5(offscreenDiv);
  7889. return textContent;
  7890. };
  7891. const registerEvents = (editor, actions) => {
  7892. editor.on('BeforeGetContent', (e) => {
  7893. const multiCellContext = (cells) => {
  7894. e.preventDefault();
  7895. extractSelected(cells).each((replicaElements) => {
  7896. const content = e.format === 'text' ? getTextContent(editor, replicaElements) : serializeElements(editor, replicaElements);
  7897. e.content = content;
  7898. });
  7899. };
  7900. if (e.selection === true) {
  7901. const cells = getCellsFromFakeSelection(editor);
  7902. if (cells.length >= 1) {
  7903. multiCellContext(cells);
  7904. }
  7905. }
  7906. });
  7907. editor.on('BeforeSetContent', (e) => {
  7908. if (e.selection === true && e.paste === true) {
  7909. const selectedCells = getCellsFromSelection(editor);
  7910. head(selectedCells).each((cell) => {
  7911. table(cell).each((table) => {
  7912. const elements = filter$2(fromHtml(e.content), (content) => {
  7913. return name(content) !== 'meta';
  7914. });
  7915. const isTable = isTag('table');
  7916. if (shouldMergeContentOnPaste(editor) && elements.length === 1 && isTable(elements[0])) {
  7917. e.preventDefault();
  7918. const doc = SugarElement.fromDom(editor.getDoc());
  7919. const generators = paste$1(doc);
  7920. const targets = paste(cell, elements[0], generators);
  7921. actions.pasteCells(table, targets).each(() => {
  7922. editor.focus();
  7923. });
  7924. }
  7925. });
  7926. });
  7927. }
  7928. });
  7929. };
  7930. /*
  7931. NOTE: This file is duplicated in the following locations:
  7932. - core/api/TableEvents.ts
  7933. - plugins/table/api/Events.ts
  7934. - advtable
  7935. Make sure that if making changes to this file, the other files are updated as well
  7936. */
  7937. const fireNewRow = (editor, row) => editor.dispatch('NewRow', { node: row });
  7938. const fireNewCell = (editor, cell) => editor.dispatch('NewCell', { node: cell });
  7939. const fireTableModified = (editor, table, data) => {
  7940. editor.dispatch('TableModified', { ...data, table });
  7941. };
  7942. const fireTableSelectionChange = (editor, cells, start, finish, otherCells) => {
  7943. editor.dispatch('TableSelectionChange', {
  7944. cells,
  7945. start,
  7946. finish,
  7947. otherCells
  7948. });
  7949. };
  7950. const fireTableSelectionClear = (editor) => {
  7951. editor.dispatch('TableSelectionClear');
  7952. };
  7953. const fireObjectResizeStart = (editor, target, width, height, origin) => {
  7954. editor.dispatch('ObjectResizeStart', { target, width, height, origin });
  7955. };
  7956. const fireObjectResized = (editor, target, width, height, origin) => {
  7957. editor.dispatch('ObjectResized', { target, width, height, origin });
  7958. };
  7959. const styleModified = { structure: false, style: true };
  7960. const structureModified = { structure: true, style: false };
  7961. const styleAndStructureModified = { structure: true, style: true };
  7962. const get$1 = (editor, table) => {
  7963. // Note: We can't enforce none (responsive), as if someone manually resizes a table
  7964. // then it must switch to either pixel (fixed) or percentage (relative) sizing
  7965. if (isTablePercentagesForced(editor)) {
  7966. return TableSize.percentageSize(table);
  7967. }
  7968. else if (isTablePixelsForced(editor)) {
  7969. return TableSize.pixelSize(table);
  7970. }
  7971. else {
  7972. // Detect based on the table width
  7973. return TableSize.getTableSize(table);
  7974. }
  7975. };
  7976. const TableActions = (editor, resizeHandler, cellSelectionHandler) => {
  7977. const isTableBody = (editor) => name(getBody(editor)) === 'table';
  7978. const lastRowGuard = (table) => !isTableBody(editor) || getGridSize(table).rows > 1;
  7979. const lastColumnGuard = (table) => !isTableBody(editor) || getGridSize(table).columns > 1;
  7980. // Optional.none gives the default cloneFormats.
  7981. const cloneFormats = getTableCloneElements(editor);
  7982. const colMutationOp = isResizeTableColumnResizing(editor) ? noop : halve;
  7983. const getTableSectionType = (table) => {
  7984. switch (getTableHeaderType(editor)) {
  7985. case 'section':
  7986. return TableSection.section();
  7987. case 'sectionCells':
  7988. return TableSection.sectionCells();
  7989. case 'cells':
  7990. return TableSection.cells();
  7991. default:
  7992. // Attempt to automatically find the type. If a type can't be found
  7993. // then fallback to "section" to maintain backwards compatibility.
  7994. return TableSection.getTableSectionType(table, 'section');
  7995. }
  7996. };
  7997. const setSelectionFromAction = (table, result) => result.cursor.fold(() => {
  7998. // Snooker has reported we don't have a good cursor position. However, we may have a locked column
  7999. // with noneditable cells, so lets check if we have a noneditable cell and if so place the selection
  8000. const cells = cells$1(table);
  8001. return head(cells).filter(inBody).map((firstCell) => {
  8002. cellSelectionHandler.clearSelectedCells(table.dom);
  8003. const rng = editor.dom.createRng();
  8004. rng.selectNode(firstCell.dom);
  8005. editor.selection.setRng(rng);
  8006. set$2(firstCell, 'data-mce-selected', '1');
  8007. return rng;
  8008. });
  8009. }, (cell) => {
  8010. const des = freefallRtl(cell);
  8011. const rng = editor.dom.createRng();
  8012. rng.setStart(des.element.dom, des.offset);
  8013. rng.setEnd(des.element.dom, des.offset);
  8014. editor.selection.setRng(rng);
  8015. cellSelectionHandler.clearSelectedCells(table.dom);
  8016. return Optional.some(rng);
  8017. });
  8018. const execute = (operation, guard, mutate, effect) => (table, target, noEvents = false) => {
  8019. removeDataStyle(table);
  8020. const doc = SugarElement.fromDom(editor.getDoc());
  8021. const generators = cellOperations(mutate, doc, cloneFormats);
  8022. const behaviours = {
  8023. sizing: get$1(editor, table),
  8024. resize: isResizeTableColumnResizing(editor) ? resizeTable() : preserveTable(),
  8025. section: getTableSectionType(table)
  8026. };
  8027. return guard(table) ? operation(table, target, generators, behaviours).bind((result) => {
  8028. // Update the resize bars after the table operation
  8029. resizeHandler.refresh(table.dom);
  8030. // INVESTIGATE: Should "noEvents" prevent these from firing as well?
  8031. each$2(result.newRows, (row) => {
  8032. fireNewRow(editor, row.dom);
  8033. });
  8034. each$2(result.newCells, (cell) => {
  8035. fireNewCell(editor, cell.dom);
  8036. });
  8037. const range = setSelectionFromAction(table, result);
  8038. if (inBody(table)) {
  8039. removeDataStyle(table);
  8040. if (!noEvents) {
  8041. fireTableModified(editor, table.dom, effect);
  8042. }
  8043. }
  8044. return range.map((rng) => ({
  8045. rng,
  8046. effect
  8047. }));
  8048. }) : Optional.none();
  8049. };
  8050. const deleteRow = execute(eraseRows, lastRowGuard, noop, structureModified);
  8051. const deleteColumn = execute(eraseColumns, lastColumnGuard, noop, structureModified);
  8052. const insertRowsBefore$1 = execute(insertRowsBefore, always, noop, structureModified);
  8053. const insertRowsAfter$1 = execute(insertRowsAfter, always, noop, structureModified);
  8054. const insertColumnsBefore$1 = execute(insertColumnsBefore, always, colMutationOp, structureModified);
  8055. const insertColumnsAfter$1 = execute(insertColumnsAfter, always, colMutationOp, structureModified);
  8056. const mergeCells$1 = execute(mergeCells, always, noop, structureModified);
  8057. const unmergeCells$1 = execute(unmergeCells, always, noop, structureModified);
  8058. const pasteColsBefore$1 = execute(pasteColsBefore, always, noop, structureModified);
  8059. const pasteColsAfter$1 = execute(pasteColsAfter, always, noop, structureModified);
  8060. const pasteRowsBefore$1 = execute(pasteRowsBefore, always, noop, structureModified);
  8061. const pasteRowsAfter$1 = execute(pasteRowsAfter, always, noop, structureModified);
  8062. const pasteCells$1 = execute(pasteCells, always, noop, styleAndStructureModified);
  8063. const makeCellsHeader$1 = execute(makeCellsHeader, always, noop, structureModified);
  8064. const unmakeCellsHeader$1 = execute(unmakeCellsHeader, always, noop, structureModified);
  8065. const makeColumnsHeader$1 = execute(makeColumnsHeader, always, noop, structureModified);
  8066. const unmakeColumnsHeader$1 = execute(unmakeColumnsHeader, always, noop, structureModified);
  8067. const makeRowsHeader$1 = execute(makeRowsHeader, always, noop, structureModified);
  8068. const makeRowsBody$1 = execute(makeRowsBody, always, noop, structureModified);
  8069. const makeRowsFooter$1 = execute(makeRowsFooter, always, noop, structureModified);
  8070. const getTableCellType = getCellsType;
  8071. const getTableColType = getColumnsType;
  8072. const getTableRowType = getRowsType;
  8073. return {
  8074. deleteRow,
  8075. deleteColumn,
  8076. insertRowsBefore: insertRowsBefore$1,
  8077. insertRowsAfter: insertRowsAfter$1,
  8078. insertColumnsBefore: insertColumnsBefore$1,
  8079. insertColumnsAfter: insertColumnsAfter$1,
  8080. mergeCells: mergeCells$1,
  8081. unmergeCells: unmergeCells$1,
  8082. pasteColsBefore: pasteColsBefore$1,
  8083. pasteColsAfter: pasteColsAfter$1,
  8084. pasteRowsBefore: pasteRowsBefore$1,
  8085. pasteRowsAfter: pasteRowsAfter$1,
  8086. pasteCells: pasteCells$1,
  8087. makeCellsHeader: makeCellsHeader$1,
  8088. unmakeCellsHeader: unmakeCellsHeader$1,
  8089. makeColumnsHeader: makeColumnsHeader$1,
  8090. unmakeColumnsHeader: unmakeColumnsHeader$1,
  8091. makeRowsHeader: makeRowsHeader$1,
  8092. makeRowsBody: makeRowsBody$1,
  8093. makeRowsFooter: makeRowsFooter$1,
  8094. getTableRowType,
  8095. getTableCellType,
  8096. getTableColType
  8097. };
  8098. };
  8099. const placeCaretInCell = (editor, cell) => {
  8100. editor.selection.select(cell.dom, true);
  8101. editor.selection.collapse(true);
  8102. };
  8103. const selectFirstCellInTable = (editor, tableElm) => {
  8104. descendant(tableElm, 'td,th').each(curry(placeCaretInCell, editor));
  8105. };
  8106. const fireEvents = (editor, table) => {
  8107. each$2(descendants(table, 'tr'), (row) => {
  8108. fireNewRow(editor, row.dom);
  8109. each$2(descendants(row, 'th,td'), (cell) => {
  8110. fireNewCell(editor, cell.dom);
  8111. });
  8112. });
  8113. };
  8114. const isPercentage = (width) => isString(width) && width.indexOf('%') !== -1;
  8115. const insert = (editor, columns, rows, colHeaders, rowHeaders) => {
  8116. const defaultStyles = getTableDefaultStyles(editor);
  8117. const options = {
  8118. styles: defaultStyles,
  8119. attributes: getTableDefaultAttributes(editor),
  8120. colGroups: tableUseColumnGroup(editor)
  8121. };
  8122. // Don't create an undo level when inserting the base table HTML otherwise we can end up with 2 undo levels
  8123. editor.undoManager.ignore(() => {
  8124. const table = render(rows, columns, rowHeaders, colHeaders, getTableHeaderType(editor), options);
  8125. set$2(table, 'data-mce-id', '__mce');
  8126. const html = getOuter$2(table);
  8127. editor.insertContent(html);
  8128. editor.addVisual();
  8129. });
  8130. // Enforce the sizing mode of the table
  8131. return descendant(getBody(editor), 'table[data-mce-id="__mce"]').map((table) => {
  8132. if (isTablePixelsForced(editor)) {
  8133. convertToPixelSizeWidth(table);
  8134. }
  8135. else if (isTableResponsiveForced(editor)) {
  8136. convertToNoneSizeWidth(table);
  8137. }
  8138. else if (isTablePercentagesForced(editor) || isPercentage(defaultStyles.width)) {
  8139. convertToPercentSizeWidth(table);
  8140. }
  8141. removeDataStyle(table);
  8142. remove$6(table, 'data-mce-id');
  8143. fireEvents(editor, table);
  8144. selectFirstCellInTable(editor, table);
  8145. return table.dom;
  8146. }).getOrNull();
  8147. };
  8148. const insertTable = (editor, rows, columns, options = {}) => {
  8149. const checkInput = (val) => isNumber(val) && val > 0;
  8150. if (checkInput(rows) && checkInput(columns)) {
  8151. const headerRows = options.headerRows || 0;
  8152. const headerColumns = options.headerColumns || 0;
  8153. return insert(editor, columns, rows, headerColumns, headerRows);
  8154. }
  8155. else {
  8156. // eslint-disable-next-line no-console
  8157. console.error('Invalid values for mceInsertTable - rows and columns values are required to insert a table.');
  8158. return null;
  8159. }
  8160. };
  8161. var global = tinymce.util.Tools.resolve('tinymce.FakeClipboard');
  8162. /*
  8163. NOTE: This file is duplicated in the following locations:
  8164. - plugins/table/api/Clipboard.ts
  8165. Make sure that if making changes to this file, the other files are updated as well
  8166. */
  8167. const tableTypeBase = 'x-tinymce/dom-table-';
  8168. const tableTypeRow = tableTypeBase + 'rows';
  8169. const tableTypeColumn = tableTypeBase + 'columns';
  8170. const setData = (items) => {
  8171. const fakeClipboardItem = global.FakeClipboardItem(items);
  8172. global.write([fakeClipboardItem]);
  8173. };
  8174. const getData = (type) => {
  8175. var _a;
  8176. const items = (_a = global.read()) !== null && _a !== void 0 ? _a : [];
  8177. return findMap(items, (item) => Optional.from(item.getType(type)));
  8178. };
  8179. const clearData = (type) => {
  8180. if (getData(type).isSome()) {
  8181. global.clear();
  8182. }
  8183. };
  8184. const setRows = (rowsOpt) => {
  8185. rowsOpt.fold(clearRows, (rows) => setData({ [tableTypeRow]: rows }));
  8186. };
  8187. const getRows = () => getData(tableTypeRow);
  8188. const clearRows = () => clearData(tableTypeRow);
  8189. const setColumns = (columnsOpt) => {
  8190. columnsOpt.fold(clearColumns, (columns) => setData({ [tableTypeColumn]: columns }));
  8191. };
  8192. const getColumns = () => getData(tableTypeColumn);
  8193. const clearColumns = () => clearData(tableTypeColumn);
  8194. const getSelectionStartCellOrCaption = (editor) => getSelectionCellOrCaption(getSelectionStart(editor), getIsRoot(editor)).filter(isInEditableContext$1);
  8195. const getSelectionStartCell = (editor) => getSelectionCell(getSelectionStart(editor), getIsRoot(editor)).filter(isInEditableContext$1);
  8196. const registerCommands = (editor, actions) => {
  8197. const isRoot = getIsRoot(editor);
  8198. const eraseTable = () => getSelectionStartCellOrCaption(editor).each((cellOrCaption) => {
  8199. table(cellOrCaption, isRoot).filter(not(isRoot)).each((table) => {
  8200. const cursor = SugarElement.fromText('');
  8201. after$4(table, cursor);
  8202. remove$5(table);
  8203. if (editor.dom.isEmpty(editor.getBody())) {
  8204. editor.setContent('');
  8205. editor.selection.setCursorLocation();
  8206. }
  8207. else {
  8208. const rng = editor.dom.createRng();
  8209. rng.setStart(cursor.dom, 0);
  8210. rng.setEnd(cursor.dom, 0);
  8211. editor.selection.setRng(rng);
  8212. editor.nodeChanged();
  8213. }
  8214. });
  8215. });
  8216. const setSizingMode = (sizing) => getSelectionStartCellOrCaption(editor).each((cellOrCaption) => {
  8217. // Do nothing if tables are forced to use a specific sizing mode
  8218. const isForcedSizing = isTableResponsiveForced(editor) || isTablePixelsForced(editor) || isTablePercentagesForced(editor);
  8219. if (!isForcedSizing) {
  8220. table(cellOrCaption, isRoot).each((table) => {
  8221. if (sizing === 'relative' && !isPercentSizing(table)) {
  8222. convertToPercentSizeWidth(table);
  8223. }
  8224. else if (sizing === 'fixed' && !isPixelSizing(table)) {
  8225. convertToPixelSizeWidth(table);
  8226. }
  8227. else if (sizing === 'responsive' && !isNoneSizing(table)) {
  8228. convertToNoneSizeWidth(table);
  8229. }
  8230. removeDataStyle(table);
  8231. fireTableModified(editor, table.dom, structureModified);
  8232. });
  8233. }
  8234. });
  8235. const getTableFromCell = (cell) => table(cell, isRoot);
  8236. const performActionOnSelection = (action) => getSelectionStartCell(editor).bind((cell) => getTableFromCell(cell).map((table) => action(table, cell)));
  8237. const toggleTableClass = (_ui, clazz) => {
  8238. performActionOnSelection((table) => {
  8239. editor.formatter.toggle('tableclass', { value: clazz }, table.dom);
  8240. fireTableModified(editor, table.dom, styleModified);
  8241. });
  8242. };
  8243. const toggleTableCellClass = (_ui, clazz) => {
  8244. performActionOnSelection((table) => {
  8245. const selectedCells = getCellsFromSelection(editor);
  8246. const allHaveClass = forall(selectedCells, (cell) => editor.formatter.match('tablecellclass', { value: clazz }, cell.dom));
  8247. const formatterAction = allHaveClass ? editor.formatter.remove : editor.formatter.apply;
  8248. each$2(selectedCells, (cell) => formatterAction('tablecellclass', { value: clazz }, cell.dom));
  8249. fireTableModified(editor, table.dom, styleModified);
  8250. });
  8251. };
  8252. const toggleCaption = () => {
  8253. getSelectionStartCellOrCaption(editor).each((cellOrCaption) => {
  8254. table(cellOrCaption, isRoot).each((table) => {
  8255. child(table, 'caption').fold(() => {
  8256. const caption = SugarElement.fromTag('caption');
  8257. append$1(caption, SugarElement.fromText('Caption'));
  8258. appendAt(table, caption, 0);
  8259. editor.selection.setCursorLocation(caption.dom, 0);
  8260. }, (caption) => {
  8261. if (isTag('caption')(cellOrCaption)) {
  8262. one('td', table).each((td) => editor.selection.setCursorLocation(td.dom, 0));
  8263. }
  8264. remove$5(caption);
  8265. });
  8266. fireTableModified(editor, table.dom, structureModified);
  8267. });
  8268. });
  8269. };
  8270. const postExecute = (_data) => {
  8271. editor.focus();
  8272. };
  8273. const actOnSelection = (execute, noEvents = false) => performActionOnSelection((table, startCell) => {
  8274. const targets = forMenu(getCellsFromSelection(editor), table, startCell);
  8275. execute(table, targets, noEvents).each(postExecute);
  8276. });
  8277. const copyRowSelection = () => performActionOnSelection((table, startCell) => {
  8278. const targets = forMenu(getCellsFromSelection(editor), table, startCell);
  8279. const generators = cellOperations(noop, SugarElement.fromDom(editor.getDoc()), Optional.none());
  8280. return copyRows(table, targets, generators);
  8281. });
  8282. const copyColSelection = () => performActionOnSelection((table, startCell) => {
  8283. const targets = forMenu(getCellsFromSelection(editor), table, startCell);
  8284. return copyCols(table, targets);
  8285. });
  8286. const pasteOnSelection = (execute, getRows) =>
  8287. // If we have FakeClipboard rows to paste
  8288. getRows().each((rows) => {
  8289. const clonedRows = map$1(rows, (row) => deep(row));
  8290. performActionOnSelection((table, startCell) => {
  8291. const generators = paste$1(SugarElement.fromDom(editor.getDoc()));
  8292. const targets = pasteRows(getCellsFromSelection(editor), startCell, clonedRows, generators);
  8293. execute(table, targets).each(postExecute);
  8294. });
  8295. });
  8296. const actOnType = (getAction) => (_ui, args) => get$c(args, 'type').each((type) => {
  8297. actOnSelection(getAction(type), args.no_events);
  8298. });
  8299. // Register action commands
  8300. each$1({
  8301. mceTableSplitCells: () => actOnSelection(actions.unmergeCells),
  8302. mceTableMergeCells: () => actOnSelection(actions.mergeCells),
  8303. mceTableInsertRowBefore: () => actOnSelection(actions.insertRowsBefore),
  8304. mceTableInsertRowAfter: () => actOnSelection(actions.insertRowsAfter),
  8305. mceTableInsertColBefore: () => actOnSelection(actions.insertColumnsBefore),
  8306. mceTableInsertColAfter: () => actOnSelection(actions.insertColumnsAfter),
  8307. mceTableDeleteCol: () => actOnSelection(actions.deleteColumn),
  8308. mceTableDeleteRow: () => actOnSelection(actions.deleteRow),
  8309. mceTableCutCol: () => copyColSelection().each((selection) => {
  8310. setColumns(selection);
  8311. actOnSelection(actions.deleteColumn);
  8312. }),
  8313. mceTableCutRow: () => copyRowSelection().each((selection) => {
  8314. setRows(selection);
  8315. actOnSelection(actions.deleteRow);
  8316. }),
  8317. mceTableCopyCol: () => copyColSelection().each((selection) => setColumns(selection)),
  8318. mceTableCopyRow: () => copyRowSelection().each((selection) => setRows(selection)),
  8319. mceTablePasteColBefore: () => pasteOnSelection(actions.pasteColsBefore, getColumns),
  8320. mceTablePasteColAfter: () => pasteOnSelection(actions.pasteColsAfter, getColumns),
  8321. mceTablePasteRowBefore: () => pasteOnSelection(actions.pasteRowsBefore, getRows),
  8322. mceTablePasteRowAfter: () => pasteOnSelection(actions.pasteRowsAfter, getRows),
  8323. mceTableDelete: eraseTable,
  8324. mceTableCellToggleClass: toggleTableCellClass,
  8325. mceTableToggleClass: toggleTableClass,
  8326. mceTableToggleCaption: toggleCaption,
  8327. mceTableSizingMode: (_ui, sizing) => setSizingMode(sizing),
  8328. mceTableCellType: actOnType((type) => type === 'th' ? actions.makeCellsHeader : actions.unmakeCellsHeader),
  8329. mceTableColType: actOnType((type) => type === 'th' ? actions.makeColumnsHeader : actions.unmakeColumnsHeader),
  8330. mceTableRowType: actOnType((type) => {
  8331. switch (type) {
  8332. case 'header':
  8333. return actions.makeRowsHeader;
  8334. case 'footer':
  8335. return actions.makeRowsFooter;
  8336. default:
  8337. return actions.makeRowsBody;
  8338. }
  8339. })
  8340. }, (func, name) => editor.addCommand(name, func));
  8341. editor.addCommand('mceInsertTable', (_ui, args) => {
  8342. insertTable(editor, args.rows, args.columns, args.options);
  8343. });
  8344. // Apply cell style using command (background color, border color, border style and border width)
  8345. // tinyMCE.activeEditor.execCommand('mceTableApplyCellStyle', false, { backgroundColor: 'red', borderColor: 'blue' })
  8346. // Remove cell style using command (an empty string indicates to remove the style)
  8347. // tinyMCE.activeEditor.execCommand('mceTableApplyCellStyle', false, { backgroundColor: '' })
  8348. editor.addCommand('mceTableApplyCellStyle', (_ui, args) => {
  8349. const getFormatName = (style) => 'tablecell' + style.toLowerCase().replace('-', '');
  8350. if (!isObject(args)) {
  8351. return;
  8352. }
  8353. const cells = filter$2(getCellsFromSelection(editor), isInEditableContext$1);
  8354. if (cells.length === 0) {
  8355. return;
  8356. }
  8357. const validArgs = filter$1(args, (value, style) => editor.formatter.has(getFormatName(style)) && isString(value));
  8358. if (isEmpty(validArgs)) {
  8359. return;
  8360. }
  8361. each$1(validArgs, (value, style) => {
  8362. const formatName = getFormatName(style);
  8363. each$2(cells, (cell) => {
  8364. if (value === '') {
  8365. editor.formatter.remove(formatName, { value: null }, cell.dom, true);
  8366. }
  8367. else {
  8368. editor.formatter.apply(formatName, { value }, cell.dom);
  8369. }
  8370. });
  8371. });
  8372. /*
  8373. Use the first cell in the selection to get the table and fire the TableModified event.
  8374. If this command is applied over multiple tables, only the first table selected
  8375. will have a TableModified event thrown.
  8376. */
  8377. getTableFromCell(cells[0]).each((table) => fireTableModified(editor, table.dom, styleModified));
  8378. });
  8379. };
  8380. const registerQueryCommands = (editor, actions) => {
  8381. const isRoot = getIsRoot(editor);
  8382. const lookupOnSelection = (action) => getSelectionCell(getSelectionStart(editor)).bind((cell) => table(cell, isRoot).map((table) => {
  8383. const targets = forMenu(getCellsFromSelection(editor), table, cell);
  8384. return action(table, targets);
  8385. })).getOr('');
  8386. each$1({
  8387. mceTableRowType: () => lookupOnSelection(actions.getTableRowType),
  8388. mceTableCellType: () => lookupOnSelection(actions.getTableCellType),
  8389. mceTableColType: () => lookupOnSelection(actions.getTableColType)
  8390. }, (func, name) => editor.addQueryValueHandler(name, func));
  8391. };
  8392. const hasInternalTarget = (e) => !has(SugarElement.fromDom(e.target), 'ephox-snooker-resizer-bar');
  8393. const TableCellSelectionHandler = (editor, resizeHandler) => {
  8394. const cellSelection = Selections(() => SugarElement.fromDom(editor.getBody()), () => getSelectionCell(getSelectionStart(editor), getIsRoot(editor)), ephemera.selectedSelector);
  8395. const onSelection = (cells, start, finish) => {
  8396. const tableOpt = table(start);
  8397. tableOpt.each((table) => {
  8398. const cellsDom = map$1(cells, (cell) => cell.dom);
  8399. const cloneFormats = getTableCloneElements(editor);
  8400. const generators = cellOperations(noop, SugarElement.fromDom(editor.getDoc()), cloneFormats);
  8401. const selectedCells = getCellsFromSelection(editor);
  8402. const otherCellsDom = getOtherCells(table, { selection: selectedCells }, generators)
  8403. .map((otherCells) => map(otherCells, (cellArr) => map$1(cellArr, (cell) => cell.dom)))
  8404. .getOrUndefined();
  8405. fireTableSelectionChange(editor, cellsDom, start.dom, finish.dom, otherCellsDom);
  8406. });
  8407. };
  8408. const onClear = () => fireTableSelectionClear(editor);
  8409. const annotations = SelectionAnnotation.byAttr(ephemera, onSelection, onClear);
  8410. editor.on('init', (_e) => {
  8411. const win = editor.getWin();
  8412. const body = getBody(editor);
  8413. const isRoot = getIsRoot(editor);
  8414. // When the selection changes through either the mouse or keyboard, and the selection is no longer within the table.
  8415. // Remove the selection.
  8416. const syncSelection = () => {
  8417. const sel = editor.selection;
  8418. const start = SugarElement.fromDom(sel.getStart());
  8419. const end = SugarElement.fromDom(sel.getEnd());
  8420. const shared = sharedOne(table, [start, end]);
  8421. shared.fold(() => annotations.clear(body), noop);
  8422. };
  8423. const mouseHandlers = mouse(win, body, isRoot, annotations);
  8424. const keyHandlers = keyboard(win, body, isRoot, annotations);
  8425. const external$1 = external(win, body, isRoot, annotations);
  8426. const hasShiftKey = (event) => event.raw.shiftKey === true;
  8427. editor.on('TableSelectorChange', (e) => external$1(e.start, e.finish));
  8428. const handleResponse = (event, response) => {
  8429. // Only handle shift key non shiftkey cell navigation is handled by core
  8430. if (!hasShiftKey(event)) {
  8431. return;
  8432. }
  8433. if (response.kill) {
  8434. event.kill();
  8435. }
  8436. response.selection.each((ns) => {
  8437. const relative = SimSelection.relative(ns.start, ns.finish);
  8438. const rng = asLtrRange(win, relative);
  8439. editor.selection.setRng(rng);
  8440. });
  8441. };
  8442. const keyup = (event) => {
  8443. const wrappedEvent = fromRawEvent(event);
  8444. // Note, this is an optimisation.
  8445. if (wrappedEvent.raw.shiftKey && isNavigation(wrappedEvent.raw.which)) {
  8446. const rng = editor.selection.getRng();
  8447. const start = SugarElement.fromDom(rng.startContainer);
  8448. const end = SugarElement.fromDom(rng.endContainer);
  8449. keyHandlers.keyup(wrappedEvent, start, rng.startOffset, end, rng.endOffset).each((response) => {
  8450. handleResponse(wrappedEvent, response);
  8451. });
  8452. }
  8453. };
  8454. const keydown = (event) => {
  8455. const wrappedEvent = fromRawEvent(event);
  8456. resizeHandler.hide();
  8457. const rng = editor.selection.getRng();
  8458. const start = SugarElement.fromDom(rng.startContainer);
  8459. const end = SugarElement.fromDom(rng.endContainer);
  8460. const direction = onDirection(ltr, rtl)(SugarElement.fromDom(editor.selection.getStart()));
  8461. keyHandlers.keydown(wrappedEvent, start, rng.startOffset, end, rng.endOffset, direction).each((response) => {
  8462. handleResponse(wrappedEvent, response);
  8463. });
  8464. resizeHandler.show();
  8465. };
  8466. const isLeftMouse = (raw) => raw.button === 0;
  8467. // https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
  8468. const isLeftButtonPressed = (raw) => {
  8469. // Only added by Chrome/Firefox in June 2015.
  8470. // This is only to fix a 1px bug (TBIO-2836) so return true if we're on an older browser
  8471. if (raw.buttons === undefined) {
  8472. return true;
  8473. }
  8474. // use bitwise & for optimal comparison
  8475. // eslint-disable-next-line no-bitwise
  8476. return (raw.buttons & 1) !== 0;
  8477. };
  8478. const dragStart = (_e) => {
  8479. mouseHandlers.clearstate();
  8480. };
  8481. const mouseDown = (e) => {
  8482. if (isLeftMouse(e) && hasInternalTarget(e)) {
  8483. mouseHandlers.mousedown(fromRawEvent(e));
  8484. }
  8485. };
  8486. const mouseOver = (e) => {
  8487. if (isLeftButtonPressed(e) && hasInternalTarget(e)) {
  8488. mouseHandlers.mouseover(fromRawEvent(e));
  8489. }
  8490. };
  8491. const mouseUp = (e) => {
  8492. if (isLeftMouse(e) && hasInternalTarget(e)) {
  8493. mouseHandlers.mouseup(fromRawEvent(e));
  8494. }
  8495. };
  8496. const getDoubleTap = () => {
  8497. const lastTarget = Cell(SugarElement.fromDom(body));
  8498. const lastTimeStamp = Cell(0);
  8499. const touchEnd = (t) => {
  8500. const target = SugarElement.fromDom(t.target);
  8501. if (isTag('td')(target) || isTag('th')(target)) {
  8502. const lT = lastTarget.get();
  8503. const lTS = lastTimeStamp.get();
  8504. if (eq$1(lT, target) && (t.timeStamp - lTS) < 300) {
  8505. t.preventDefault();
  8506. external$1(target, target);
  8507. }
  8508. }
  8509. lastTarget.set(target);
  8510. lastTimeStamp.set(t.timeStamp);
  8511. };
  8512. return {
  8513. touchEnd
  8514. };
  8515. };
  8516. const doubleTap = getDoubleTap();
  8517. editor.on('dragstart', dragStart);
  8518. editor.on('mousedown', mouseDown);
  8519. editor.on('mouseover', mouseOver);
  8520. editor.on('mouseup', mouseUp);
  8521. editor.on('touchend', doubleTap.touchEnd);
  8522. editor.on('keyup', keyup);
  8523. editor.on('keydown', keydown);
  8524. editor.on('NodeChange', syncSelection);
  8525. });
  8526. editor.on('PreInit', () => {
  8527. editor.serializer.addTempAttr(ephemera.firstSelected);
  8528. editor.serializer.addTempAttr(ephemera.lastSelected);
  8529. });
  8530. const clearSelectedCells = (container) => annotations.clear(SugarElement.fromDom(container));
  8531. const getSelectedCells = () => fold(cellSelection.get(),
  8532. // No fake selected cells
  8533. constant([]),
  8534. // This path is taken whenever there is fake cell selection even for just a single selected cell
  8535. (cells) => {
  8536. return map$1(cells, (cell) => cell.dom);
  8537. },
  8538. // For this path, the start of the selection whether collapsed or ranged is within a table cell
  8539. (cell) => [cell.dom]);
  8540. return {
  8541. getSelectedCells,
  8542. clearSelectedCells
  8543. };
  8544. };
  8545. const get = (editor, isResizable) => {
  8546. const editorBody = SugarElement.fromDom(editor.getBody());
  8547. return ResizeWire.body(editorBody, isResizable);
  8548. };
  8549. const isTable = (node) => isNonNullable(node) && node.nodeName === 'TABLE';
  8550. const barResizerPrefix = 'bar-';
  8551. const isResizable = (elm) => get$b(elm, 'data-mce-resize') !== 'false';
  8552. const syncTableCellPixels = (table) => {
  8553. const warehouse = Warehouse.fromTable(table);
  8554. if (!Warehouse.hasColumns(warehouse)) {
  8555. // Ensure the specified width matches the actual cell width
  8556. each$2(cells$1(table), (cell) => {
  8557. const computedWidth = get$9(cell, 'width');
  8558. set$1(cell, 'width', computedWidth);
  8559. remove$6(cell, 'width');
  8560. });
  8561. }
  8562. };
  8563. const isCornerResize = (origin) => startsWith(origin, 'corner-');
  8564. const getCornerLocation = (origin) => removeLeading(origin, 'corner-');
  8565. const TableResizeHandler = (editor) => {
  8566. const selectionRng = value();
  8567. const tableResize = value();
  8568. const resizeWire = value();
  8569. let startW;
  8570. let startRawW;
  8571. let startH;
  8572. let startRawH;
  8573. const lazySizing = (table) => get$1(editor, table);
  8574. const lazyResizingBehaviour = () => isPreserveTableColumnResizing(editor) ? preserveTable() : resizeTable();
  8575. const getNumColumns = (table) => getGridSize(table).columns;
  8576. const getNumRows = (table) => getGridSize(table).rows;
  8577. const afterCornerResize = (table, origin, width, height) => {
  8578. // Origin will tell us which handle was clicked, eg corner-se or corner-nw
  8579. // so check to see if it ends with `e` (eg east edge)
  8580. const location = getCornerLocation(origin);
  8581. const isRightEdgeResize = endsWith(location, 'e');
  8582. const isNorthEdgeResize = startsWith(location, 'n');
  8583. // Responsive tables don't have a width so we need to convert it to a relative/percent
  8584. // table instead, as that's closer to responsive sizing than fixed sizing
  8585. if (startRawW === '') {
  8586. convertToPercentSizeWidth(table);
  8587. }
  8588. // Responsive tables don't have a height so we need to convert it to a fixed value to be able to resize the table height
  8589. if (startRawH === '') {
  8590. convertToPixelSizeHeight(table);
  8591. }
  8592. // Adjust the column sizes and update the table width to use the right sizing, if the table changed size.
  8593. // This is needed as core will always use pixels when setting the width.
  8594. if (width !== startW && startRawW !== '') {
  8595. // Restore the original size and then let snooker resize appropriately
  8596. set$1(table, 'width', startRawW);
  8597. const resizing = lazyResizingBehaviour();
  8598. const tableSize = lazySizing(table);
  8599. // For preserve table we want to always resize the entire table. So pretend the last column is being resized
  8600. const col = isPreserveTableColumnResizing(editor) || isRightEdgeResize ? getNumColumns(table) - 1 : 0;
  8601. adjustWidth(table, width - startW, col, resizing, tableSize);
  8602. // Handle the edge case where someone might fire this event without resizing.
  8603. // If so then we need to ensure the table is still using percent
  8604. }
  8605. else if (isPercentage$1(startRawW)) {
  8606. const percentW = parseFloat(startRawW.replace('%', ''));
  8607. const targetPercentW = width * percentW / startW;
  8608. set$1(table, 'width', targetPercentW + '%');
  8609. }
  8610. // Sync the cell sizes, as the core resizing logic doesn't update them, but snooker does
  8611. if (isPixel(startRawW)) {
  8612. syncTableCellPixels(table);
  8613. }
  8614. // NOTE: This will only change the height of the first or last tr
  8615. if (height !== startH && startRawH !== '') {
  8616. // Restore the original size and then let snooker resize appropriately
  8617. set$1(table, 'height', startRawH);
  8618. const idx = isNorthEdgeResize ? 0 : getNumRows(table) - 1;
  8619. adjustHeight(table, height - startH, idx);
  8620. }
  8621. };
  8622. const destroy = () => {
  8623. tableResize.on((sz) => {
  8624. sz.destroy();
  8625. });
  8626. };
  8627. editor.on('init', () => {
  8628. const rawWire = get(editor, isResizable);
  8629. resizeWire.set(rawWire);
  8630. if (hasTableObjectResizing(editor) && hasTableResizeBars(editor)) {
  8631. const resizing = lazyResizingBehaviour();
  8632. const sz = TableResize.create(rawWire, resizing, lazySizing);
  8633. if (!editor.mode.isReadOnly()) {
  8634. sz.on();
  8635. }
  8636. sz.events.startDrag.bind((_event) => {
  8637. selectionRng.set(editor.selection.getRng());
  8638. });
  8639. sz.events.beforeResize.bind((event) => {
  8640. const rawTable = event.table.dom;
  8641. fireObjectResizeStart(editor, rawTable, getPixelWidth(rawTable), getPixelHeight(rawTable), barResizerPrefix + event.type);
  8642. });
  8643. sz.events.afterResize.bind((event) => {
  8644. const table = event.table;
  8645. const rawTable = table.dom;
  8646. removeDataStyle(table);
  8647. selectionRng.on((rng) => {
  8648. editor.selection.setRng(rng);
  8649. editor.focus();
  8650. });
  8651. fireObjectResized(editor, rawTable, getPixelWidth(rawTable), getPixelHeight(rawTable), barResizerPrefix + event.type);
  8652. editor.undoManager.add();
  8653. });
  8654. tableResize.set(sz);
  8655. }
  8656. });
  8657. // If we're updating the table width via the old mechanic, we need to update the constituent cells' widths/heights too.
  8658. editor.on('ObjectResizeStart', (e) => {
  8659. const targetElm = e.target;
  8660. if (isTable(targetElm) && !editor.mode.isReadOnly()) {
  8661. const table = SugarElement.fromDom(targetElm);
  8662. // Add a class based on the resizing mode
  8663. each$2(editor.dom.select('.mce-clonedresizable'), (clone) => {
  8664. editor.dom.addClass(clone, 'mce-' + getTableColumnResizingBehaviour(editor) + '-columns');
  8665. });
  8666. if (!isPixelSizing(table) && isTablePixelsForced(editor)) {
  8667. convertToPixelSizeWidth(table);
  8668. }
  8669. else if (!isPercentSizing(table) && isTablePercentagesForced(editor)) {
  8670. convertToPercentSizeWidth(table);
  8671. }
  8672. // TINY-6601: If resizing using a bar, then snooker will base the resizing on the initial size. So
  8673. // when using a responsive table we need to ensure we convert to a relative table before resizing
  8674. if (isNoneSizing(table) && startsWith(e.origin, barResizerPrefix)) {
  8675. convertToPercentSizeWidth(table);
  8676. }
  8677. startW = e.width;
  8678. startRawW = isTableResponsiveForced(editor) ? '' : getRawWidth(editor, targetElm).getOr('');
  8679. startH = e.height;
  8680. startRawH = getRawHeight(editor, targetElm).getOr('');
  8681. }
  8682. });
  8683. editor.on('ObjectResized', (e) => {
  8684. const targetElm = e.target;
  8685. if (isTable(targetElm)) {
  8686. const table = SugarElement.fromDom(targetElm);
  8687. // Resize based on the snooker logic to adjust the individual col/rows if resized from a corner
  8688. const origin = e.origin;
  8689. if (isCornerResize(origin)) {
  8690. afterCornerResize(table, origin, e.width, e.height);
  8691. }
  8692. removeDataStyle(table);
  8693. fireTableModified(editor, table.dom, styleModified);
  8694. }
  8695. });
  8696. const showResizeBars = () => {
  8697. tableResize.on((resize) => {
  8698. resize.on();
  8699. resize.showBars();
  8700. });
  8701. };
  8702. const hideResizeBars = () => {
  8703. tableResize.on((resize) => {
  8704. resize.off();
  8705. resize.hideBars();
  8706. });
  8707. };
  8708. editor.on('DisabledStateChange', (e) => {
  8709. e.state ? hideResizeBars() : showResizeBars();
  8710. });
  8711. editor.on('SwitchMode', () => {
  8712. editor.mode.isReadOnly() ? hideResizeBars() : showResizeBars();
  8713. });
  8714. editor.on('dragstart dragend', (e) => {
  8715. e.type === 'dragstart' ? hideResizeBars() : showResizeBars();
  8716. });
  8717. editor.on('remove', () => {
  8718. destroy();
  8719. });
  8720. const refresh = (table) => {
  8721. tableResize.on((resize) => resize.refreshBars(SugarElement.fromDom(table)));
  8722. };
  8723. const hide = () => {
  8724. tableResize.on((resize) => resize.hideBars());
  8725. };
  8726. const show = () => {
  8727. tableResize.on((resize) => resize.showBars());
  8728. };
  8729. return {
  8730. refresh,
  8731. hide,
  8732. show
  8733. };
  8734. };
  8735. const setupTable = (editor) => {
  8736. register(editor);
  8737. const resizeHandler = TableResizeHandler(editor);
  8738. const cellSelectionHandler = TableCellSelectionHandler(editor, resizeHandler);
  8739. const actions = TableActions(editor, resizeHandler, cellSelectionHandler);
  8740. registerCommands(editor, actions);
  8741. registerQueryCommands(editor, actions);
  8742. // TODO: TINY-8385 Maybe move to core. Although, will need RTC to have that working first
  8743. registerEvents(editor, actions);
  8744. return {
  8745. getSelectedCells: cellSelectionHandler.getSelectedCells,
  8746. clearSelectedCells: cellSelectionHandler.clearSelectedCells
  8747. };
  8748. };
  8749. const DomModel = (editor) => {
  8750. const table = setupTable(editor);
  8751. return {
  8752. table
  8753. };
  8754. };
  8755. var Model = () => {
  8756. global$1.add('dom', DomModel);
  8757. };
  8758. Model();
  8759. /** *****
  8760. * DO NOT EXPORT ANYTHING
  8761. *
  8762. * IF YOU DO ROLLUP WILL LEAVE A GLOBAL ON THE PAGE
  8763. *******/
  8764. })();