theme.js 1.5 MB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542554355445545554655475548554955505551555255535554555555565557555855595560556155625563556455655566556755685569557055715572557355745575557655775578557955805581558255835584558555865587558855895590559155925593559455955596559755985599560056015602560356045605560656075608560956105611561256135614561556165617561856195620562156225623562456255626562756285629563056315632563356345635563656375638563956405641564256435644564556465647564856495650565156525653565456555656565756585659566056615662566356645665566656675668566956705671567256735674567556765677567856795680568156825683568456855686568756885689569056915692569356945695569656975698569957005701570257035704570557065707570857095710571157125713571457155716571757185719572057215722572357245725572657275728572957305731573257335734573557365737573857395740574157425743574457455746574757485749575057515752575357545755575657575758575957605761576257635764576557665767576857695770577157725773577457755776577757785779578057815782578357845785578657875788578957905791579257935794579557965797579857995800580158025803580458055806580758085809581058115812581358145815581658175818581958205821582258235824582558265827582858295830583158325833583458355836583758385839584058415842584358445845584658475848584958505851585258535854585558565857585858595860586158625863586458655866586758685869587058715872587358745875587658775878587958805881588258835884588558865887588858895890589158925893589458955896589758985899590059015902590359045905590659075908590959105911591259135914591559165917591859195920592159225923592459255926592759285929593059315932593359345935593659375938593959405941594259435944594559465947594859495950595159525953595459555956595759585959596059615962596359645965596659675968596959705971597259735974597559765977597859795980598159825983598459855986598759885989599059915992599359945995599659975998599960006001600260036004600560066007600860096010601160126013601460156016601760186019602060216022602360246025602660276028602960306031603260336034603560366037603860396040604160426043604460456046604760486049605060516052605360546055605660576058605960606061606260636064606560666067606860696070607160726073607460756076607760786079608060816082608360846085608660876088608960906091609260936094609560966097609860996100610161026103610461056106610761086109611061116112611361146115611661176118611961206121612261236124612561266127612861296130613161326133613461356136613761386139614061416142614361446145614661476148614961506151615261536154615561566157615861596160616161626163616461656166616761686169617061716172617361746175617661776178617961806181618261836184618561866187618861896190619161926193619461956196619761986199620062016202620362046205620662076208620962106211621262136214621562166217621862196220622162226223622462256226622762286229623062316232623362346235623662376238623962406241624262436244624562466247624862496250625162526253625462556256625762586259626062616262626362646265626662676268626962706271627262736274627562766277627862796280628162826283628462856286628762886289629062916292629362946295629662976298629963006301630263036304630563066307630863096310631163126313631463156316631763186319632063216322632363246325632663276328632963306331633263336334633563366337633863396340634163426343634463456346634763486349635063516352635363546355635663576358635963606361636263636364636563666367636863696370637163726373637463756376637763786379638063816382638363846385638663876388638963906391639263936394639563966397639863996400640164026403640464056406640764086409641064116412641364146415641664176418641964206421642264236424642564266427642864296430643164326433643464356436643764386439644064416442644364446445644664476448644964506451645264536454645564566457645864596460646164626463646464656466646764686469647064716472647364746475647664776478647964806481648264836484648564866487648864896490649164926493649464956496649764986499650065016502650365046505650665076508650965106511651265136514651565166517651865196520652165226523652465256526652765286529653065316532653365346535653665376538653965406541654265436544654565466547654865496550655165526553655465556556655765586559656065616562656365646565656665676568656965706571657265736574657565766577657865796580658165826583658465856586658765886589659065916592659365946595659665976598659966006601660266036604660566066607660866096610661166126613661466156616661766186619662066216622662366246625662666276628662966306631663266336634663566366637663866396640664166426643664466456646664766486649665066516652665366546655665666576658665966606661666266636664666566666667666866696670667166726673667466756676667766786679668066816682668366846685668666876688668966906691669266936694669566966697669866996700670167026703670467056706670767086709671067116712671367146715671667176718671967206721672267236724672567266727672867296730673167326733673467356736673767386739674067416742674367446745674667476748674967506751675267536754675567566757675867596760676167626763676467656766676767686769677067716772677367746775677667776778677967806781678267836784678567866787678867896790679167926793679467956796679767986799680068016802680368046805680668076808680968106811681268136814681568166817681868196820682168226823682468256826682768286829683068316832683368346835683668376838683968406841684268436844684568466847684868496850685168526853685468556856685768586859686068616862686368646865686668676868686968706871687268736874687568766877687868796880688168826883688468856886688768886889689068916892689368946895689668976898689969006901690269036904690569066907690869096910691169126913691469156916691769186919692069216922692369246925692669276928692969306931693269336934693569366937693869396940694169426943694469456946694769486949695069516952695369546955695669576958695969606961696269636964696569666967696869696970697169726973697469756976697769786979698069816982698369846985698669876988698969906991699269936994699569966997699869997000700170027003700470057006700770087009701070117012701370147015701670177018701970207021702270237024702570267027702870297030703170327033703470357036703770387039704070417042704370447045704670477048704970507051705270537054705570567057705870597060706170627063706470657066706770687069707070717072707370747075707670777078707970807081708270837084708570867087708870897090709170927093709470957096709770987099710071017102710371047105710671077108710971107111711271137114711571167117711871197120712171227123712471257126712771287129713071317132713371347135713671377138713971407141714271437144714571467147714871497150715171527153715471557156715771587159716071617162716371647165716671677168716971707171717271737174717571767177717871797180718171827183718471857186718771887189719071917192719371947195719671977198719972007201720272037204720572067207720872097210721172127213721472157216721772187219722072217222722372247225722672277228722972307231723272337234723572367237723872397240724172427243724472457246724772487249725072517252725372547255725672577258725972607261726272637264726572667267726872697270727172727273727472757276727772787279728072817282728372847285728672877288728972907291729272937294729572967297729872997300730173027303730473057306730773087309731073117312731373147315731673177318731973207321732273237324732573267327732873297330733173327333733473357336733773387339734073417342734373447345734673477348734973507351735273537354735573567357735873597360736173627363736473657366736773687369737073717372737373747375737673777378737973807381738273837384738573867387738873897390739173927393739473957396739773987399740074017402740374047405740674077408740974107411741274137414741574167417741874197420742174227423742474257426742774287429743074317432743374347435743674377438743974407441744274437444744574467447744874497450745174527453745474557456745774587459746074617462746374647465746674677468746974707471747274737474747574767477747874797480748174827483748474857486748774887489749074917492749374947495749674977498749975007501750275037504750575067507750875097510751175127513751475157516751775187519752075217522752375247525752675277528752975307531753275337534753575367537753875397540754175427543754475457546754775487549755075517552755375547555755675577558755975607561756275637564756575667567756875697570757175727573757475757576757775787579758075817582758375847585758675877588758975907591759275937594759575967597759875997600760176027603760476057606760776087609761076117612761376147615761676177618761976207621762276237624762576267627762876297630763176327633763476357636763776387639764076417642764376447645764676477648764976507651765276537654765576567657765876597660766176627663766476657666766776687669767076717672767376747675767676777678767976807681768276837684768576867687768876897690769176927693769476957696769776987699770077017702770377047705770677077708770977107711771277137714771577167717771877197720772177227723772477257726772777287729773077317732773377347735773677377738773977407741774277437744774577467747774877497750775177527753775477557756775777587759776077617762776377647765776677677768776977707771777277737774777577767777777877797780778177827783778477857786778777887789779077917792779377947795779677977798779978007801780278037804780578067807780878097810781178127813781478157816781778187819782078217822782378247825782678277828782978307831783278337834783578367837783878397840784178427843784478457846784778487849785078517852785378547855785678577858785978607861786278637864786578667867786878697870787178727873787478757876787778787879788078817882788378847885788678877888788978907891789278937894789578967897789878997900790179027903790479057906790779087909791079117912791379147915791679177918791979207921792279237924792579267927792879297930793179327933793479357936793779387939794079417942794379447945794679477948794979507951795279537954795579567957795879597960796179627963796479657966796779687969797079717972797379747975797679777978797979807981798279837984798579867987798879897990799179927993799479957996799779987999800080018002800380048005800680078008800980108011801280138014801580168017801880198020802180228023802480258026802780288029803080318032803380348035803680378038803980408041804280438044804580468047804880498050805180528053805480558056805780588059806080618062806380648065806680678068806980708071807280738074807580768077807880798080808180828083808480858086808780888089809080918092809380948095809680978098809981008101810281038104810581068107810881098110811181128113811481158116811781188119812081218122812381248125812681278128812981308131813281338134813581368137813881398140814181428143814481458146814781488149815081518152815381548155815681578158815981608161816281638164816581668167816881698170817181728173817481758176817781788179818081818182818381848185818681878188818981908191819281938194819581968197819881998200820182028203820482058206820782088209821082118212821382148215821682178218821982208221822282238224822582268227822882298230823182328233823482358236823782388239824082418242824382448245824682478248824982508251825282538254825582568257825882598260826182628263826482658266826782688269827082718272827382748275827682778278827982808281828282838284828582868287828882898290829182928293829482958296829782988299830083018302830383048305830683078308830983108311831283138314831583168317831883198320832183228323832483258326832783288329833083318332833383348335833683378338833983408341834283438344834583468347834883498350835183528353835483558356835783588359836083618362836383648365836683678368836983708371837283738374837583768377837883798380838183828383838483858386838783888389839083918392839383948395839683978398839984008401840284038404840584068407840884098410841184128413841484158416841784188419842084218422842384248425842684278428842984308431843284338434843584368437843884398440844184428443844484458446844784488449845084518452845384548455845684578458845984608461846284638464846584668467846884698470847184728473847484758476847784788479848084818482848384848485848684878488848984908491849284938494849584968497849884998500850185028503850485058506850785088509851085118512851385148515851685178518851985208521852285238524852585268527852885298530853185328533853485358536853785388539854085418542854385448545854685478548854985508551855285538554855585568557855885598560856185628563856485658566856785688569857085718572857385748575857685778578857985808581858285838584858585868587858885898590859185928593859485958596859785988599860086018602860386048605860686078608860986108611861286138614861586168617861886198620862186228623862486258626862786288629863086318632863386348635863686378638863986408641864286438644864586468647864886498650865186528653865486558656865786588659866086618662866386648665866686678668866986708671867286738674867586768677867886798680868186828683868486858686868786888689869086918692869386948695869686978698869987008701870287038704870587068707870887098710871187128713871487158716871787188719872087218722872387248725872687278728872987308731873287338734873587368737873887398740874187428743874487458746874787488749875087518752875387548755875687578758875987608761876287638764876587668767876887698770877187728773877487758776877787788779878087818782878387848785878687878788878987908791879287938794879587968797879887998800880188028803880488058806880788088809881088118812881388148815881688178818881988208821882288238824882588268827882888298830883188328833883488358836883788388839884088418842884388448845884688478848884988508851885288538854885588568857885888598860886188628863886488658866886788688869887088718872887388748875887688778878887988808881888288838884888588868887888888898890889188928893889488958896889788988899890089018902890389048905890689078908890989108911891289138914891589168917891889198920892189228923892489258926892789288929893089318932893389348935893689378938893989408941894289438944894589468947894889498950895189528953895489558956895789588959896089618962896389648965896689678968896989708971897289738974897589768977897889798980898189828983898489858986898789888989899089918992899389948995899689978998899990009001900290039004900590069007900890099010901190129013901490159016901790189019902090219022902390249025902690279028902990309031903290339034903590369037903890399040904190429043904490459046904790489049905090519052905390549055905690579058905990609061906290639064906590669067906890699070907190729073907490759076907790789079908090819082908390849085908690879088908990909091909290939094909590969097909890999100910191029103910491059106910791089109911091119112911391149115911691179118911991209121912291239124912591269127912891299130913191329133913491359136913791389139914091419142914391449145914691479148914991509151915291539154915591569157915891599160916191629163916491659166916791689169917091719172917391749175917691779178917991809181918291839184918591869187918891899190919191929193919491959196919791989199920092019202920392049205920692079208920992109211921292139214921592169217921892199220922192229223922492259226922792289229923092319232923392349235923692379238923992409241924292439244924592469247924892499250925192529253925492559256925792589259926092619262926392649265926692679268926992709271927292739274927592769277927892799280928192829283928492859286928792889289929092919292929392949295929692979298929993009301930293039304930593069307930893099310931193129313931493159316931793189319932093219322932393249325932693279328932993309331933293339334933593369337933893399340934193429343934493459346934793489349935093519352935393549355935693579358935993609361936293639364936593669367936893699370937193729373937493759376937793789379938093819382938393849385938693879388938993909391939293939394939593969397939893999400940194029403940494059406940794089409941094119412941394149415941694179418941994209421942294239424942594269427942894299430943194329433943494359436943794389439944094419442944394449445944694479448944994509451945294539454945594569457945894599460946194629463946494659466946794689469947094719472947394749475947694779478947994809481948294839484948594869487948894899490949194929493949494959496949794989499950095019502950395049505950695079508950995109511951295139514951595169517951895199520952195229523952495259526952795289529953095319532953395349535953695379538953995409541954295439544954595469547954895499550955195529553955495559556955795589559956095619562956395649565956695679568956995709571957295739574957595769577957895799580958195829583958495859586958795889589959095919592959395949595959695979598959996009601960296039604960596069607960896099610961196129613961496159616961796189619962096219622962396249625962696279628962996309631963296339634963596369637963896399640964196429643964496459646964796489649965096519652965396549655965696579658965996609661966296639664966596669667966896699670967196729673967496759676967796789679968096819682968396849685968696879688968996909691969296939694969596969697969896999700970197029703970497059706970797089709971097119712971397149715971697179718971997209721972297239724972597269727972897299730973197329733973497359736973797389739974097419742974397449745974697479748974997509751975297539754975597569757975897599760976197629763976497659766976797689769977097719772977397749775977697779778977997809781978297839784978597869787978897899790979197929793979497959796979797989799980098019802980398049805980698079808980998109811981298139814981598169817981898199820982198229823982498259826982798289829983098319832983398349835983698379838983998409841984298439844984598469847984898499850985198529853985498559856985798589859986098619862986398649865986698679868986998709871987298739874987598769877987898799880988198829883988498859886988798889889989098919892989398949895989698979898989999009901990299039904990599069907990899099910991199129913991499159916991799189919992099219922992399249925992699279928992999309931993299339934993599369937993899399940994199429943994499459946994799489949995099519952995399549955995699579958995999609961996299639964996599669967996899699970997199729973997499759976997799789979998099819982998399849985998699879988998999909991999299939994999599969997999899991000010001100021000310004100051000610007100081000910010100111001210013100141001510016100171001810019100201002110022100231002410025100261002710028100291003010031100321003310034100351003610037100381003910040100411004210043100441004510046100471004810049100501005110052100531005410055100561005710058100591006010061100621006310064100651006610067100681006910070100711007210073100741007510076100771007810079100801008110082100831008410085100861008710088100891009010091100921009310094100951009610097100981009910100101011010210103101041010510106101071010810109101101011110112101131011410115101161011710118101191012010121101221012310124101251012610127101281012910130101311013210133101341013510136101371013810139101401014110142101431014410145101461014710148101491015010151101521015310154101551015610157101581015910160101611016210163101641016510166101671016810169101701017110172101731017410175101761017710178101791018010181101821018310184101851018610187101881018910190101911019210193101941019510196101971019810199102001020110202102031020410205102061020710208102091021010211102121021310214102151021610217102181021910220102211022210223102241022510226102271022810229102301023110232102331023410235102361023710238102391024010241102421024310244102451024610247102481024910250102511025210253102541025510256102571025810259102601026110262102631026410265102661026710268102691027010271102721027310274102751027610277102781027910280102811028210283102841028510286102871028810289102901029110292102931029410295102961029710298102991030010301103021030310304103051030610307103081030910310103111031210313103141031510316103171031810319103201032110322103231032410325103261032710328103291033010331103321033310334103351033610337103381033910340103411034210343103441034510346103471034810349103501035110352103531035410355103561035710358103591036010361103621036310364103651036610367103681036910370103711037210373103741037510376103771037810379103801038110382103831038410385103861038710388103891039010391103921039310394103951039610397103981039910400104011040210403104041040510406104071040810409104101041110412104131041410415104161041710418104191042010421104221042310424104251042610427104281042910430104311043210433104341043510436104371043810439104401044110442104431044410445104461044710448104491045010451104521045310454104551045610457104581045910460104611046210463104641046510466104671046810469104701047110472104731047410475104761047710478104791048010481104821048310484104851048610487104881048910490104911049210493104941049510496104971049810499105001050110502105031050410505105061050710508105091051010511105121051310514105151051610517105181051910520105211052210523105241052510526105271052810529105301053110532105331053410535105361053710538105391054010541105421054310544105451054610547105481054910550105511055210553105541055510556105571055810559105601056110562105631056410565105661056710568105691057010571105721057310574105751057610577105781057910580105811058210583105841058510586105871058810589105901059110592105931059410595105961059710598105991060010601106021060310604106051060610607106081060910610106111061210613106141061510616106171061810619106201062110622106231062410625106261062710628106291063010631106321063310634106351063610637106381063910640106411064210643106441064510646106471064810649106501065110652106531065410655106561065710658106591066010661106621066310664106651066610667106681066910670106711067210673106741067510676106771067810679106801068110682106831068410685106861068710688106891069010691106921069310694106951069610697106981069910700107011070210703107041070510706107071070810709107101071110712107131071410715107161071710718107191072010721107221072310724107251072610727107281072910730107311073210733107341073510736107371073810739107401074110742107431074410745107461074710748107491075010751107521075310754107551075610757107581075910760107611076210763107641076510766107671076810769107701077110772107731077410775107761077710778107791078010781107821078310784107851078610787107881078910790107911079210793107941079510796107971079810799108001080110802108031080410805108061080710808108091081010811108121081310814108151081610817108181081910820108211082210823108241082510826108271082810829108301083110832108331083410835108361083710838108391084010841108421084310844108451084610847108481084910850108511085210853108541085510856108571085810859108601086110862108631086410865108661086710868108691087010871108721087310874108751087610877108781087910880108811088210883108841088510886108871088810889108901089110892108931089410895108961089710898108991090010901109021090310904109051090610907109081090910910109111091210913109141091510916109171091810919109201092110922109231092410925109261092710928109291093010931109321093310934109351093610937109381093910940109411094210943109441094510946109471094810949109501095110952109531095410955109561095710958109591096010961109621096310964109651096610967109681096910970109711097210973109741097510976109771097810979109801098110982109831098410985109861098710988109891099010991109921099310994109951099610997109981099911000110011100211003110041100511006110071100811009110101101111012110131101411015110161101711018110191102011021110221102311024110251102611027110281102911030110311103211033110341103511036110371103811039110401104111042110431104411045110461104711048110491105011051110521105311054110551105611057110581105911060110611106211063110641106511066110671106811069110701107111072110731107411075110761107711078110791108011081110821108311084110851108611087110881108911090110911109211093110941109511096110971109811099111001110111102111031110411105111061110711108111091111011111111121111311114111151111611117111181111911120111211112211123111241112511126111271112811129111301113111132111331113411135111361113711138111391114011141111421114311144111451114611147111481114911150111511115211153111541115511156111571115811159111601116111162111631116411165111661116711168111691117011171111721117311174111751117611177111781117911180111811118211183111841118511186111871118811189111901119111192111931119411195111961119711198111991120011201112021120311204112051120611207112081120911210112111121211213112141121511216112171121811219112201122111222112231122411225112261122711228112291123011231112321123311234112351123611237112381123911240112411124211243112441124511246112471124811249112501125111252112531125411255112561125711258112591126011261112621126311264112651126611267112681126911270112711127211273112741127511276112771127811279112801128111282112831128411285112861128711288112891129011291112921129311294112951129611297112981129911300113011130211303113041130511306113071130811309113101131111312113131131411315113161131711318113191132011321113221132311324113251132611327113281132911330113311133211333113341133511336113371133811339113401134111342113431134411345113461134711348113491135011351113521135311354113551135611357113581135911360113611136211363113641136511366113671136811369113701137111372113731137411375113761137711378113791138011381113821138311384113851138611387113881138911390113911139211393113941139511396113971139811399114001140111402114031140411405114061140711408114091141011411114121141311414114151141611417114181141911420114211142211423114241142511426114271142811429114301143111432114331143411435114361143711438114391144011441114421144311444114451144611447114481144911450114511145211453114541145511456114571145811459114601146111462114631146411465114661146711468114691147011471114721147311474114751147611477114781147911480114811148211483114841148511486114871148811489114901149111492114931149411495114961149711498114991150011501115021150311504115051150611507115081150911510115111151211513115141151511516115171151811519115201152111522115231152411525115261152711528115291153011531115321153311534115351153611537115381153911540115411154211543115441154511546115471154811549115501155111552115531155411555115561155711558115591156011561115621156311564115651156611567115681156911570115711157211573115741157511576115771157811579115801158111582115831158411585115861158711588115891159011591115921159311594115951159611597115981159911600116011160211603116041160511606116071160811609116101161111612116131161411615116161161711618116191162011621116221162311624116251162611627116281162911630116311163211633116341163511636116371163811639116401164111642116431164411645116461164711648116491165011651116521165311654116551165611657116581165911660116611166211663116641166511666116671166811669116701167111672116731167411675116761167711678116791168011681116821168311684116851168611687116881168911690116911169211693116941169511696116971169811699117001170111702117031170411705117061170711708117091171011711117121171311714117151171611717117181171911720117211172211723117241172511726117271172811729117301173111732117331173411735117361173711738117391174011741117421174311744117451174611747117481174911750117511175211753117541175511756117571175811759117601176111762117631176411765117661176711768117691177011771117721177311774117751177611777117781177911780117811178211783117841178511786117871178811789117901179111792117931179411795117961179711798117991180011801118021180311804118051180611807118081180911810118111181211813118141181511816118171181811819118201182111822118231182411825118261182711828118291183011831118321183311834118351183611837118381183911840118411184211843118441184511846118471184811849118501185111852118531185411855118561185711858118591186011861118621186311864118651186611867118681186911870118711187211873118741187511876118771187811879118801188111882118831188411885118861188711888118891189011891118921189311894118951189611897118981189911900119011190211903119041190511906119071190811909119101191111912119131191411915119161191711918119191192011921119221192311924119251192611927119281192911930119311193211933119341193511936119371193811939119401194111942119431194411945119461194711948119491195011951119521195311954119551195611957119581195911960119611196211963119641196511966119671196811969119701197111972119731197411975119761197711978119791198011981119821198311984119851198611987119881198911990119911199211993119941199511996119971199811999120001200112002120031200412005120061200712008120091201012011120121201312014120151201612017120181201912020120211202212023120241202512026120271202812029120301203112032120331203412035120361203712038120391204012041120421204312044120451204612047120481204912050120511205212053120541205512056120571205812059120601206112062120631206412065120661206712068120691207012071120721207312074120751207612077120781207912080120811208212083120841208512086120871208812089120901209112092120931209412095120961209712098120991210012101121021210312104121051210612107121081210912110121111211212113121141211512116121171211812119121201212112122121231212412125121261212712128121291213012131121321213312134121351213612137121381213912140121411214212143121441214512146121471214812149121501215112152121531215412155121561215712158121591216012161121621216312164121651216612167121681216912170121711217212173121741217512176121771217812179121801218112182121831218412185121861218712188121891219012191121921219312194121951219612197121981219912200122011220212203122041220512206122071220812209122101221112212122131221412215122161221712218122191222012221122221222312224122251222612227122281222912230122311223212233122341223512236122371223812239122401224112242122431224412245122461224712248122491225012251122521225312254122551225612257122581225912260122611226212263122641226512266122671226812269122701227112272122731227412275122761227712278122791228012281122821228312284122851228612287122881228912290122911229212293122941229512296122971229812299123001230112302123031230412305123061230712308123091231012311123121231312314123151231612317123181231912320123211232212323123241232512326123271232812329123301233112332123331233412335123361233712338123391234012341123421234312344123451234612347123481234912350123511235212353123541235512356123571235812359123601236112362123631236412365123661236712368123691237012371123721237312374123751237612377123781237912380123811238212383123841238512386123871238812389123901239112392123931239412395123961239712398123991240012401124021240312404124051240612407124081240912410124111241212413124141241512416124171241812419124201242112422124231242412425124261242712428124291243012431124321243312434124351243612437124381243912440124411244212443124441244512446124471244812449124501245112452124531245412455124561245712458124591246012461124621246312464124651246612467124681246912470124711247212473124741247512476124771247812479124801248112482124831248412485124861248712488124891249012491124921249312494124951249612497124981249912500125011250212503125041250512506125071250812509125101251112512125131251412515125161251712518125191252012521125221252312524125251252612527125281252912530125311253212533125341253512536125371253812539125401254112542125431254412545125461254712548125491255012551125521255312554125551255612557125581255912560125611256212563125641256512566125671256812569125701257112572125731257412575125761257712578125791258012581125821258312584125851258612587125881258912590125911259212593125941259512596125971259812599126001260112602126031260412605126061260712608126091261012611126121261312614126151261612617126181261912620126211262212623126241262512626126271262812629126301263112632126331263412635126361263712638126391264012641126421264312644126451264612647126481264912650126511265212653126541265512656126571265812659126601266112662126631266412665126661266712668126691267012671126721267312674126751267612677126781267912680126811268212683126841268512686126871268812689126901269112692126931269412695126961269712698126991270012701127021270312704127051270612707127081270912710127111271212713127141271512716127171271812719127201272112722127231272412725127261272712728127291273012731127321273312734127351273612737127381273912740127411274212743127441274512746127471274812749127501275112752127531275412755127561275712758127591276012761127621276312764127651276612767127681276912770127711277212773127741277512776127771277812779127801278112782127831278412785127861278712788127891279012791127921279312794127951279612797127981279912800128011280212803128041280512806128071280812809128101281112812128131281412815128161281712818128191282012821128221282312824128251282612827128281282912830128311283212833128341283512836128371283812839128401284112842128431284412845128461284712848128491285012851128521285312854128551285612857128581285912860128611286212863128641286512866128671286812869128701287112872128731287412875128761287712878128791288012881128821288312884128851288612887128881288912890128911289212893128941289512896128971289812899129001290112902129031290412905129061290712908129091291012911129121291312914129151291612917129181291912920129211292212923129241292512926129271292812929129301293112932129331293412935129361293712938129391294012941129421294312944129451294612947129481294912950129511295212953129541295512956129571295812959129601296112962129631296412965129661296712968129691297012971129721297312974129751297612977129781297912980129811298212983129841298512986129871298812989129901299112992129931299412995129961299712998129991300013001130021300313004130051300613007130081300913010130111301213013130141301513016130171301813019130201302113022130231302413025130261302713028130291303013031130321303313034130351303613037130381303913040130411304213043130441304513046130471304813049130501305113052130531305413055130561305713058130591306013061130621306313064130651306613067130681306913070130711307213073130741307513076130771307813079130801308113082130831308413085130861308713088130891309013091130921309313094130951309613097130981309913100131011310213103131041310513106131071310813109131101311113112131131311413115131161311713118131191312013121131221312313124131251312613127131281312913130131311313213133131341313513136131371313813139131401314113142131431314413145131461314713148131491315013151131521315313154131551315613157131581315913160131611316213163131641316513166131671316813169131701317113172131731317413175131761317713178131791318013181131821318313184131851318613187131881318913190131911319213193131941319513196131971319813199132001320113202132031320413205132061320713208132091321013211132121321313214132151321613217132181321913220132211322213223132241322513226132271322813229132301323113232132331323413235132361323713238132391324013241132421324313244132451324613247132481324913250132511325213253132541325513256132571325813259132601326113262132631326413265132661326713268132691327013271132721327313274132751327613277132781327913280132811328213283132841328513286132871328813289132901329113292132931329413295132961329713298132991330013301133021330313304133051330613307133081330913310133111331213313133141331513316133171331813319133201332113322133231332413325133261332713328133291333013331133321333313334133351333613337133381333913340133411334213343133441334513346133471334813349133501335113352133531335413355133561335713358133591336013361133621336313364133651336613367133681336913370133711337213373133741337513376133771337813379133801338113382133831338413385133861338713388133891339013391133921339313394133951339613397133981339913400134011340213403134041340513406134071340813409134101341113412134131341413415134161341713418134191342013421134221342313424134251342613427134281342913430134311343213433134341343513436134371343813439134401344113442134431344413445134461344713448134491345013451134521345313454134551345613457134581345913460134611346213463134641346513466134671346813469134701347113472134731347413475134761347713478134791348013481134821348313484134851348613487134881348913490134911349213493134941349513496134971349813499135001350113502135031350413505135061350713508135091351013511135121351313514135151351613517135181351913520135211352213523135241352513526135271352813529135301353113532135331353413535135361353713538135391354013541135421354313544135451354613547135481354913550135511355213553135541355513556135571355813559135601356113562135631356413565135661356713568135691357013571135721357313574135751357613577135781357913580135811358213583135841358513586135871358813589135901359113592135931359413595135961359713598135991360013601136021360313604136051360613607136081360913610136111361213613136141361513616136171361813619136201362113622136231362413625136261362713628136291363013631136321363313634136351363613637136381363913640136411364213643136441364513646136471364813649136501365113652136531365413655136561365713658136591366013661136621366313664136651366613667136681366913670136711367213673136741367513676136771367813679136801368113682136831368413685136861368713688136891369013691136921369313694136951369613697136981369913700137011370213703137041370513706137071370813709137101371113712137131371413715137161371713718137191372013721137221372313724137251372613727137281372913730137311373213733137341373513736137371373813739137401374113742137431374413745137461374713748137491375013751137521375313754137551375613757137581375913760137611376213763137641376513766137671376813769137701377113772137731377413775137761377713778137791378013781137821378313784137851378613787137881378913790137911379213793137941379513796137971379813799138001380113802138031380413805138061380713808138091381013811138121381313814138151381613817138181381913820138211382213823138241382513826138271382813829138301383113832138331383413835138361383713838138391384013841138421384313844138451384613847138481384913850138511385213853138541385513856138571385813859138601386113862138631386413865138661386713868138691387013871138721387313874138751387613877138781387913880138811388213883138841388513886138871388813889138901389113892138931389413895138961389713898138991390013901139021390313904139051390613907139081390913910139111391213913139141391513916139171391813919139201392113922139231392413925139261392713928139291393013931139321393313934139351393613937139381393913940139411394213943139441394513946139471394813949139501395113952139531395413955139561395713958139591396013961139621396313964139651396613967139681396913970139711397213973139741397513976139771397813979139801398113982139831398413985139861398713988139891399013991139921399313994139951399613997139981399914000140011400214003140041400514006140071400814009140101401114012140131401414015140161401714018140191402014021140221402314024140251402614027140281402914030140311403214033140341403514036140371403814039140401404114042140431404414045140461404714048140491405014051140521405314054140551405614057140581405914060140611406214063140641406514066140671406814069140701407114072140731407414075140761407714078140791408014081140821408314084140851408614087140881408914090140911409214093140941409514096140971409814099141001410114102141031410414105141061410714108141091411014111141121411314114141151411614117141181411914120141211412214123141241412514126141271412814129141301413114132141331413414135141361413714138141391414014141141421414314144141451414614147141481414914150141511415214153141541415514156141571415814159141601416114162141631416414165141661416714168141691417014171141721417314174141751417614177141781417914180141811418214183141841418514186141871418814189141901419114192141931419414195141961419714198141991420014201142021420314204142051420614207142081420914210142111421214213142141421514216142171421814219142201422114222142231422414225142261422714228142291423014231142321423314234142351423614237142381423914240142411424214243142441424514246142471424814249142501425114252142531425414255142561425714258142591426014261142621426314264142651426614267142681426914270142711427214273142741427514276142771427814279142801428114282142831428414285142861428714288142891429014291142921429314294142951429614297142981429914300143011430214303143041430514306143071430814309143101431114312143131431414315143161431714318143191432014321143221432314324143251432614327143281432914330143311433214333143341433514336143371433814339143401434114342143431434414345143461434714348143491435014351143521435314354143551435614357143581435914360143611436214363143641436514366143671436814369143701437114372143731437414375143761437714378143791438014381143821438314384143851438614387143881438914390143911439214393143941439514396143971439814399144001440114402144031440414405144061440714408144091441014411144121441314414144151441614417144181441914420144211442214423144241442514426144271442814429144301443114432144331443414435144361443714438144391444014441144421444314444144451444614447144481444914450144511445214453144541445514456144571445814459144601446114462144631446414465144661446714468144691447014471144721447314474144751447614477144781447914480144811448214483144841448514486144871448814489144901449114492144931449414495144961449714498144991450014501145021450314504145051450614507145081450914510145111451214513145141451514516145171451814519145201452114522145231452414525145261452714528145291453014531145321453314534145351453614537145381453914540145411454214543145441454514546145471454814549145501455114552145531455414555145561455714558145591456014561145621456314564145651456614567145681456914570145711457214573145741457514576145771457814579145801458114582145831458414585145861458714588145891459014591145921459314594145951459614597145981459914600146011460214603146041460514606146071460814609146101461114612146131461414615146161461714618146191462014621146221462314624146251462614627146281462914630146311463214633146341463514636146371463814639146401464114642146431464414645146461464714648146491465014651146521465314654146551465614657146581465914660146611466214663146641466514666146671466814669146701467114672146731467414675146761467714678146791468014681146821468314684146851468614687146881468914690146911469214693146941469514696146971469814699147001470114702147031470414705147061470714708147091471014711147121471314714147151471614717147181471914720147211472214723147241472514726147271472814729147301473114732147331473414735147361473714738147391474014741147421474314744147451474614747147481474914750147511475214753147541475514756147571475814759147601476114762147631476414765147661476714768147691477014771147721477314774147751477614777147781477914780147811478214783147841478514786147871478814789147901479114792147931479414795147961479714798147991480014801148021480314804148051480614807148081480914810148111481214813148141481514816148171481814819148201482114822148231482414825148261482714828148291483014831148321483314834148351483614837148381483914840148411484214843148441484514846148471484814849148501485114852148531485414855148561485714858148591486014861148621486314864148651486614867148681486914870148711487214873148741487514876148771487814879148801488114882148831488414885148861488714888148891489014891148921489314894148951489614897148981489914900149011490214903149041490514906149071490814909149101491114912149131491414915149161491714918149191492014921149221492314924149251492614927149281492914930149311493214933149341493514936149371493814939149401494114942149431494414945149461494714948149491495014951149521495314954149551495614957149581495914960149611496214963149641496514966149671496814969149701497114972149731497414975149761497714978149791498014981149821498314984149851498614987149881498914990149911499214993149941499514996149971499814999150001500115002150031500415005150061500715008150091501015011150121501315014150151501615017150181501915020150211502215023150241502515026150271502815029150301503115032150331503415035150361503715038150391504015041150421504315044150451504615047150481504915050150511505215053150541505515056150571505815059150601506115062150631506415065150661506715068150691507015071150721507315074150751507615077150781507915080150811508215083150841508515086150871508815089150901509115092150931509415095150961509715098150991510015101151021510315104151051510615107151081510915110151111511215113151141511515116151171511815119151201512115122151231512415125151261512715128151291513015131151321513315134151351513615137151381513915140151411514215143151441514515146151471514815149151501515115152151531515415155151561515715158151591516015161151621516315164151651516615167151681516915170151711517215173151741517515176151771517815179151801518115182151831518415185151861518715188151891519015191151921519315194151951519615197151981519915200152011520215203152041520515206152071520815209152101521115212152131521415215152161521715218152191522015221152221522315224152251522615227152281522915230152311523215233152341523515236152371523815239152401524115242152431524415245152461524715248152491525015251152521525315254152551525615257152581525915260152611526215263152641526515266152671526815269152701527115272152731527415275152761527715278152791528015281152821528315284152851528615287152881528915290152911529215293152941529515296152971529815299153001530115302153031530415305153061530715308153091531015311153121531315314153151531615317153181531915320153211532215323153241532515326153271532815329153301533115332153331533415335153361533715338153391534015341153421534315344153451534615347153481534915350153511535215353153541535515356153571535815359153601536115362153631536415365153661536715368153691537015371153721537315374153751537615377153781537915380153811538215383153841538515386153871538815389153901539115392153931539415395153961539715398153991540015401154021540315404154051540615407154081540915410154111541215413154141541515416154171541815419154201542115422154231542415425154261542715428154291543015431154321543315434154351543615437154381543915440154411544215443154441544515446154471544815449154501545115452154531545415455154561545715458154591546015461154621546315464154651546615467154681546915470154711547215473154741547515476154771547815479154801548115482154831548415485154861548715488154891549015491154921549315494154951549615497154981549915500155011550215503155041550515506155071550815509155101551115512155131551415515155161551715518155191552015521155221552315524155251552615527155281552915530155311553215533155341553515536155371553815539155401554115542155431554415545155461554715548155491555015551155521555315554155551555615557155581555915560155611556215563155641556515566155671556815569155701557115572155731557415575155761557715578155791558015581155821558315584155851558615587155881558915590155911559215593155941559515596155971559815599156001560115602156031560415605156061560715608156091561015611156121561315614156151561615617156181561915620156211562215623156241562515626156271562815629156301563115632156331563415635156361563715638156391564015641156421564315644156451564615647156481564915650156511565215653156541565515656156571565815659156601566115662156631566415665156661566715668156691567015671156721567315674156751567615677156781567915680156811568215683156841568515686156871568815689156901569115692156931569415695156961569715698156991570015701157021570315704157051570615707157081570915710157111571215713157141571515716157171571815719157201572115722157231572415725157261572715728157291573015731157321573315734157351573615737157381573915740157411574215743157441574515746157471574815749157501575115752157531575415755157561575715758157591576015761157621576315764157651576615767157681576915770157711577215773157741577515776157771577815779157801578115782157831578415785157861578715788157891579015791157921579315794157951579615797157981579915800158011580215803158041580515806158071580815809158101581115812158131581415815158161581715818158191582015821158221582315824158251582615827158281582915830158311583215833158341583515836158371583815839158401584115842158431584415845158461584715848158491585015851158521585315854158551585615857158581585915860158611586215863158641586515866158671586815869158701587115872158731587415875158761587715878158791588015881158821588315884158851588615887158881588915890158911589215893158941589515896158971589815899159001590115902159031590415905159061590715908159091591015911159121591315914159151591615917159181591915920159211592215923159241592515926159271592815929159301593115932159331593415935159361593715938159391594015941159421594315944159451594615947159481594915950159511595215953159541595515956159571595815959159601596115962159631596415965159661596715968159691597015971159721597315974159751597615977159781597915980159811598215983159841598515986159871598815989159901599115992159931599415995159961599715998159991600016001160021600316004160051600616007160081600916010160111601216013160141601516016160171601816019160201602116022160231602416025160261602716028160291603016031160321603316034160351603616037160381603916040160411604216043160441604516046160471604816049160501605116052160531605416055160561605716058160591606016061160621606316064160651606616067160681606916070160711607216073160741607516076160771607816079160801608116082160831608416085160861608716088160891609016091160921609316094160951609616097160981609916100161011610216103161041610516106161071610816109161101611116112161131611416115161161611716118161191612016121161221612316124161251612616127161281612916130161311613216133161341613516136161371613816139161401614116142161431614416145161461614716148161491615016151161521615316154161551615616157161581615916160161611616216163161641616516166161671616816169161701617116172161731617416175161761617716178161791618016181161821618316184161851618616187161881618916190161911619216193161941619516196161971619816199162001620116202162031620416205162061620716208162091621016211162121621316214162151621616217162181621916220162211622216223162241622516226162271622816229162301623116232162331623416235162361623716238162391624016241162421624316244162451624616247162481624916250162511625216253162541625516256162571625816259162601626116262162631626416265162661626716268162691627016271162721627316274162751627616277162781627916280162811628216283162841628516286162871628816289162901629116292162931629416295162961629716298162991630016301163021630316304163051630616307163081630916310163111631216313163141631516316163171631816319163201632116322163231632416325163261632716328163291633016331163321633316334163351633616337163381633916340163411634216343163441634516346163471634816349163501635116352163531635416355163561635716358163591636016361163621636316364163651636616367163681636916370163711637216373163741637516376163771637816379163801638116382163831638416385163861638716388163891639016391163921639316394163951639616397163981639916400164011640216403164041640516406164071640816409164101641116412164131641416415164161641716418164191642016421164221642316424164251642616427164281642916430164311643216433164341643516436164371643816439164401644116442164431644416445164461644716448164491645016451164521645316454164551645616457164581645916460164611646216463164641646516466164671646816469164701647116472164731647416475164761647716478164791648016481164821648316484164851648616487164881648916490164911649216493164941649516496164971649816499165001650116502165031650416505165061650716508165091651016511165121651316514165151651616517165181651916520165211652216523165241652516526165271652816529165301653116532165331653416535165361653716538165391654016541165421654316544165451654616547165481654916550165511655216553165541655516556165571655816559165601656116562165631656416565165661656716568165691657016571165721657316574165751657616577165781657916580165811658216583165841658516586165871658816589165901659116592165931659416595165961659716598165991660016601166021660316604166051660616607166081660916610166111661216613166141661516616166171661816619166201662116622166231662416625166261662716628166291663016631166321663316634166351663616637166381663916640166411664216643166441664516646166471664816649166501665116652166531665416655166561665716658166591666016661166621666316664166651666616667166681666916670166711667216673166741667516676166771667816679166801668116682166831668416685166861668716688166891669016691166921669316694166951669616697166981669916700167011670216703167041670516706167071670816709167101671116712167131671416715167161671716718167191672016721167221672316724167251672616727167281672916730167311673216733167341673516736167371673816739167401674116742167431674416745167461674716748167491675016751167521675316754167551675616757167581675916760167611676216763167641676516766167671676816769167701677116772167731677416775167761677716778167791678016781167821678316784167851678616787167881678916790167911679216793167941679516796167971679816799168001680116802168031680416805168061680716808168091681016811168121681316814168151681616817168181681916820168211682216823168241682516826168271682816829168301683116832168331683416835168361683716838168391684016841168421684316844168451684616847168481684916850168511685216853168541685516856168571685816859168601686116862168631686416865168661686716868168691687016871168721687316874168751687616877168781687916880168811688216883168841688516886168871688816889168901689116892168931689416895168961689716898168991690016901169021690316904169051690616907169081690916910169111691216913169141691516916169171691816919169201692116922169231692416925169261692716928169291693016931169321693316934169351693616937169381693916940169411694216943169441694516946169471694816949169501695116952169531695416955169561695716958169591696016961169621696316964169651696616967169681696916970169711697216973169741697516976169771697816979169801698116982169831698416985169861698716988169891699016991169921699316994169951699616997169981699917000170011700217003170041700517006170071700817009170101701117012170131701417015170161701717018170191702017021170221702317024170251702617027170281702917030170311703217033170341703517036170371703817039170401704117042170431704417045170461704717048170491705017051170521705317054170551705617057170581705917060170611706217063170641706517066170671706817069170701707117072170731707417075170761707717078170791708017081170821708317084170851708617087170881708917090170911709217093170941709517096170971709817099171001710117102171031710417105171061710717108171091711017111171121711317114171151711617117171181711917120171211712217123171241712517126171271712817129171301713117132171331713417135171361713717138171391714017141171421714317144171451714617147171481714917150171511715217153171541715517156171571715817159171601716117162171631716417165171661716717168171691717017171171721717317174171751717617177171781717917180171811718217183171841718517186171871718817189171901719117192171931719417195171961719717198171991720017201172021720317204172051720617207172081720917210172111721217213172141721517216172171721817219172201722117222172231722417225172261722717228172291723017231172321723317234172351723617237172381723917240172411724217243172441724517246172471724817249172501725117252172531725417255172561725717258172591726017261172621726317264172651726617267172681726917270172711727217273172741727517276172771727817279172801728117282172831728417285172861728717288172891729017291172921729317294172951729617297172981729917300173011730217303173041730517306173071730817309173101731117312173131731417315173161731717318173191732017321173221732317324173251732617327173281732917330173311733217333173341733517336173371733817339173401734117342173431734417345173461734717348173491735017351173521735317354173551735617357173581735917360173611736217363173641736517366173671736817369173701737117372173731737417375173761737717378173791738017381173821738317384173851738617387173881738917390173911739217393173941739517396173971739817399174001740117402174031740417405174061740717408174091741017411174121741317414174151741617417174181741917420174211742217423174241742517426174271742817429174301743117432174331743417435174361743717438174391744017441174421744317444174451744617447174481744917450174511745217453174541745517456174571745817459174601746117462174631746417465174661746717468174691747017471174721747317474174751747617477174781747917480174811748217483174841748517486174871748817489174901749117492174931749417495174961749717498174991750017501175021750317504175051750617507175081750917510175111751217513175141751517516175171751817519175201752117522175231752417525175261752717528175291753017531175321753317534175351753617537175381753917540175411754217543175441754517546175471754817549175501755117552175531755417555175561755717558175591756017561175621756317564175651756617567175681756917570175711757217573175741757517576175771757817579175801758117582175831758417585175861758717588175891759017591175921759317594175951759617597175981759917600176011760217603176041760517606176071760817609176101761117612176131761417615176161761717618176191762017621176221762317624176251762617627176281762917630176311763217633176341763517636176371763817639176401764117642176431764417645176461764717648176491765017651176521765317654176551765617657176581765917660176611766217663176641766517666176671766817669176701767117672176731767417675176761767717678176791768017681176821768317684176851768617687176881768917690176911769217693176941769517696176971769817699177001770117702177031770417705177061770717708177091771017711177121771317714177151771617717177181771917720177211772217723177241772517726177271772817729177301773117732177331773417735177361773717738177391774017741177421774317744177451774617747177481774917750177511775217753177541775517756177571775817759177601776117762177631776417765177661776717768177691777017771177721777317774177751777617777177781777917780177811778217783177841778517786177871778817789177901779117792177931779417795177961779717798177991780017801178021780317804178051780617807178081780917810178111781217813178141781517816178171781817819178201782117822178231782417825178261782717828178291783017831178321783317834178351783617837178381783917840178411784217843178441784517846178471784817849178501785117852178531785417855178561785717858178591786017861178621786317864178651786617867178681786917870178711787217873178741787517876178771787817879178801788117882178831788417885178861788717888178891789017891178921789317894178951789617897178981789917900179011790217903179041790517906179071790817909179101791117912179131791417915179161791717918179191792017921179221792317924179251792617927179281792917930179311793217933179341793517936179371793817939179401794117942179431794417945179461794717948179491795017951179521795317954179551795617957179581795917960179611796217963179641796517966179671796817969179701797117972179731797417975179761797717978179791798017981179821798317984179851798617987179881798917990179911799217993179941799517996179971799817999180001800118002180031800418005180061800718008180091801018011180121801318014180151801618017180181801918020180211802218023180241802518026180271802818029180301803118032180331803418035180361803718038180391804018041180421804318044180451804618047180481804918050180511805218053180541805518056180571805818059180601806118062180631806418065180661806718068180691807018071180721807318074180751807618077180781807918080180811808218083180841808518086180871808818089180901809118092180931809418095180961809718098180991810018101181021810318104181051810618107181081810918110181111811218113181141811518116181171811818119181201812118122181231812418125181261812718128181291813018131181321813318134181351813618137181381813918140181411814218143181441814518146181471814818149181501815118152181531815418155181561815718158181591816018161181621816318164181651816618167181681816918170181711817218173181741817518176181771817818179181801818118182181831818418185181861818718188181891819018191181921819318194181951819618197181981819918200182011820218203182041820518206182071820818209182101821118212182131821418215182161821718218182191822018221182221822318224182251822618227182281822918230182311823218233182341823518236182371823818239182401824118242182431824418245182461824718248182491825018251182521825318254182551825618257182581825918260182611826218263182641826518266182671826818269182701827118272182731827418275182761827718278182791828018281182821828318284182851828618287182881828918290182911829218293182941829518296182971829818299183001830118302183031830418305183061830718308183091831018311183121831318314183151831618317183181831918320183211832218323183241832518326183271832818329183301833118332183331833418335183361833718338183391834018341183421834318344183451834618347183481834918350183511835218353183541835518356183571835818359183601836118362183631836418365183661836718368183691837018371183721837318374183751837618377183781837918380183811838218383183841838518386183871838818389183901839118392183931839418395183961839718398183991840018401184021840318404184051840618407184081840918410184111841218413184141841518416184171841818419184201842118422184231842418425184261842718428184291843018431184321843318434184351843618437184381843918440184411844218443184441844518446184471844818449184501845118452184531845418455184561845718458184591846018461184621846318464184651846618467184681846918470184711847218473184741847518476184771847818479184801848118482184831848418485184861848718488184891849018491184921849318494184951849618497184981849918500185011850218503185041850518506185071850818509185101851118512185131851418515185161851718518185191852018521185221852318524185251852618527185281852918530185311853218533185341853518536185371853818539185401854118542185431854418545185461854718548185491855018551185521855318554185551855618557185581855918560185611856218563185641856518566185671856818569185701857118572185731857418575185761857718578185791858018581185821858318584185851858618587185881858918590185911859218593185941859518596185971859818599186001860118602186031860418605186061860718608186091861018611186121861318614186151861618617186181861918620186211862218623186241862518626186271862818629186301863118632186331863418635186361863718638186391864018641186421864318644186451864618647186481864918650186511865218653186541865518656186571865818659186601866118662186631866418665186661866718668186691867018671186721867318674186751867618677186781867918680186811868218683186841868518686186871868818689186901869118692186931869418695186961869718698186991870018701187021870318704187051870618707187081870918710187111871218713187141871518716187171871818719187201872118722187231872418725187261872718728187291873018731187321873318734187351873618737187381873918740187411874218743187441874518746187471874818749187501875118752187531875418755187561875718758187591876018761187621876318764187651876618767187681876918770187711877218773187741877518776187771877818779187801878118782187831878418785187861878718788187891879018791187921879318794187951879618797187981879918800188011880218803188041880518806188071880818809188101881118812188131881418815188161881718818188191882018821188221882318824188251882618827188281882918830188311883218833188341883518836188371883818839188401884118842188431884418845188461884718848188491885018851188521885318854188551885618857188581885918860188611886218863188641886518866188671886818869188701887118872188731887418875188761887718878188791888018881188821888318884188851888618887188881888918890188911889218893188941889518896188971889818899189001890118902189031890418905189061890718908189091891018911189121891318914189151891618917189181891918920189211892218923189241892518926189271892818929189301893118932189331893418935189361893718938189391894018941189421894318944189451894618947189481894918950189511895218953189541895518956189571895818959189601896118962189631896418965189661896718968189691897018971189721897318974189751897618977189781897918980189811898218983189841898518986189871898818989189901899118992189931899418995189961899718998189991900019001190021900319004190051900619007190081900919010190111901219013190141901519016190171901819019190201902119022190231902419025190261902719028190291903019031190321903319034190351903619037190381903919040190411904219043190441904519046190471904819049190501905119052190531905419055190561905719058190591906019061190621906319064190651906619067190681906919070190711907219073190741907519076190771907819079190801908119082190831908419085190861908719088190891909019091190921909319094190951909619097190981909919100191011910219103191041910519106191071910819109191101911119112191131911419115191161911719118191191912019121191221912319124191251912619127191281912919130191311913219133191341913519136191371913819139191401914119142191431914419145191461914719148191491915019151191521915319154191551915619157191581915919160191611916219163191641916519166191671916819169191701917119172191731917419175191761917719178191791918019181191821918319184191851918619187191881918919190191911919219193191941919519196191971919819199192001920119202192031920419205192061920719208192091921019211192121921319214192151921619217192181921919220192211922219223192241922519226192271922819229192301923119232192331923419235192361923719238192391924019241192421924319244192451924619247192481924919250192511925219253192541925519256192571925819259192601926119262192631926419265192661926719268192691927019271192721927319274192751927619277192781927919280192811928219283192841928519286192871928819289192901929119292192931929419295192961929719298192991930019301193021930319304193051930619307193081930919310193111931219313193141931519316193171931819319193201932119322193231932419325193261932719328193291933019331193321933319334193351933619337193381933919340193411934219343193441934519346193471934819349193501935119352193531935419355193561935719358193591936019361193621936319364193651936619367193681936919370193711937219373193741937519376193771937819379193801938119382193831938419385193861938719388193891939019391193921939319394193951939619397193981939919400194011940219403194041940519406194071940819409194101941119412194131941419415194161941719418194191942019421194221942319424194251942619427194281942919430194311943219433194341943519436194371943819439194401944119442194431944419445194461944719448194491945019451194521945319454194551945619457194581945919460194611946219463194641946519466194671946819469194701947119472194731947419475194761947719478194791948019481194821948319484194851948619487194881948919490194911949219493194941949519496194971949819499195001950119502195031950419505195061950719508195091951019511195121951319514195151951619517195181951919520195211952219523195241952519526195271952819529195301953119532195331953419535195361953719538195391954019541195421954319544195451954619547195481954919550195511955219553195541955519556195571955819559195601956119562195631956419565195661956719568195691957019571195721957319574195751957619577195781957919580195811958219583195841958519586195871958819589195901959119592195931959419595195961959719598195991960019601196021960319604196051960619607196081960919610196111961219613196141961519616196171961819619196201962119622196231962419625196261962719628196291963019631196321963319634196351963619637196381963919640196411964219643196441964519646196471964819649196501965119652196531965419655196561965719658196591966019661196621966319664196651966619667196681966919670196711967219673196741967519676196771967819679196801968119682196831968419685196861968719688196891969019691196921969319694196951969619697196981969919700197011970219703197041970519706197071970819709197101971119712197131971419715197161971719718197191972019721197221972319724197251972619727197281972919730197311973219733197341973519736197371973819739197401974119742197431974419745197461974719748197491975019751197521975319754197551975619757197581975919760197611976219763197641976519766197671976819769197701977119772197731977419775197761977719778197791978019781197821978319784197851978619787197881978919790197911979219793197941979519796197971979819799198001980119802198031980419805198061980719808198091981019811198121981319814198151981619817198181981919820198211982219823198241982519826198271982819829198301983119832198331983419835198361983719838198391984019841198421984319844198451984619847198481984919850198511985219853198541985519856198571985819859198601986119862198631986419865198661986719868198691987019871198721987319874198751987619877198781987919880198811988219883198841988519886198871988819889198901989119892198931989419895198961989719898198991990019901199021990319904199051990619907199081990919910199111991219913199141991519916199171991819919199201992119922199231992419925199261992719928199291993019931199321993319934199351993619937199381993919940199411994219943199441994519946199471994819949199501995119952199531995419955199561995719958199591996019961199621996319964199651996619967199681996919970199711997219973199741997519976199771997819979199801998119982199831998419985199861998719988199891999019991199921999319994199951999619997199981999920000200012000220003200042000520006200072000820009200102001120012200132001420015200162001720018200192002020021200222002320024200252002620027200282002920030200312003220033200342003520036200372003820039200402004120042200432004420045200462004720048200492005020051200522005320054200552005620057200582005920060200612006220063200642006520066200672006820069200702007120072200732007420075200762007720078200792008020081200822008320084200852008620087200882008920090200912009220093200942009520096200972009820099201002010120102201032010420105201062010720108201092011020111201122011320114201152011620117201182011920120201212012220123201242012520126201272012820129201302013120132201332013420135201362013720138201392014020141201422014320144201452014620147201482014920150201512015220153201542015520156201572015820159201602016120162201632016420165201662016720168201692017020171201722017320174201752017620177201782017920180201812018220183201842018520186201872018820189201902019120192201932019420195201962019720198201992020020201202022020320204202052020620207202082020920210202112021220213202142021520216202172021820219202202022120222202232022420225202262022720228202292023020231202322023320234202352023620237202382023920240202412024220243202442024520246202472024820249202502025120252202532025420255202562025720258202592026020261202622026320264202652026620267202682026920270202712027220273202742027520276202772027820279202802028120282202832028420285202862028720288202892029020291202922029320294202952029620297202982029920300203012030220303203042030520306203072030820309203102031120312203132031420315203162031720318203192032020321203222032320324203252032620327203282032920330203312033220333203342033520336203372033820339203402034120342203432034420345203462034720348203492035020351203522035320354203552035620357203582035920360203612036220363203642036520366203672036820369203702037120372203732037420375203762037720378203792038020381203822038320384203852038620387203882038920390203912039220393203942039520396203972039820399204002040120402204032040420405204062040720408204092041020411204122041320414204152041620417204182041920420204212042220423204242042520426204272042820429204302043120432204332043420435204362043720438204392044020441204422044320444204452044620447204482044920450204512045220453204542045520456204572045820459204602046120462204632046420465204662046720468204692047020471204722047320474204752047620477204782047920480204812048220483204842048520486204872048820489204902049120492204932049420495204962049720498204992050020501205022050320504205052050620507205082050920510205112051220513205142051520516205172051820519205202052120522205232052420525205262052720528205292053020531205322053320534205352053620537205382053920540205412054220543205442054520546205472054820549205502055120552205532055420555205562055720558205592056020561205622056320564205652056620567205682056920570205712057220573205742057520576205772057820579205802058120582205832058420585205862058720588205892059020591205922059320594205952059620597205982059920600206012060220603206042060520606206072060820609206102061120612206132061420615206162061720618206192062020621206222062320624206252062620627206282062920630206312063220633206342063520636206372063820639206402064120642206432064420645206462064720648206492065020651206522065320654206552065620657206582065920660206612066220663206642066520666206672066820669206702067120672206732067420675206762067720678206792068020681206822068320684206852068620687206882068920690206912069220693206942069520696206972069820699207002070120702207032070420705207062070720708207092071020711207122071320714207152071620717207182071920720207212072220723207242072520726207272072820729207302073120732207332073420735207362073720738207392074020741207422074320744207452074620747207482074920750207512075220753207542075520756207572075820759207602076120762207632076420765207662076720768207692077020771207722077320774207752077620777207782077920780207812078220783207842078520786207872078820789207902079120792207932079420795207962079720798207992080020801208022080320804208052080620807208082080920810208112081220813208142081520816208172081820819208202082120822208232082420825208262082720828208292083020831208322083320834208352083620837208382083920840208412084220843208442084520846208472084820849208502085120852208532085420855208562085720858208592086020861208622086320864208652086620867208682086920870208712087220873208742087520876208772087820879208802088120882208832088420885208862088720888208892089020891208922089320894208952089620897208982089920900209012090220903209042090520906209072090820909209102091120912209132091420915209162091720918209192092020921209222092320924209252092620927209282092920930209312093220933209342093520936209372093820939209402094120942209432094420945209462094720948209492095020951209522095320954209552095620957209582095920960209612096220963209642096520966209672096820969209702097120972209732097420975209762097720978209792098020981209822098320984209852098620987209882098920990209912099220993209942099520996209972099820999210002100121002210032100421005210062100721008210092101021011210122101321014210152101621017210182101921020210212102221023210242102521026210272102821029210302103121032210332103421035210362103721038210392104021041210422104321044210452104621047210482104921050210512105221053210542105521056210572105821059210602106121062210632106421065210662106721068210692107021071210722107321074210752107621077210782107921080210812108221083210842108521086210872108821089210902109121092210932109421095210962109721098210992110021101211022110321104211052110621107211082110921110211112111221113211142111521116211172111821119211202112121122211232112421125211262112721128211292113021131211322113321134211352113621137211382113921140211412114221143211442114521146211472114821149211502115121152211532115421155211562115721158211592116021161211622116321164211652116621167211682116921170211712117221173211742117521176211772117821179211802118121182211832118421185211862118721188211892119021191211922119321194211952119621197211982119921200212012120221203212042120521206212072120821209212102121121212212132121421215212162121721218212192122021221212222122321224212252122621227212282122921230212312123221233212342123521236212372123821239212402124121242212432124421245212462124721248212492125021251212522125321254212552125621257212582125921260212612126221263212642126521266212672126821269212702127121272212732127421275212762127721278212792128021281212822128321284212852128621287212882128921290212912129221293212942129521296212972129821299213002130121302213032130421305213062130721308213092131021311213122131321314213152131621317213182131921320213212132221323213242132521326213272132821329213302133121332213332133421335213362133721338213392134021341213422134321344213452134621347213482134921350213512135221353213542135521356213572135821359213602136121362213632136421365213662136721368213692137021371213722137321374213752137621377213782137921380213812138221383213842138521386213872138821389213902139121392213932139421395213962139721398213992140021401214022140321404214052140621407214082140921410214112141221413214142141521416214172141821419214202142121422214232142421425214262142721428214292143021431214322143321434214352143621437214382143921440214412144221443214442144521446214472144821449214502145121452214532145421455214562145721458214592146021461214622146321464214652146621467214682146921470214712147221473214742147521476214772147821479214802148121482214832148421485214862148721488214892149021491214922149321494214952149621497214982149921500215012150221503215042150521506215072150821509215102151121512215132151421515215162151721518215192152021521215222152321524215252152621527215282152921530215312153221533215342153521536215372153821539215402154121542215432154421545215462154721548215492155021551215522155321554215552155621557215582155921560215612156221563215642156521566215672156821569215702157121572215732157421575215762157721578215792158021581215822158321584215852158621587215882158921590215912159221593215942159521596215972159821599216002160121602216032160421605216062160721608216092161021611216122161321614216152161621617216182161921620216212162221623216242162521626216272162821629216302163121632216332163421635216362163721638216392164021641216422164321644216452164621647216482164921650216512165221653216542165521656216572165821659216602166121662216632166421665216662166721668216692167021671216722167321674216752167621677216782167921680216812168221683216842168521686216872168821689216902169121692216932169421695216962169721698216992170021701217022170321704217052170621707217082170921710217112171221713217142171521716217172171821719217202172121722217232172421725217262172721728217292173021731217322173321734217352173621737217382173921740217412174221743217442174521746217472174821749217502175121752217532175421755217562175721758217592176021761217622176321764217652176621767217682176921770217712177221773217742177521776217772177821779217802178121782217832178421785217862178721788217892179021791217922179321794217952179621797217982179921800218012180221803218042180521806218072180821809218102181121812218132181421815218162181721818218192182021821218222182321824218252182621827218282182921830218312183221833218342183521836218372183821839218402184121842218432184421845218462184721848218492185021851218522185321854218552185621857218582185921860218612186221863218642186521866218672186821869218702187121872218732187421875218762187721878218792188021881218822188321884218852188621887218882188921890218912189221893218942189521896218972189821899219002190121902219032190421905219062190721908219092191021911219122191321914219152191621917219182191921920219212192221923219242192521926219272192821929219302193121932219332193421935219362193721938219392194021941219422194321944219452194621947219482194921950219512195221953219542195521956219572195821959219602196121962219632196421965219662196721968219692197021971219722197321974219752197621977219782197921980219812198221983219842198521986219872198821989219902199121992219932199421995219962199721998219992200022001220022200322004220052200622007220082200922010220112201222013220142201522016220172201822019220202202122022220232202422025220262202722028220292203022031220322203322034220352203622037220382203922040220412204222043220442204522046220472204822049220502205122052220532205422055220562205722058220592206022061220622206322064220652206622067220682206922070220712207222073220742207522076220772207822079220802208122082220832208422085220862208722088220892209022091220922209322094220952209622097220982209922100221012210222103221042210522106221072210822109221102211122112221132211422115221162211722118221192212022121221222212322124221252212622127221282212922130221312213222133221342213522136221372213822139221402214122142221432214422145221462214722148221492215022151221522215322154221552215622157221582215922160221612216222163221642216522166221672216822169221702217122172221732217422175221762217722178221792218022181221822218322184221852218622187221882218922190221912219222193221942219522196221972219822199222002220122202222032220422205222062220722208222092221022211222122221322214222152221622217222182221922220222212222222223222242222522226222272222822229222302223122232222332223422235222362223722238222392224022241222422224322244222452224622247222482224922250222512225222253222542225522256222572225822259222602226122262222632226422265222662226722268222692227022271222722227322274222752227622277222782227922280222812228222283222842228522286222872228822289222902229122292222932229422295222962229722298222992230022301223022230322304223052230622307223082230922310223112231222313223142231522316223172231822319223202232122322223232232422325223262232722328223292233022331223322233322334223352233622337223382233922340223412234222343223442234522346223472234822349223502235122352223532235422355223562235722358223592236022361223622236322364223652236622367223682236922370223712237222373223742237522376223772237822379223802238122382223832238422385223862238722388223892239022391223922239322394223952239622397223982239922400224012240222403224042240522406224072240822409224102241122412224132241422415224162241722418224192242022421224222242322424224252242622427224282242922430224312243222433224342243522436224372243822439224402244122442224432244422445224462244722448224492245022451224522245322454224552245622457224582245922460224612246222463224642246522466224672246822469224702247122472224732247422475224762247722478224792248022481224822248322484224852248622487224882248922490224912249222493224942249522496224972249822499225002250122502225032250422505225062250722508225092251022511225122251322514225152251622517225182251922520225212252222523225242252522526225272252822529225302253122532225332253422535225362253722538225392254022541225422254322544225452254622547225482254922550225512255222553225542255522556225572255822559225602256122562225632256422565225662256722568225692257022571225722257322574225752257622577225782257922580225812258222583225842258522586225872258822589225902259122592225932259422595225962259722598225992260022601226022260322604226052260622607226082260922610226112261222613226142261522616226172261822619226202262122622226232262422625226262262722628226292263022631226322263322634226352263622637226382263922640226412264222643226442264522646226472264822649226502265122652226532265422655226562265722658226592266022661226622266322664226652266622667226682266922670226712267222673226742267522676226772267822679226802268122682226832268422685226862268722688226892269022691226922269322694226952269622697226982269922700227012270222703227042270522706227072270822709227102271122712227132271422715227162271722718227192272022721227222272322724227252272622727227282272922730227312273222733227342273522736227372273822739227402274122742227432274422745227462274722748227492275022751227522275322754227552275622757227582275922760227612276222763227642276522766227672276822769227702277122772227732277422775227762277722778227792278022781227822278322784227852278622787227882278922790227912279222793227942279522796227972279822799228002280122802228032280422805228062280722808228092281022811228122281322814228152281622817228182281922820228212282222823228242282522826228272282822829228302283122832228332283422835228362283722838228392284022841228422284322844228452284622847228482284922850228512285222853228542285522856228572285822859228602286122862228632286422865228662286722868228692287022871228722287322874228752287622877228782287922880228812288222883228842288522886228872288822889228902289122892228932289422895228962289722898228992290022901229022290322904229052290622907229082290922910229112291222913229142291522916229172291822919229202292122922229232292422925229262292722928229292293022931229322293322934229352293622937229382293922940229412294222943229442294522946229472294822949229502295122952229532295422955229562295722958229592296022961229622296322964229652296622967229682296922970229712297222973229742297522976229772297822979229802298122982229832298422985229862298722988229892299022991229922299322994229952299622997229982299923000230012300223003230042300523006230072300823009230102301123012230132301423015230162301723018230192302023021230222302323024230252302623027230282302923030230312303223033230342303523036230372303823039230402304123042230432304423045230462304723048230492305023051230522305323054230552305623057230582305923060230612306223063230642306523066230672306823069230702307123072230732307423075230762307723078230792308023081230822308323084230852308623087230882308923090230912309223093230942309523096230972309823099231002310123102231032310423105231062310723108231092311023111231122311323114231152311623117231182311923120231212312223123231242312523126231272312823129231302313123132231332313423135231362313723138231392314023141231422314323144231452314623147231482314923150231512315223153231542315523156231572315823159231602316123162231632316423165231662316723168231692317023171231722317323174231752317623177231782317923180231812318223183231842318523186231872318823189231902319123192231932319423195231962319723198231992320023201232022320323204232052320623207232082320923210232112321223213232142321523216232172321823219232202322123222232232322423225232262322723228232292323023231232322323323234232352323623237232382323923240232412324223243232442324523246232472324823249232502325123252232532325423255232562325723258232592326023261232622326323264232652326623267232682326923270232712327223273232742327523276232772327823279232802328123282232832328423285232862328723288232892329023291232922329323294232952329623297232982329923300233012330223303233042330523306233072330823309233102331123312233132331423315233162331723318233192332023321233222332323324233252332623327233282332923330233312333223333233342333523336233372333823339233402334123342233432334423345233462334723348233492335023351233522335323354233552335623357233582335923360233612336223363233642336523366233672336823369233702337123372233732337423375233762337723378233792338023381233822338323384233852338623387233882338923390233912339223393233942339523396233972339823399234002340123402234032340423405234062340723408234092341023411234122341323414234152341623417234182341923420234212342223423234242342523426234272342823429234302343123432234332343423435234362343723438234392344023441234422344323444234452344623447234482344923450234512345223453234542345523456234572345823459234602346123462234632346423465234662346723468234692347023471234722347323474234752347623477234782347923480234812348223483234842348523486234872348823489234902349123492234932349423495234962349723498234992350023501235022350323504235052350623507235082350923510235112351223513235142351523516235172351823519235202352123522235232352423525235262352723528235292353023531235322353323534235352353623537235382353923540235412354223543235442354523546235472354823549235502355123552235532355423555235562355723558235592356023561235622356323564235652356623567235682356923570235712357223573235742357523576235772357823579235802358123582235832358423585235862358723588235892359023591235922359323594235952359623597235982359923600236012360223603236042360523606236072360823609236102361123612236132361423615236162361723618236192362023621236222362323624236252362623627236282362923630236312363223633236342363523636236372363823639236402364123642236432364423645236462364723648236492365023651236522365323654236552365623657236582365923660236612366223663236642366523666236672366823669236702367123672236732367423675236762367723678236792368023681236822368323684236852368623687236882368923690236912369223693236942369523696236972369823699237002370123702237032370423705237062370723708237092371023711237122371323714237152371623717237182371923720237212372223723237242372523726237272372823729237302373123732237332373423735237362373723738237392374023741237422374323744237452374623747237482374923750237512375223753237542375523756237572375823759237602376123762237632376423765237662376723768237692377023771237722377323774237752377623777237782377923780237812378223783237842378523786237872378823789237902379123792237932379423795237962379723798237992380023801238022380323804238052380623807238082380923810238112381223813238142381523816238172381823819238202382123822238232382423825238262382723828238292383023831238322383323834238352383623837238382383923840238412384223843238442384523846238472384823849238502385123852238532385423855238562385723858238592386023861238622386323864238652386623867238682386923870238712387223873238742387523876238772387823879238802388123882238832388423885238862388723888238892389023891238922389323894238952389623897238982389923900239012390223903239042390523906239072390823909239102391123912239132391423915239162391723918239192392023921239222392323924239252392623927239282392923930239312393223933239342393523936239372393823939239402394123942239432394423945239462394723948239492395023951239522395323954239552395623957239582395923960239612396223963239642396523966239672396823969239702397123972239732397423975239762397723978239792398023981239822398323984239852398623987239882398923990239912399223993239942399523996239972399823999240002400124002240032400424005240062400724008240092401024011240122401324014240152401624017240182401924020240212402224023240242402524026240272402824029240302403124032240332403424035240362403724038240392404024041240422404324044240452404624047240482404924050240512405224053240542405524056240572405824059240602406124062240632406424065240662406724068240692407024071240722407324074240752407624077240782407924080240812408224083240842408524086240872408824089240902409124092240932409424095240962409724098240992410024101241022410324104241052410624107241082410924110241112411224113241142411524116241172411824119241202412124122241232412424125241262412724128241292413024131241322413324134241352413624137241382413924140241412414224143241442414524146241472414824149241502415124152241532415424155241562415724158241592416024161241622416324164241652416624167241682416924170241712417224173241742417524176241772417824179241802418124182241832418424185241862418724188241892419024191241922419324194241952419624197241982419924200242012420224203242042420524206242072420824209242102421124212242132421424215242162421724218242192422024221242222422324224242252422624227242282422924230242312423224233242342423524236242372423824239242402424124242242432424424245242462424724248242492425024251242522425324254242552425624257242582425924260242612426224263242642426524266242672426824269242702427124272242732427424275242762427724278242792428024281242822428324284242852428624287242882428924290242912429224293242942429524296242972429824299243002430124302243032430424305243062430724308243092431024311243122431324314243152431624317243182431924320243212432224323243242432524326243272432824329243302433124332243332433424335243362433724338243392434024341243422434324344243452434624347243482434924350243512435224353243542435524356243572435824359243602436124362243632436424365243662436724368243692437024371243722437324374243752437624377243782437924380243812438224383243842438524386243872438824389243902439124392243932439424395243962439724398243992440024401244022440324404244052440624407244082440924410244112441224413244142441524416244172441824419244202442124422244232442424425244262442724428244292443024431244322443324434244352443624437244382443924440244412444224443244442444524446244472444824449244502445124452244532445424455244562445724458244592446024461244622446324464244652446624467244682446924470244712447224473244742447524476244772447824479244802448124482244832448424485244862448724488244892449024491244922449324494244952449624497244982449924500245012450224503245042450524506245072450824509245102451124512245132451424515245162451724518245192452024521245222452324524245252452624527245282452924530245312453224533245342453524536245372453824539245402454124542245432454424545245462454724548245492455024551245522455324554245552455624557245582455924560245612456224563245642456524566245672456824569245702457124572245732457424575245762457724578245792458024581245822458324584245852458624587245882458924590245912459224593245942459524596245972459824599246002460124602246032460424605246062460724608246092461024611246122461324614246152461624617246182461924620246212462224623246242462524626246272462824629246302463124632246332463424635246362463724638246392464024641246422464324644246452464624647246482464924650246512465224653246542465524656246572465824659246602466124662246632466424665246662466724668246692467024671246722467324674246752467624677246782467924680246812468224683246842468524686246872468824689246902469124692246932469424695246962469724698246992470024701247022470324704247052470624707247082470924710247112471224713247142471524716247172471824719247202472124722247232472424725247262472724728247292473024731247322473324734247352473624737247382473924740247412474224743247442474524746247472474824749247502475124752247532475424755247562475724758247592476024761247622476324764247652476624767247682476924770247712477224773247742477524776247772477824779247802478124782247832478424785247862478724788247892479024791247922479324794247952479624797247982479924800248012480224803248042480524806248072480824809248102481124812248132481424815248162481724818248192482024821248222482324824248252482624827248282482924830248312483224833248342483524836248372483824839248402484124842248432484424845248462484724848248492485024851248522485324854248552485624857248582485924860248612486224863248642486524866248672486824869248702487124872248732487424875248762487724878248792488024881248822488324884248852488624887248882488924890248912489224893248942489524896248972489824899249002490124902249032490424905249062490724908249092491024911249122491324914249152491624917249182491924920249212492224923249242492524926249272492824929249302493124932249332493424935249362493724938249392494024941249422494324944249452494624947249482494924950249512495224953249542495524956249572495824959249602496124962249632496424965249662496724968249692497024971249722497324974249752497624977249782497924980249812498224983249842498524986249872498824989249902499124992249932499424995249962499724998249992500025001250022500325004250052500625007250082500925010250112501225013250142501525016250172501825019250202502125022250232502425025250262502725028250292503025031250322503325034250352503625037250382503925040250412504225043250442504525046250472504825049250502505125052250532505425055250562505725058250592506025061250622506325064250652506625067250682506925070250712507225073250742507525076250772507825079250802508125082250832508425085250862508725088250892509025091250922509325094250952509625097250982509925100251012510225103251042510525106251072510825109251102511125112251132511425115251162511725118251192512025121251222512325124251252512625127251282512925130251312513225133251342513525136251372513825139251402514125142251432514425145251462514725148251492515025151251522515325154251552515625157251582515925160251612516225163251642516525166251672516825169251702517125172251732517425175251762517725178251792518025181251822518325184251852518625187251882518925190251912519225193251942519525196251972519825199252002520125202252032520425205252062520725208252092521025211252122521325214252152521625217252182521925220252212522225223252242522525226252272522825229252302523125232252332523425235252362523725238252392524025241252422524325244252452524625247252482524925250252512525225253252542525525256252572525825259252602526125262252632526425265252662526725268252692527025271252722527325274252752527625277252782527925280252812528225283252842528525286252872528825289252902529125292252932529425295252962529725298252992530025301253022530325304253052530625307253082530925310253112531225313253142531525316253172531825319253202532125322253232532425325253262532725328253292533025331253322533325334253352533625337253382533925340253412534225343253442534525346253472534825349253502535125352253532535425355253562535725358253592536025361253622536325364253652536625367253682536925370253712537225373253742537525376253772537825379253802538125382253832538425385253862538725388253892539025391253922539325394253952539625397253982539925400254012540225403254042540525406254072540825409254102541125412254132541425415254162541725418254192542025421254222542325424254252542625427254282542925430254312543225433254342543525436254372543825439254402544125442254432544425445254462544725448254492545025451254522545325454254552545625457254582545925460254612546225463254642546525466254672546825469254702547125472254732547425475254762547725478254792548025481254822548325484254852548625487254882548925490254912549225493254942549525496254972549825499255002550125502255032550425505255062550725508255092551025511255122551325514255152551625517255182551925520255212552225523255242552525526255272552825529255302553125532255332553425535255362553725538255392554025541255422554325544255452554625547255482554925550255512555225553255542555525556255572555825559255602556125562255632556425565255662556725568255692557025571255722557325574255752557625577255782557925580255812558225583255842558525586255872558825589255902559125592255932559425595255962559725598255992560025601256022560325604256052560625607256082560925610256112561225613256142561525616256172561825619256202562125622256232562425625256262562725628256292563025631256322563325634256352563625637256382563925640256412564225643256442564525646256472564825649256502565125652256532565425655256562565725658256592566025661256622566325664256652566625667256682566925670256712567225673256742567525676256772567825679256802568125682256832568425685256862568725688256892569025691256922569325694256952569625697256982569925700257012570225703257042570525706257072570825709257102571125712257132571425715257162571725718257192572025721257222572325724257252572625727257282572925730257312573225733257342573525736257372573825739257402574125742257432574425745257462574725748257492575025751257522575325754257552575625757257582575925760257612576225763257642576525766257672576825769257702577125772257732577425775257762577725778257792578025781257822578325784257852578625787257882578925790257912579225793257942579525796257972579825799258002580125802258032580425805258062580725808258092581025811258122581325814258152581625817258182581925820258212582225823258242582525826258272582825829258302583125832258332583425835258362583725838258392584025841258422584325844258452584625847258482584925850258512585225853258542585525856258572585825859258602586125862258632586425865258662586725868258692587025871258722587325874258752587625877258782587925880258812588225883258842588525886258872588825889258902589125892258932589425895258962589725898258992590025901259022590325904259052590625907259082590925910259112591225913259142591525916259172591825919259202592125922259232592425925259262592725928259292593025931259322593325934259352593625937259382593925940259412594225943259442594525946259472594825949259502595125952259532595425955259562595725958259592596025961259622596325964259652596625967259682596925970259712597225973259742597525976259772597825979259802598125982259832598425985259862598725988259892599025991259922599325994259952599625997259982599926000260012600226003260042600526006260072600826009260102601126012260132601426015260162601726018260192602026021260222602326024260252602626027260282602926030260312603226033260342603526036260372603826039260402604126042260432604426045260462604726048260492605026051260522605326054260552605626057260582605926060260612606226063260642606526066260672606826069260702607126072260732607426075260762607726078260792608026081260822608326084260852608626087260882608926090260912609226093260942609526096260972609826099261002610126102261032610426105261062610726108261092611026111261122611326114261152611626117261182611926120261212612226123261242612526126261272612826129261302613126132261332613426135261362613726138261392614026141261422614326144261452614626147261482614926150261512615226153261542615526156261572615826159261602616126162261632616426165261662616726168261692617026171261722617326174261752617626177261782617926180261812618226183261842618526186261872618826189261902619126192261932619426195261962619726198261992620026201262022620326204262052620626207262082620926210262112621226213262142621526216262172621826219262202622126222262232622426225262262622726228262292623026231262322623326234262352623626237262382623926240262412624226243262442624526246262472624826249262502625126252262532625426255262562625726258262592626026261262622626326264262652626626267262682626926270262712627226273262742627526276262772627826279262802628126282262832628426285262862628726288262892629026291262922629326294262952629626297262982629926300263012630226303263042630526306263072630826309263102631126312263132631426315263162631726318263192632026321263222632326324263252632626327263282632926330263312633226333263342633526336263372633826339263402634126342263432634426345263462634726348263492635026351263522635326354263552635626357263582635926360263612636226363263642636526366263672636826369263702637126372263732637426375263762637726378263792638026381263822638326384263852638626387263882638926390263912639226393263942639526396263972639826399264002640126402264032640426405264062640726408264092641026411264122641326414264152641626417264182641926420264212642226423264242642526426264272642826429264302643126432264332643426435264362643726438264392644026441264422644326444264452644626447264482644926450264512645226453264542645526456264572645826459264602646126462264632646426465264662646726468264692647026471264722647326474264752647626477264782647926480264812648226483264842648526486264872648826489264902649126492264932649426495264962649726498264992650026501265022650326504265052650626507265082650926510265112651226513265142651526516265172651826519265202652126522265232652426525265262652726528265292653026531265322653326534265352653626537265382653926540265412654226543265442654526546265472654826549265502655126552265532655426555265562655726558265592656026561265622656326564265652656626567265682656926570265712657226573265742657526576265772657826579265802658126582265832658426585265862658726588265892659026591265922659326594265952659626597265982659926600266012660226603266042660526606266072660826609266102661126612266132661426615266162661726618266192662026621266222662326624266252662626627266282662926630266312663226633266342663526636266372663826639266402664126642266432664426645266462664726648266492665026651266522665326654266552665626657266582665926660266612666226663266642666526666266672666826669266702667126672266732667426675266762667726678266792668026681266822668326684266852668626687266882668926690266912669226693266942669526696266972669826699267002670126702267032670426705267062670726708267092671026711267122671326714267152671626717267182671926720267212672226723267242672526726267272672826729267302673126732267332673426735267362673726738267392674026741267422674326744267452674626747267482674926750267512675226753267542675526756267572675826759267602676126762267632676426765267662676726768267692677026771267722677326774267752677626777267782677926780267812678226783267842678526786267872678826789267902679126792267932679426795267962679726798267992680026801268022680326804268052680626807268082680926810268112681226813268142681526816268172681826819268202682126822268232682426825268262682726828268292683026831268322683326834268352683626837268382683926840268412684226843268442684526846268472684826849268502685126852268532685426855268562685726858268592686026861268622686326864268652686626867268682686926870268712687226873268742687526876268772687826879268802688126882268832688426885268862688726888268892689026891268922689326894268952689626897268982689926900269012690226903269042690526906269072690826909269102691126912269132691426915269162691726918269192692026921269222692326924269252692626927269282692926930269312693226933269342693526936269372693826939269402694126942269432694426945269462694726948269492695026951269522695326954269552695626957269582695926960269612696226963269642696526966269672696826969269702697126972269732697426975269762697726978269792698026981269822698326984269852698626987269882698926990269912699226993269942699526996269972699826999270002700127002270032700427005270062700727008270092701027011270122701327014270152701627017270182701927020270212702227023270242702527026270272702827029270302703127032270332703427035270362703727038270392704027041270422704327044270452704627047270482704927050270512705227053270542705527056270572705827059270602706127062270632706427065270662706727068270692707027071270722707327074270752707627077270782707927080270812708227083270842708527086270872708827089270902709127092270932709427095270962709727098270992710027101271022710327104271052710627107271082710927110271112711227113271142711527116271172711827119271202712127122271232712427125271262712727128271292713027131271322713327134271352713627137271382713927140271412714227143271442714527146271472714827149271502715127152271532715427155271562715727158271592716027161271622716327164271652716627167271682716927170271712717227173271742717527176271772717827179271802718127182271832718427185271862718727188271892719027191271922719327194271952719627197271982719927200272012720227203272042720527206272072720827209272102721127212272132721427215272162721727218272192722027221272222722327224272252722627227272282722927230272312723227233272342723527236272372723827239272402724127242272432724427245272462724727248272492725027251272522725327254272552725627257272582725927260272612726227263272642726527266272672726827269272702727127272272732727427275272762727727278272792728027281272822728327284272852728627287272882728927290272912729227293272942729527296272972729827299273002730127302273032730427305273062730727308273092731027311273122731327314273152731627317273182731927320273212732227323273242732527326273272732827329273302733127332273332733427335273362733727338273392734027341273422734327344273452734627347273482734927350273512735227353273542735527356273572735827359273602736127362273632736427365273662736727368273692737027371273722737327374273752737627377273782737927380273812738227383273842738527386273872738827389273902739127392273932739427395273962739727398273992740027401274022740327404274052740627407274082740927410274112741227413274142741527416274172741827419274202742127422274232742427425274262742727428274292743027431274322743327434274352743627437274382743927440274412744227443274442744527446274472744827449274502745127452274532745427455274562745727458274592746027461274622746327464274652746627467274682746927470274712747227473274742747527476274772747827479274802748127482274832748427485274862748727488274892749027491274922749327494274952749627497274982749927500275012750227503275042750527506275072750827509275102751127512275132751427515275162751727518275192752027521275222752327524275252752627527275282752927530275312753227533275342753527536275372753827539275402754127542275432754427545275462754727548275492755027551275522755327554275552755627557275582755927560275612756227563275642756527566275672756827569275702757127572275732757427575275762757727578275792758027581275822758327584275852758627587275882758927590275912759227593275942759527596275972759827599276002760127602276032760427605276062760727608276092761027611276122761327614276152761627617276182761927620276212762227623276242762527626276272762827629276302763127632276332763427635276362763727638276392764027641276422764327644276452764627647276482764927650276512765227653276542765527656276572765827659276602766127662276632766427665276662766727668276692767027671276722767327674276752767627677276782767927680276812768227683276842768527686276872768827689276902769127692276932769427695276962769727698276992770027701277022770327704277052770627707277082770927710277112771227713277142771527716277172771827719277202772127722277232772427725277262772727728277292773027731277322773327734277352773627737277382773927740277412774227743277442774527746277472774827749277502775127752277532775427755277562775727758277592776027761277622776327764277652776627767277682776927770277712777227773277742777527776277772777827779277802778127782277832778427785277862778727788277892779027791277922779327794277952779627797277982779927800278012780227803278042780527806278072780827809278102781127812278132781427815278162781727818278192782027821278222782327824278252782627827278282782927830278312783227833278342783527836278372783827839278402784127842278432784427845278462784727848278492785027851278522785327854278552785627857278582785927860278612786227863278642786527866278672786827869278702787127872278732787427875278762787727878278792788027881278822788327884278852788627887278882788927890278912789227893278942789527896278972789827899279002790127902279032790427905279062790727908279092791027911279122791327914279152791627917279182791927920279212792227923279242792527926279272792827929279302793127932279332793427935279362793727938279392794027941279422794327944279452794627947279482794927950279512795227953279542795527956279572795827959279602796127962279632796427965279662796727968279692797027971279722797327974279752797627977279782797927980279812798227983279842798527986279872798827989279902799127992279932799427995279962799727998279992800028001280022800328004280052800628007280082800928010280112801228013280142801528016280172801828019280202802128022280232802428025280262802728028280292803028031280322803328034280352803628037280382803928040280412804228043280442804528046280472804828049280502805128052280532805428055280562805728058280592806028061280622806328064280652806628067280682806928070280712807228073280742807528076280772807828079280802808128082280832808428085280862808728088280892809028091280922809328094280952809628097280982809928100281012810228103281042810528106281072810828109281102811128112281132811428115281162811728118281192812028121281222812328124281252812628127281282812928130281312813228133281342813528136281372813828139281402814128142281432814428145281462814728148281492815028151281522815328154281552815628157281582815928160281612816228163281642816528166281672816828169281702817128172281732817428175281762817728178281792818028181281822818328184281852818628187281882818928190281912819228193281942819528196281972819828199282002820128202282032820428205282062820728208282092821028211282122821328214282152821628217282182821928220282212822228223282242822528226282272822828229282302823128232282332823428235282362823728238282392824028241282422824328244282452824628247282482824928250282512825228253282542825528256282572825828259282602826128262282632826428265282662826728268282692827028271282722827328274282752827628277282782827928280282812828228283282842828528286282872828828289282902829128292282932829428295282962829728298282992830028301283022830328304283052830628307283082830928310283112831228313283142831528316283172831828319283202832128322283232832428325283262832728328283292833028331283322833328334283352833628337283382833928340283412834228343283442834528346283472834828349283502835128352283532835428355283562835728358283592836028361283622836328364283652836628367283682836928370283712837228373283742837528376283772837828379283802838128382283832838428385283862838728388283892839028391283922839328394283952839628397283982839928400284012840228403284042840528406284072840828409284102841128412284132841428415284162841728418284192842028421284222842328424284252842628427284282842928430284312843228433284342843528436284372843828439284402844128442284432844428445284462844728448284492845028451284522845328454284552845628457284582845928460284612846228463284642846528466284672846828469284702847128472284732847428475284762847728478284792848028481284822848328484284852848628487284882848928490284912849228493284942849528496284972849828499285002850128502285032850428505285062850728508285092851028511285122851328514285152851628517285182851928520285212852228523285242852528526285272852828529285302853128532285332853428535285362853728538285392854028541285422854328544285452854628547285482854928550285512855228553285542855528556285572855828559285602856128562285632856428565285662856728568285692857028571285722857328574285752857628577285782857928580285812858228583285842858528586285872858828589285902859128592285932859428595285962859728598285992860028601286022860328604286052860628607286082860928610286112861228613286142861528616286172861828619286202862128622286232862428625286262862728628286292863028631286322863328634286352863628637286382863928640286412864228643286442864528646286472864828649286502865128652286532865428655286562865728658286592866028661286622866328664286652866628667286682866928670286712867228673286742867528676286772867828679286802868128682286832868428685286862868728688286892869028691286922869328694286952869628697286982869928700287012870228703287042870528706287072870828709287102871128712287132871428715287162871728718287192872028721287222872328724287252872628727287282872928730287312873228733287342873528736287372873828739287402874128742287432874428745287462874728748287492875028751287522875328754287552875628757287582875928760287612876228763287642876528766287672876828769287702877128772287732877428775287762877728778287792878028781287822878328784287852878628787287882878928790287912879228793287942879528796287972879828799288002880128802288032880428805288062880728808288092881028811288122881328814288152881628817288182881928820288212882228823288242882528826288272882828829288302883128832288332883428835288362883728838288392884028841288422884328844288452884628847288482884928850288512885228853288542885528856288572885828859288602886128862288632886428865288662886728868288692887028871288722887328874288752887628877288782887928880288812888228883288842888528886288872888828889288902889128892288932889428895288962889728898288992890028901289022890328904289052890628907289082890928910289112891228913289142891528916289172891828919289202892128922289232892428925289262892728928289292893028931289322893328934289352893628937289382893928940289412894228943289442894528946289472894828949289502895128952289532895428955289562895728958289592896028961289622896328964289652896628967289682896928970289712897228973289742897528976289772897828979289802898128982289832898428985289862898728988289892899028991289922899328994289952899628997289982899929000290012900229003290042900529006290072900829009290102901129012290132901429015290162901729018290192902029021290222902329024290252902629027290282902929030290312903229033290342903529036290372903829039290402904129042290432904429045290462904729048290492905029051290522905329054290552905629057290582905929060290612906229063290642906529066290672906829069290702907129072290732907429075290762907729078290792908029081290822908329084290852908629087290882908929090290912909229093290942909529096290972909829099291002910129102291032910429105291062910729108291092911029111291122911329114291152911629117291182911929120291212912229123291242912529126291272912829129291302913129132291332913429135291362913729138291392914029141291422914329144291452914629147291482914929150291512915229153291542915529156291572915829159291602916129162291632916429165291662916729168291692917029171291722917329174291752917629177291782917929180291812918229183291842918529186291872918829189291902919129192291932919429195291962919729198291992920029201292022920329204292052920629207292082920929210292112921229213292142921529216292172921829219292202922129222292232922429225292262922729228292292923029231292322923329234292352923629237292382923929240292412924229243292442924529246292472924829249292502925129252292532925429255292562925729258292592926029261292622926329264292652926629267292682926929270292712927229273292742927529276292772927829279292802928129282292832928429285292862928729288292892929029291292922929329294292952929629297292982929929300293012930229303293042930529306293072930829309293102931129312293132931429315293162931729318293192932029321293222932329324293252932629327293282932929330293312933229333293342933529336293372933829339293402934129342293432934429345293462934729348293492935029351293522935329354293552935629357293582935929360293612936229363293642936529366293672936829369293702937129372293732937429375293762937729378293792938029381293822938329384293852938629387293882938929390293912939229393293942939529396293972939829399294002940129402294032940429405294062940729408294092941029411294122941329414294152941629417294182941929420294212942229423294242942529426294272942829429294302943129432294332943429435294362943729438294392944029441294422944329444294452944629447294482944929450294512945229453294542945529456294572945829459294602946129462294632946429465294662946729468294692947029471294722947329474294752947629477294782947929480294812948229483294842948529486294872948829489294902949129492294932949429495294962949729498294992950029501295022950329504295052950629507295082950929510295112951229513295142951529516295172951829519295202952129522295232952429525295262952729528295292953029531295322953329534295352953629537295382953929540295412954229543295442954529546295472954829549295502955129552295532955429555295562955729558295592956029561295622956329564295652956629567295682956929570295712957229573295742957529576295772957829579295802958129582295832958429585295862958729588295892959029591295922959329594295952959629597295982959929600296012960229603296042960529606296072960829609296102961129612296132961429615296162961729618296192962029621296222962329624296252962629627296282962929630296312963229633296342963529636296372963829639296402964129642296432964429645296462964729648296492965029651296522965329654296552965629657296582965929660296612966229663296642966529666296672966829669296702967129672296732967429675296762967729678296792968029681296822968329684296852968629687296882968929690296912969229693296942969529696296972969829699297002970129702297032970429705297062970729708297092971029711297122971329714297152971629717297182971929720297212972229723297242972529726297272972829729297302973129732297332973429735297362973729738297392974029741297422974329744297452974629747297482974929750297512975229753297542975529756297572975829759297602976129762297632976429765297662976729768297692977029771297722977329774297752977629777297782977929780297812978229783297842978529786297872978829789297902979129792297932979429795297962979729798297992980029801298022980329804298052980629807298082980929810298112981229813298142981529816298172981829819298202982129822298232982429825298262982729828298292983029831298322983329834298352983629837298382983929840298412984229843298442984529846298472984829849298502985129852298532985429855298562985729858298592986029861298622986329864298652986629867298682986929870298712987229873298742987529876298772987829879298802988129882298832988429885298862988729888298892989029891298922989329894298952989629897298982989929900299012990229903299042990529906299072990829909299102991129912299132991429915299162991729918299192992029921299222992329924299252992629927299282992929930299312993229933299342993529936299372993829939299402994129942299432994429945299462994729948299492995029951299522995329954299552995629957299582995929960299612996229963299642996529966299672996829969299702997129972299732997429975299762997729978299792998029981299822998329984299852998629987299882998929990299912999229993299942999529996299972999829999300003000130002300033000430005300063000730008300093001030011300123001330014300153001630017300183001930020300213002230023300243002530026300273002830029300303003130032300333003430035300363003730038300393004030041300423004330044300453004630047300483004930050300513005230053300543005530056300573005830059300603006130062300633006430065300663006730068300693007030071300723007330074300753007630077300783007930080300813008230083300843008530086300873008830089300903009130092300933009430095300963009730098300993010030101301023010330104301053010630107301083010930110301113011230113301143011530116301173011830119301203012130122301233012430125301263012730128301293013030131301323013330134301353013630137301383013930140301413014230143301443014530146301473014830149301503015130152301533015430155301563015730158301593016030161301623016330164301653016630167301683016930170301713017230173301743017530176301773017830179301803018130182301833018430185301863018730188301893019030191301923019330194301953019630197301983019930200302013020230203302043020530206302073020830209302103021130212302133021430215302163021730218302193022030221302223022330224302253022630227302283022930230302313023230233302343023530236302373023830239302403024130242302433024430245302463024730248302493025030251302523025330254302553025630257302583025930260302613026230263302643026530266302673026830269302703027130272302733027430275302763027730278302793028030281302823028330284302853028630287302883028930290302913029230293302943029530296302973029830299303003030130302303033030430305303063030730308303093031030311303123031330314303153031630317303183031930320303213032230323303243032530326303273032830329303303033130332303333033430335303363033730338303393034030341303423034330344303453034630347303483034930350303513035230353303543035530356303573035830359303603036130362303633036430365303663036730368303693037030371303723037330374303753037630377303783037930380303813038230383303843038530386303873038830389303903039130392303933039430395303963039730398303993040030401304023040330404304053040630407304083040930410304113041230413304143041530416304173041830419304203042130422304233042430425304263042730428304293043030431304323043330434304353043630437304383043930440304413044230443304443044530446304473044830449304503045130452304533045430455304563045730458304593046030461304623046330464304653046630467304683046930470304713047230473304743047530476304773047830479304803048130482304833048430485304863048730488304893049030491304923049330494304953049630497304983049930500305013050230503305043050530506305073050830509305103051130512305133051430515305163051730518305193052030521305223052330524305253052630527305283052930530305313053230533305343053530536305373053830539305403054130542305433054430545305463054730548305493055030551305523055330554305553055630557305583055930560305613056230563305643056530566305673056830569305703057130572305733057430575305763057730578305793058030581305823058330584305853058630587305883058930590305913059230593305943059530596305973059830599306003060130602306033060430605306063060730608306093061030611306123061330614306153061630617306183061930620306213062230623306243062530626306273062830629306303063130632306333063430635306363063730638306393064030641306423064330644306453064630647306483064930650306513065230653306543065530656306573065830659306603066130662306633066430665306663066730668306693067030671306723067330674306753067630677306783067930680306813068230683306843068530686306873068830689306903069130692306933069430695306963069730698306993070030701307023070330704307053070630707307083070930710307113071230713307143071530716307173071830719307203072130722307233072430725307263072730728307293073030731307323073330734307353073630737307383073930740307413074230743307443074530746307473074830749307503075130752307533075430755307563075730758307593076030761307623076330764307653076630767307683076930770307713077230773307743077530776307773077830779307803078130782307833078430785307863078730788307893079030791307923079330794307953079630797307983079930800308013080230803308043080530806308073080830809308103081130812308133081430815308163081730818308193082030821308223082330824308253082630827308283082930830308313083230833308343083530836308373083830839308403084130842308433084430845308463084730848308493085030851308523085330854308553085630857308583085930860308613086230863308643086530866308673086830869308703087130872308733087430875308763087730878308793088030881308823088330884308853088630887308883088930890308913089230893308943089530896308973089830899309003090130902309033090430905309063090730908309093091030911309123091330914309153091630917309183091930920309213092230923309243092530926309273092830929309303093130932309333093430935309363093730938309393094030941309423094330944309453094630947309483094930950309513095230953309543095530956309573095830959309603096130962309633096430965309663096730968309693097030971309723097330974309753097630977309783097930980309813098230983309843098530986309873098830989309903099130992309933099430995309963099730998309993100031001310023100331004310053100631007310083100931010310113101231013310143101531016310173101831019310203102131022310233102431025310263102731028310293103031031310323103331034310353103631037310383103931040310413104231043310443104531046310473104831049310503105131052310533105431055310563105731058310593106031061310623106331064310653106631067310683106931070310713107231073310743107531076310773107831079310803108131082310833108431085310863108731088310893109031091310923109331094310953109631097310983109931100311013110231103311043110531106311073110831109311103111131112311133111431115311163111731118311193112031121311223112331124311253112631127311283112931130311313113231133311343113531136311373113831139311403114131142311433114431145311463114731148311493115031151311523115331154311553115631157311583115931160311613116231163311643116531166311673116831169311703117131172311733117431175311763117731178311793118031181311823118331184311853118631187311883118931190311913119231193311943119531196311973119831199312003120131202312033120431205312063120731208312093121031211312123121331214312153121631217312183121931220312213122231223312243122531226312273122831229312303123131232312333123431235312363123731238312393124031241312423124331244312453124631247312483124931250312513125231253312543125531256312573125831259312603126131262312633126431265312663126731268312693127031271312723127331274312753127631277312783127931280312813128231283312843128531286312873128831289312903129131292312933129431295312963129731298312993130031301313023130331304313053130631307313083130931310313113131231313313143131531316313173131831319313203132131322313233132431325313263132731328313293133031331313323133331334313353133631337313383133931340313413134231343313443134531346313473134831349313503135131352313533135431355313563135731358313593136031361313623136331364313653136631367313683136931370313713137231373313743137531376313773137831379313803138131382313833138431385313863138731388313893139031391313923139331394313953139631397313983139931400314013140231403314043140531406314073140831409314103141131412314133141431415314163141731418314193142031421314223142331424314253142631427314283142931430314313143231433314343143531436314373143831439314403144131442314433144431445314463144731448314493145031451314523145331454314553145631457314583145931460314613146231463314643146531466314673146831469314703147131472314733147431475314763147731478314793148031481314823148331484314853148631487314883148931490314913149231493314943149531496314973149831499315003150131502315033150431505315063150731508315093151031511315123151331514315153151631517315183151931520315213152231523315243152531526315273152831529315303153131532315333153431535315363153731538315393154031541315423154331544315453154631547315483154931550315513155231553315543155531556315573155831559315603156131562315633156431565315663156731568315693157031571315723157331574315753157631577315783157931580315813158231583315843158531586315873158831589315903159131592315933159431595315963159731598315993160031601316023160331604316053160631607316083160931610316113161231613316143161531616316173161831619316203162131622316233162431625316263162731628316293163031631316323163331634316353163631637316383163931640316413164231643316443164531646316473164831649316503165131652316533165431655316563165731658316593166031661316623166331664316653166631667316683166931670316713167231673316743167531676316773167831679316803168131682316833168431685316863168731688316893169031691316923169331694316953169631697316983169931700317013170231703317043170531706317073170831709317103171131712317133171431715317163171731718317193172031721317223172331724317253172631727317283172931730317313173231733317343173531736317373173831739317403174131742317433174431745317463174731748317493175031751317523175331754317553175631757317583175931760317613176231763317643176531766317673176831769317703177131772317733177431775317763177731778317793178031781317823178331784317853178631787317883178931790317913179231793317943179531796317973179831799318003180131802318033180431805318063180731808318093181031811318123181331814318153181631817318183181931820318213182231823318243182531826318273182831829318303183131832318333183431835318363183731838318393184031841318423184331844318453184631847318483184931850318513185231853318543185531856318573185831859318603186131862318633186431865318663186731868318693187031871318723187331874318753187631877318783187931880318813188231883318843188531886318873188831889318903189131892318933189431895318963189731898318993190031901319023190331904319053190631907319083190931910319113191231913319143191531916319173191831919319203192131922319233192431925319263192731928319293193031931319323193331934319353193631937319383193931940319413194231943319443194531946319473194831949319503195131952319533195431955319563195731958319593196031961319623196331964319653196631967319683196931970319713197231973319743197531976319773197831979319803198131982319833198431985319863198731988319893199031991319923199331994319953199631997319983199932000320013200232003320043200532006320073200832009320103201132012320133201432015320163201732018320193202032021320223202332024320253202632027320283202932030320313203232033320343203532036320373203832039320403204132042320433204432045320463204732048320493205032051320523205332054320553205632057320583205932060320613206232063320643206532066320673206832069320703207132072320733207432075320763207732078320793208032081320823208332084320853208632087320883208932090320913209232093320943209532096320973209832099321003210132102321033210432105321063210732108321093211032111321123211332114321153211632117321183211932120321213212232123321243212532126321273212832129321303213132132321333213432135321363213732138321393214032141321423214332144321453214632147321483214932150321513215232153321543215532156321573215832159321603216132162321633216432165321663216732168321693217032171321723217332174321753217632177321783217932180321813218232183321843218532186321873218832189321903219132192321933219432195321963219732198321993220032201322023220332204322053220632207322083220932210322113221232213322143221532216322173221832219322203222132222322233222432225322263222732228322293223032231322323223332234322353223632237322383223932240322413224232243322443224532246322473224832249322503225132252322533225432255322563225732258322593226032261322623226332264322653226632267322683226932270322713227232273322743227532276322773227832279322803228132282322833228432285322863228732288322893229032291322923229332294322953229632297322983229932300323013230232303323043230532306323073230832309323103231132312323133231432315323163231732318323193232032321323223232332324323253232632327323283232932330323313233232333323343233532336323373233832339323403234132342323433234432345323463234732348323493235032351323523235332354323553235632357323583235932360323613236232363323643236532366323673236832369323703237132372323733237432375323763237732378323793238032381323823238332384323853238632387323883238932390323913239232393323943239532396323973239832399324003240132402324033240432405324063240732408324093241032411324123241332414324153241632417324183241932420324213242232423324243242532426324273242832429324303243132432324333243432435324363243732438324393244032441324423244332444324453244632447324483244932450324513245232453324543245532456324573245832459324603246132462324633246432465324663246732468324693247032471324723247332474324753247632477324783247932480324813248232483324843248532486324873248832489324903249132492324933249432495324963249732498324993250032501325023250332504325053250632507325083250932510325113251232513325143251532516325173251832519325203252132522325233252432525325263252732528325293253032531325323253332534325353253632537325383253932540325413254232543325443254532546325473254832549325503255132552325533255432555325563255732558325593256032561325623256332564325653256632567325683256932570325713257232573325743257532576325773257832579325803258132582325833258432585325863258732588325893259032591325923259332594325953259632597325983259932600326013260232603326043260532606326073260832609326103261132612326133261432615326163261732618326193262032621326223262332624326253262632627326283262932630326313263232633326343263532636326373263832639326403264132642326433264432645326463264732648326493265032651326523265332654326553265632657326583265932660326613266232663326643266532666326673266832669326703267132672326733267432675326763267732678326793268032681326823268332684326853268632687326883268932690326913269232693326943269532696326973269832699327003270132702327033270432705327063270732708327093271032711327123271332714327153271632717327183271932720327213272232723327243272532726327273272832729327303273132732327333273432735327363273732738327393274032741327423274332744327453274632747327483274932750327513275232753327543275532756327573275832759327603276132762327633276432765327663276732768327693277032771327723277332774327753277632777327783277932780327813278232783327843278532786327873278832789327903279132792327933279432795327963279732798327993280032801328023280332804328053280632807328083280932810328113281232813328143281532816328173281832819328203282132822328233282432825328263282732828328293283032831328323283332834328353283632837328383283932840328413284232843328443284532846328473284832849328503285132852328533285432855328563285732858328593286032861328623286332864328653286632867328683286932870328713287232873328743287532876328773287832879328803288132882328833288432885328863288732888328893289032891328923289332894328953289632897328983289932900329013290232903329043290532906329073290832909329103291132912329133291432915329163291732918329193292032921329223292332924329253292632927329283292932930329313293232933329343293532936329373293832939329403294132942329433294432945329463294732948329493295032951329523295332954329553295632957329583295932960329613296232963329643296532966329673296832969329703297132972329733297432975329763297732978329793298032981329823298332984329853298632987329883298932990329913299232993329943299532996329973299832999330003300133002330033300433005330063300733008330093301033011330123301333014330153301633017330183301933020330213302233023330243302533026330273302833029330303303133032330333303433035330363303733038330393304033041330423304333044330453304633047330483304933050330513305233053330543305533056330573305833059330603306133062330633306433065330663306733068330693307033071330723307333074330753307633077330783307933080330813308233083330843308533086330873308833089330903309133092330933309433095330963309733098330993310033101331023310333104331053310633107331083310933110331113311233113331143311533116331173311833119331203312133122331233312433125331263312733128331293313033131331323313333134331353313633137331383313933140331413314233143331443314533146331473314833149331503315133152331533315433155331563315733158331593316033161331623316333164331653316633167331683316933170331713317233173331743317533176331773317833179331803318133182331833318433185331863318733188331893319033191331923319333194331953319633197331983319933200332013320233203332043320533206332073320833209332103321133212332133321433215332163321733218332193322033221332223322333224332253322633227332283322933230332313323233233332343323533236332373323833239332403324133242332433324433245332463324733248332493325033251332523325333254332553325633257332583325933260332613326233263332643326533266332673326833269332703327133272332733327433275332763327733278332793328033281332823328333284332853328633287332883328933290332913329233293332943329533296332973329833299333003330133302333033330433305333063330733308333093331033311333123331333314333153331633317333183331933320333213332233323333243332533326333273332833329333303333133332333333333433335333363333733338333393334033341333423334333344333453334633347333483334933350333513335233353333543335533356333573335833359333603336133362333633336433365333663336733368333693337033371333723337333374333753337633377333783337933380333813338233383333843338533386333873338833389333903339133392333933339433395333963339733398333993340033401334023340333404334053340633407334083340933410334113341233413334143341533416334173341833419334203342133422334233342433425334263342733428334293343033431334323343333434334353343633437334383343933440334413344233443334443344533446334473344833449334503345133452334533345433455334563345733458334593346033461334623346333464334653346633467334683346933470334713347233473334743347533476334773347833479334803348133482334833348433485334863348733488334893349033491334923349333494334953349633497334983349933500335013350233503335043350533506335073350833509335103351133512335133351433515335163351733518335193352033521335223352333524335253352633527335283352933530335313353233533335343353533536335373353833539335403354133542335433354433545335463354733548335493355033551335523355333554335553355633557335583355933560335613356233563335643356533566335673356833569335703357133572335733357433575335763357733578335793358033581335823358333584335853358633587335883358933590335913359233593335943359533596335973359833599336003360133602336033360433605336063360733608336093361033611336123361333614336153361633617336183361933620336213362233623336243362533626336273362833629336303363133632336333363433635336363363733638336393364033641336423364333644336453364633647336483364933650336513365233653336543365533656336573365833659336603366133662336633366433665336663366733668336693367033671336723367333674336753367633677336783367933680336813368233683336843368533686336873368833689336903369133692336933369433695336963369733698336993370033701337023370333704337053370633707337083370933710337113371233713337143371533716337173371833719337203372133722337233372433725337263372733728337293373033731337323373333734337353373633737337383373933740337413374233743337443374533746337473374833749337503375133752337533375433755337563375733758337593376033761337623376333764337653376633767337683376933770337713377233773337743377533776337773377833779337803378133782337833378433785337863378733788337893379033791337923379333794337953379633797337983379933800338013380233803338043380533806338073380833809338103381133812338133381433815338163381733818338193382033821338223382333824338253382633827338283382933830338313383233833338343383533836338373383833839338403384133842338433384433845338463384733848338493385033851338523385333854338553385633857338583385933860338613386233863338643386533866338673386833869338703387133872338733387433875338763387733878338793388033881338823388333884338853388633887338883388933890338913389233893338943389533896338973389833899339003390133902339033390433905339063390733908339093391033911339123391333914339153391633917339183391933920339213392233923339243392533926339273392833929339303393133932339333393433935339363393733938339393394033941339423394333944339453394633947339483394933950339513395233953339543395533956339573395833959339603396133962339633396433965339663396733968339693397033971339723397333974339753397633977339783397933980339813398233983339843398533986339873398833989339903399133992339933399433995339963399733998339993400034001340023400334004340053400634007340083400934010340113401234013340143401534016340173401834019340203402134022340233402434025340263402734028340293403034031340323403334034340353403634037340383403934040340413404234043340443404534046340473404834049340503405134052340533405434055340563405734058340593406034061340623406334064340653406634067340683406934070340713407234073340743407534076340773407834079340803408134082340833408434085340863408734088340893409034091340923409334094340953409634097340983409934100341013410234103341043410534106341073410834109341103411134112341133411434115341163411734118341193412034121341223412334124341253412634127341283412934130341313413234133341343413534136341373413834139341403414134142341433414434145341463414734148341493415034151341523415334154341553415634157341583415934160341613416234163341643416534166341673416834169341703417134172341733417434175341763417734178341793418034181341823418334184341853418634187341883418934190341913419234193341943419534196341973419834199342003420134202342033420434205342063420734208342093421034211342123421334214342153421634217342183421934220342213422234223342243422534226342273422834229342303423134232342333423434235342363423734238342393424034241342423424334244342453424634247342483424934250342513425234253342543425534256342573425834259342603426134262342633426434265342663426734268342693427034271342723427334274342753427634277342783427934280342813428234283342843428534286342873428834289342903429134292342933429434295342963429734298342993430034301343023430334304343053430634307343083430934310343113431234313343143431534316343173431834319343203432134322343233432434325343263432734328343293433034331343323433334334343353433634337343383433934340343413434234343343443434534346343473434834349343503435134352343533435434355343563435734358343593436034361343623436334364343653436634367343683436934370343713437234373343743437534376343773437834379343803438134382343833438434385343863438734388343893439034391343923439334394343953439634397343983439934400344013440234403344043440534406344073440834409344103441134412344133441434415344163441734418344193442034421344223442334424344253442634427344283442934430344313443234433344343443534436344373443834439344403444134442344433444434445344463444734448344493445034451344523445334454344553445634457344583445934460344613446234463344643446534466344673446834469344703447134472344733447434475344763447734478344793448034481344823448334484344853448634487344883448934490344913449234493344943449534496344973449834499345003450134502345033450434505345063450734508345093451034511345123451334514345153451634517345183451934520345213452234523345243452534526345273452834529345303453134532345333453434535345363453734538345393454034541345423454334544345453454634547345483454934550345513455234553345543455534556345573455834559345603456134562345633456434565345663456734568345693457034571345723457334574345753457634577345783457934580345813458234583345843458534586345873458834589345903459134592345933459434595345963459734598345993460034601346023460334604346053460634607346083460934610346113461234613346143461534616346173461834619346203462134622346233462434625346263462734628346293463034631346323463334634346353463634637346383463934640346413464234643346443464534646346473464834649346503465134652346533465434655346563465734658346593466034661346623466334664346653466634667346683466934670346713467234673346743467534676346773467834679346803468134682346833468434685346863468734688346893469034691346923469334694346953469634697346983469934700347013470234703347043470534706347073470834709347103471134712347133471434715347163471734718347193472034721347223472334724347253472634727347283472934730347313473234733347343473534736347373473834739347403474134742347433474434745347463474734748347493475034751347523475334754347553475634757
  1. /**
  2. * TinyMCE version 8.0.2 (2025-08-14)
  3. */
  4. (function () {
  5. 'use strict';
  6. /* eslint-disable @typescript-eslint/no-wrapper-object-types */
  7. const getPrototypeOf$2 = Object.getPrototypeOf;
  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$1 = (t) => (a) => t === a;
  36. const is$2 = (value, constructor) => isObject(value) && hasProto(value, constructor, (o, proto) => getPrototypeOf$2(o) === proto);
  37. const isString = isType$1('string');
  38. const isObject = isType$1('object');
  39. const isPlainObject = (value) => is$2(value, Object);
  40. const isArray = isType$1('array');
  41. const isNull = eq$1(null);
  42. const isBoolean = isSimpleType('boolean');
  43. const isUndefined = eq$1(undefined);
  44. const isNullable = (a) => a === null || a === undefined;
  45. const isNonNullable = (a) => !isNullable(a);
  46. const isFunction = isSimpleType('function');
  47. const isNumber = isSimpleType('number');
  48. const isArrayOf = (value, pred) => {
  49. if (isArray(value)) {
  50. for (let i = 0, len = value.length; i < len; ++i) {
  51. if (!(pred(value[i]))) {
  52. return false;
  53. }
  54. }
  55. return true;
  56. }
  57. return false;
  58. };
  59. const noop = () => { };
  60. const noarg = (f) => () => f();
  61. /** Compose a unary function with an n-ary function */
  62. const compose = (fa, fb) => {
  63. return (...args) => {
  64. return fa(fb.apply(null, args));
  65. };
  66. };
  67. /** Compose two unary functions. Similar to compose, but avoids using Function.prototype.apply. */
  68. const compose1 = (fbc, fab) => (a) => fbc(fab(a));
  69. const constant$1 = (value) => {
  70. return () => {
  71. return value;
  72. };
  73. };
  74. const identity = (x) => {
  75. return x;
  76. };
  77. const tripleEquals = (a, b) => {
  78. return a === b;
  79. };
  80. function curry(fn, ...initialArgs) {
  81. return (...restArgs) => {
  82. const all = initialArgs.concat(restArgs);
  83. return fn.apply(null, all);
  84. };
  85. }
  86. const not = (f) => (t) => !f(t);
  87. const die = (msg) => {
  88. return () => {
  89. throw new Error(msg);
  90. };
  91. };
  92. const apply$1 = (f) => {
  93. return f();
  94. };
  95. const never = constant$1(false);
  96. const always = constant$1(true);
  97. /**
  98. * The `Optional` type represents a value (of any type) that potentially does
  99. * not exist. Any `Optional<T>` can either be a `Some<T>` (in which case the
  100. * value does exist) or a `None` (in which case the value does not exist). This
  101. * module defines a whole lot of FP-inspired utility functions for dealing with
  102. * `Optional` objects.
  103. *
  104. * Comparison with null or undefined:
  105. * - We don't get fancy null coalescing operators with `Optional`
  106. * - We do get fancy helper functions with `Optional`
  107. * - `Optional` support nesting, and allow for the type to still be nullable (or
  108. * another `Optional`)
  109. * - There is no option to turn off strict-optional-checks like there is for
  110. * strict-null-checks
  111. */
  112. class Optional {
  113. // The internal representation has a `tag` and a `value`, but both are
  114. // private: able to be console.logged, but not able to be accessed by code
  115. constructor(tag, value) {
  116. this.tag = tag;
  117. this.value = value;
  118. }
  119. // --- Identities ---
  120. /**
  121. * Creates a new `Optional<T>` that **does** contain a value.
  122. */
  123. static some(value) {
  124. return new Optional(true, value);
  125. }
  126. /**
  127. * Create a new `Optional<T>` that **does not** contain a value. `T` can be
  128. * any type because we don't actually have a `T`.
  129. */
  130. static none() {
  131. return Optional.singletonNone;
  132. }
  133. /**
  134. * Perform a transform on an `Optional` type. Regardless of whether this
  135. * `Optional` contains a value or not, `fold` will return a value of type `U`.
  136. * If this `Optional` does not contain a value, the `U` will be created by
  137. * calling `onNone`. If this `Optional` does contain a value, the `U` will be
  138. * created by calling `onSome`.
  139. *
  140. * For the FP enthusiasts in the room, this function:
  141. * 1. Could be used to implement all of the functions below
  142. * 2. Forms a catamorphism
  143. */
  144. fold(onNone, onSome) {
  145. if (this.tag) {
  146. return onSome(this.value);
  147. }
  148. else {
  149. return onNone();
  150. }
  151. }
  152. /**
  153. * Determine if this `Optional` object contains a value.
  154. */
  155. isSome() {
  156. return this.tag;
  157. }
  158. /**
  159. * Determine if this `Optional` object **does not** contain a value.
  160. */
  161. isNone() {
  162. return !this.tag;
  163. }
  164. // --- Functor (name stolen from Haskell / maths) ---
  165. /**
  166. * Perform a transform on an `Optional` object, **if** there is a value. If
  167. * you provide a function to turn a T into a U, this is the function you use
  168. * to turn an `Optional<T>` into an `Optional<U>`. If this **does** contain
  169. * a value then the output will also contain a value (that value being the
  170. * output of `mapper(this.value)`), and if this **does not** contain a value
  171. * then neither will the output.
  172. */
  173. map(mapper) {
  174. if (this.tag) {
  175. return Optional.some(mapper(this.value));
  176. }
  177. else {
  178. return Optional.none();
  179. }
  180. }
  181. // --- Monad (name stolen from Haskell / maths) ---
  182. /**
  183. * Perform a transform on an `Optional` object, **if** there is a value.
  184. * Unlike `map`, here the transform itself also returns an `Optional`.
  185. */
  186. bind(binder) {
  187. if (this.tag) {
  188. return binder(this.value);
  189. }
  190. else {
  191. return Optional.none();
  192. }
  193. }
  194. // --- Traversable (name stolen from Haskell / maths) ---
  195. /**
  196. * For a given predicate, this function finds out if there **exists** a value
  197. * inside this `Optional` object that meets the predicate. In practice, this
  198. * means that for `Optional`s that do not contain a value it returns false (as
  199. * no predicate-meeting value exists).
  200. */
  201. exists(predicate) {
  202. return this.tag && predicate(this.value);
  203. }
  204. /**
  205. * For a given predicate, this function finds out if **all** the values inside
  206. * this `Optional` object meet the predicate. In practice, this means that
  207. * for `Optional`s that do not contain a value it returns true (as all 0
  208. * objects do meet the predicate).
  209. */
  210. forall(predicate) {
  211. return !this.tag || predicate(this.value);
  212. }
  213. filter(predicate) {
  214. if (!this.tag || predicate(this.value)) {
  215. return this;
  216. }
  217. else {
  218. return Optional.none();
  219. }
  220. }
  221. // --- Getters ---
  222. /**
  223. * Get the value out of the inside of the `Optional` object, using a default
  224. * `replacement` value if the provided `Optional` object does not contain a
  225. * value.
  226. */
  227. getOr(replacement) {
  228. return this.tag ? this.value : replacement;
  229. }
  230. /**
  231. * Get the value out of the inside of the `Optional` object, using a default
  232. * `replacement` value if the provided `Optional` object does not contain a
  233. * value. Unlike `getOr`, in this method the `replacement` object is also
  234. * `Optional` - meaning that this method will always return an `Optional`.
  235. */
  236. or(replacement) {
  237. return this.tag ? this : replacement;
  238. }
  239. /**
  240. * Get the value out of the inside of the `Optional` object, using a default
  241. * `replacement` value if the provided `Optional` object does not contain a
  242. * value. Unlike `getOr`, in this method the `replacement` value is
  243. * "thunked" - that is to say that you don't pass a value to `getOrThunk`, you
  244. * pass a function which (if called) will **return** the `value` you want to
  245. * use.
  246. */
  247. getOrThunk(thunk) {
  248. return this.tag ? this.value : thunk();
  249. }
  250. /**
  251. * Get the value out of the inside of the `Optional` object, using a default
  252. * `replacement` value if the provided Optional object does not contain a
  253. * value.
  254. *
  255. * Unlike `or`, in this method the `replacement` value is "thunked" - that is
  256. * to say that you don't pass a value to `orThunk`, you pass a function which
  257. * (if called) will **return** the `value` you want to use.
  258. *
  259. * Unlike `getOrThunk`, in this method the `replacement` value is also
  260. * `Optional`, meaning that this method will always return an `Optional`.
  261. */
  262. orThunk(thunk) {
  263. return this.tag ? this : thunk();
  264. }
  265. /**
  266. * Get the value out of the inside of the `Optional` object, throwing an
  267. * exception if the provided `Optional` object does not contain a value.
  268. *
  269. * WARNING:
  270. * You should only be using this function if you know that the `Optional`
  271. * object **is not** empty (otherwise you're throwing exceptions in production
  272. * code, which is bad).
  273. *
  274. * In tests this is more acceptable.
  275. *
  276. * Prefer other methods to this, such as `.each`.
  277. */
  278. getOrDie(message) {
  279. if (!this.tag) {
  280. throw new Error(message !== null && message !== void 0 ? message : 'Called getOrDie on None');
  281. }
  282. else {
  283. return this.value;
  284. }
  285. }
  286. // --- Interop with null and undefined ---
  287. /**
  288. * Creates an `Optional` value from a nullable (or undefined-able) input.
  289. * Null, or undefined, is converted to `None`, and anything else is converted
  290. * to `Some`.
  291. */
  292. static from(value) {
  293. return isNonNullable(value) ? Optional.some(value) : Optional.none();
  294. }
  295. /**
  296. * Converts an `Optional` to a nullable type, by getting the value if it
  297. * exists, or returning `null` if it does not.
  298. */
  299. getOrNull() {
  300. return this.tag ? this.value : null;
  301. }
  302. /**
  303. * Converts an `Optional` to an undefined-able type, by getting the value if
  304. * it exists, or returning `undefined` if it does not.
  305. */
  306. getOrUndefined() {
  307. return this.value;
  308. }
  309. // --- Utilities ---
  310. /**
  311. * If the `Optional` contains a value, perform an action on that value.
  312. * Unlike the rest of the methods on this type, `.each` has side-effects. If
  313. * you want to transform an `Optional<T>` **into** something, then this is not
  314. * the method for you. If you want to use an `Optional<T>` to **do**
  315. * something, then this is the method for you - provided you're okay with not
  316. * doing anything in the case where the `Optional` doesn't have a value inside
  317. * it. If you're not sure whether your use-case fits into transforming
  318. * **into** something or **doing** something, check whether it has a return
  319. * value. If it does, you should be performing a transform.
  320. */
  321. each(worker) {
  322. if (this.tag) {
  323. worker(this.value);
  324. }
  325. }
  326. /**
  327. * Turn the `Optional` object into an array that contains all of the values
  328. * stored inside the `Optional`. In practice, this means the output will have
  329. * either 0 or 1 elements.
  330. */
  331. toArray() {
  332. return this.tag ? [this.value] : [];
  333. }
  334. /**
  335. * Turn the `Optional` object into a string for debugging or printing. Not
  336. * recommended for production code, but good for debugging. Also note that
  337. * these days an `Optional` object can be logged to the console directly, and
  338. * its inner value (if it exists) will be visible.
  339. */
  340. toString() {
  341. return this.tag ? `some(${this.value})` : 'none()';
  342. }
  343. }
  344. // Sneaky optimisation: every instance of Optional.none is identical, so just
  345. // reuse the same object
  346. Optional.singletonNone = new Optional(false);
  347. const nativeSlice = Array.prototype.slice;
  348. const nativeIndexOf = Array.prototype.indexOf;
  349. const nativePush = Array.prototype.push;
  350. const rawIndexOf = (ts, t) => nativeIndexOf.call(ts, t);
  351. const indexOf = (xs, x) => {
  352. // The rawIndexOf method does not wrap up in an option. This is for performance reasons.
  353. const r = rawIndexOf(xs, x);
  354. return r === -1 ? Optional.none() : Optional.some(r);
  355. };
  356. const contains$2 = (xs, x) => rawIndexOf(xs, x) > -1;
  357. const exists = (xs, pred) => {
  358. for (let i = 0, len = xs.length; i < len; i++) {
  359. const x = xs[i];
  360. if (pred(x, i)) {
  361. return true;
  362. }
  363. }
  364. return false;
  365. };
  366. const range$2 = (num, f) => {
  367. const r = [];
  368. for (let i = 0; i < num; i++) {
  369. r.push(f(i));
  370. }
  371. return r;
  372. };
  373. // It's a total micro optimisation, but these do make some difference.
  374. // Particularly for browsers other than Chrome.
  375. // - length caching
  376. // http://jsperf.com/browser-diet-jquery-each-vs-for-loop/69
  377. // - not using push
  378. // http://jsperf.com/array-direct-assignment-vs-push/2
  379. const chunk$1 = (array, size) => {
  380. const r = [];
  381. for (let i = 0; i < array.length; i += size) {
  382. const s = nativeSlice.call(array, i, i + size);
  383. r.push(s);
  384. }
  385. return r;
  386. };
  387. const map$2 = (xs, f) => {
  388. // pre-allocating array size when it's guaranteed to be known
  389. // http://jsperf.com/push-allocated-vs-dynamic/22
  390. const len = xs.length;
  391. const r = new Array(len);
  392. for (let i = 0; i < len; i++) {
  393. const x = xs[i];
  394. r[i] = f(x, i);
  395. }
  396. return r;
  397. };
  398. // Unwound implementing other functions in terms of each.
  399. // The code size is roughly the same, and it should allow for better optimisation.
  400. // const each = function<T, U>(xs: T[], f: (x: T, i?: number, xs?: T[]) => void): void {
  401. const each$1 = (xs, f) => {
  402. for (let i = 0, len = xs.length; i < len; i++) {
  403. const x = xs[i];
  404. f(x, i);
  405. }
  406. };
  407. const eachr = (xs, f) => {
  408. for (let i = xs.length - 1; i >= 0; i--) {
  409. const x = xs[i];
  410. f(x, i);
  411. }
  412. };
  413. const partition$3 = (xs, pred) => {
  414. const pass = [];
  415. const fail = [];
  416. for (let i = 0, len = xs.length; i < len; i++) {
  417. const x = xs[i];
  418. const arr = pred(x, i) ? pass : fail;
  419. arr.push(x);
  420. }
  421. return { pass, fail };
  422. };
  423. const filter$2 = (xs, pred) => {
  424. const r = [];
  425. for (let i = 0, len = xs.length; i < len; i++) {
  426. const x = xs[i];
  427. if (pred(x, i)) {
  428. r.push(x);
  429. }
  430. }
  431. return r;
  432. };
  433. const foldr = (xs, f, acc) => {
  434. eachr(xs, (x, i) => {
  435. acc = f(acc, x, i);
  436. });
  437. return acc;
  438. };
  439. const foldl = (xs, f, acc) => {
  440. each$1(xs, (x, i) => {
  441. acc = f(acc, x, i);
  442. });
  443. return acc;
  444. };
  445. const findUntil = (xs, pred, until) => {
  446. for (let i = 0, len = xs.length; i < len; i++) {
  447. const x = xs[i];
  448. if (pred(x, i)) {
  449. return Optional.some(x);
  450. }
  451. else if (until(x, i)) {
  452. break;
  453. }
  454. }
  455. return Optional.none();
  456. };
  457. const find$5 = (xs, pred) => {
  458. return findUntil(xs, pred, never);
  459. };
  460. const findIndex$1 = (xs, pred) => {
  461. for (let i = 0, len = xs.length; i < len; i++) {
  462. const x = xs[i];
  463. if (pred(x, i)) {
  464. return Optional.some(i);
  465. }
  466. }
  467. return Optional.none();
  468. };
  469. const flatten = (xs) => {
  470. // Note, this is possible because push supports multiple arguments:
  471. // http://jsperf.com/concat-push/6
  472. // Note that in the past, concat() would silently work (very slowly) for array-like objects.
  473. // With this change it will throw an error.
  474. const r = [];
  475. for (let i = 0, len = xs.length; i < len; ++i) {
  476. // Ensure that each value is an array itself
  477. if (!isArray(xs[i])) {
  478. throw new Error('Arr.flatten item ' + i + ' was not an array, input: ' + xs);
  479. }
  480. nativePush.apply(r, xs[i]);
  481. }
  482. return r;
  483. };
  484. const bind$3 = (xs, f) => flatten(map$2(xs, f));
  485. const forall = (xs, pred) => {
  486. for (let i = 0, len = xs.length; i < len; ++i) {
  487. const x = xs[i];
  488. if (pred(x, i) !== true) {
  489. return false;
  490. }
  491. }
  492. return true;
  493. };
  494. const reverse = (xs) => {
  495. const r = nativeSlice.call(xs, 0);
  496. r.reverse();
  497. return r;
  498. };
  499. const difference = (a1, a2) => filter$2(a1, (x) => !contains$2(a2, x));
  500. const mapToObject = (xs, f) => {
  501. const r = {};
  502. for (let i = 0, len = xs.length; i < len; i++) {
  503. const x = xs[i];
  504. r[String(x)] = f(x, i);
  505. }
  506. return r;
  507. };
  508. const pure$2 = (x) => [x];
  509. const sort = (xs, comparator) => {
  510. const copy = nativeSlice.call(xs, 0);
  511. copy.sort(comparator);
  512. return copy;
  513. };
  514. const get$i = (xs, i) => i >= 0 && i < xs.length ? Optional.some(xs[i]) : Optional.none();
  515. const head = (xs) => get$i(xs, 0);
  516. const last$1 = (xs) => get$i(xs, xs.length - 1);
  517. const from = isFunction(Array.from) ? Array.from : (x) => nativeSlice.call(x);
  518. const findMap = (arr, f) => {
  519. for (let i = 0; i < arr.length; i++) {
  520. const r = f(arr[i], i);
  521. if (r.isSome()) {
  522. return r;
  523. }
  524. }
  525. return Optional.none();
  526. };
  527. // There are many variations of Object iteration that are faster than the 'for-in' style:
  528. // http://jsperf.com/object-keys-iteration/107
  529. //
  530. // Use the native keys if it is available (IE9+), otherwise fall back to manually filtering
  531. const keys = Object.keys;
  532. const hasOwnProperty = Object.hasOwnProperty;
  533. const each = (obj, f) => {
  534. const props = keys(obj);
  535. for (let k = 0, len = props.length; k < len; k++) {
  536. const i = props[k];
  537. const x = obj[i];
  538. f(x, i);
  539. }
  540. };
  541. const map$1 = (obj, f) => {
  542. return tupleMap(obj, (x, i) => ({
  543. k: i,
  544. v: f(x, i)
  545. }));
  546. };
  547. const tupleMap = (obj, f) => {
  548. const r = {};
  549. each(obj, (x, i) => {
  550. const tuple = f(x, i);
  551. r[tuple.k] = tuple.v;
  552. });
  553. return r;
  554. };
  555. const objAcc = (r) => (x, i) => {
  556. r[i] = x;
  557. };
  558. const internalFilter = (obj, pred, onTrue, onFalse) => {
  559. each(obj, (x, i) => {
  560. (pred(x, i) ? onTrue : onFalse)(x, i);
  561. });
  562. };
  563. const bifilter = (obj, pred) => {
  564. const t = {};
  565. const f = {};
  566. internalFilter(obj, pred, objAcc(t), objAcc(f));
  567. return { t, f };
  568. };
  569. const filter$1 = (obj, pred) => {
  570. const t = {};
  571. internalFilter(obj, pred, objAcc(t), noop);
  572. return t;
  573. };
  574. const mapToArray = (obj, f) => {
  575. const r = [];
  576. each(obj, (value, name) => {
  577. r.push(f(value, name));
  578. });
  579. return r;
  580. };
  581. const find$4 = (obj, pred) => {
  582. const props = keys(obj);
  583. for (let k = 0, len = props.length; k < len; k++) {
  584. const i = props[k];
  585. const x = obj[i];
  586. if (pred(x, i, obj)) {
  587. return Optional.some(x);
  588. }
  589. }
  590. return Optional.none();
  591. };
  592. const values = (obj) => {
  593. return mapToArray(obj, identity);
  594. };
  595. const get$h = (obj, key) => {
  596. return has$2(obj, key) ? Optional.from(obj[key]) : Optional.none();
  597. };
  598. const has$2 = (obj, key) => hasOwnProperty.call(obj, key);
  599. const hasNonNullableKey = (obj, key) => has$2(obj, key) && obj[key] !== undefined && obj[key] !== null;
  600. /*
  601. * Generates a church encoded ADT (https://en.wikipedia.org/wiki/Church_encoding)
  602. * For syntax and use, look at the test code.
  603. */
  604. const generate$7 = (cases) => {
  605. // validation
  606. if (!isArray(cases)) {
  607. throw new Error('cases must be an array');
  608. }
  609. if (cases.length === 0) {
  610. throw new Error('there must be at least one case');
  611. }
  612. const constructors = [];
  613. // adt is mutated to add the individual cases
  614. const adt = {};
  615. each$1(cases, (acase, count) => {
  616. const keys$1 = keys(acase);
  617. // validation
  618. if (keys$1.length !== 1) {
  619. throw new Error('one and only one name per case');
  620. }
  621. const key = keys$1[0];
  622. const value = acase[key];
  623. // validation
  624. if (adt[key] !== undefined) {
  625. throw new Error('duplicate key detected:' + key);
  626. }
  627. else if (key === 'cata') {
  628. throw new Error('cannot have a case named cata (sorry)');
  629. }
  630. else if (!isArray(value)) {
  631. // this implicitly checks if acase is an object
  632. throw new Error('case arguments must be an array');
  633. }
  634. constructors.push(key);
  635. //
  636. // constructor for key
  637. //
  638. adt[key] = (...args) => {
  639. const argLength = args.length;
  640. // validation
  641. if (argLength !== value.length) {
  642. throw new Error('Wrong number of arguments to case ' + key + '. Expected ' + value.length + ' (' + value + '), got ' + argLength);
  643. }
  644. const match = (branches) => {
  645. const branchKeys = keys(branches);
  646. if (constructors.length !== branchKeys.length) {
  647. throw new Error('Wrong number of arguments to match. Expected: ' + constructors.join(',') + '\nActual: ' + branchKeys.join(','));
  648. }
  649. const allReqd = forall(constructors, (reqKey) => {
  650. return contains$2(branchKeys, reqKey);
  651. });
  652. if (!allReqd) {
  653. throw new Error('Not all branches were specified when using match. Specified: ' + branchKeys.join(', ') + '\nRequired: ' + constructors.join(', '));
  654. }
  655. return branches[key].apply(null, args);
  656. };
  657. //
  658. // the fold function for key
  659. //
  660. return {
  661. fold: (...foldArgs) => {
  662. // runtime validation
  663. if (foldArgs.length !== cases.length) {
  664. throw new Error('Wrong number of arguments to fold. Expected ' + cases.length + ', got ' + foldArgs.length);
  665. }
  666. const target = foldArgs[count];
  667. return target.apply(null, args);
  668. },
  669. match,
  670. // NOTE: Only for debugging.
  671. log: (label) => {
  672. // eslint-disable-next-line no-console
  673. console.log(label, {
  674. constructors,
  675. constructor: key,
  676. params: args
  677. });
  678. }
  679. };
  680. };
  681. });
  682. return adt;
  683. };
  684. const Adt = {
  685. generate: generate$7
  686. };
  687. const Cell = (initial) => {
  688. let value = initial;
  689. const get = () => {
  690. return value;
  691. };
  692. const set = (v) => {
  693. value = v;
  694. };
  695. return {
  696. get,
  697. set
  698. };
  699. };
  700. const nu$d = (baseFn) => {
  701. let data = Optional.none();
  702. let callbacks = [];
  703. /** map :: this LazyValue a -> (a -> b) -> LazyValue b */
  704. const map = (f) => nu$d((nCallback) => {
  705. get((data) => {
  706. nCallback(f(data));
  707. });
  708. });
  709. const get = (nCallback) => {
  710. if (isReady()) {
  711. call(nCallback);
  712. }
  713. else {
  714. callbacks.push(nCallback);
  715. }
  716. };
  717. const set = (x) => {
  718. if (!isReady()) {
  719. data = Optional.some(x);
  720. run(callbacks);
  721. callbacks = [];
  722. }
  723. };
  724. const isReady = () => data.isSome();
  725. const run = (cbs) => {
  726. each$1(cbs, call);
  727. };
  728. const call = (cb) => {
  729. data.each((x) => {
  730. setTimeout(() => {
  731. cb(x);
  732. }, 0);
  733. });
  734. };
  735. // Lazy values cache the value and kick off immediately
  736. baseFn(set);
  737. return {
  738. get,
  739. map,
  740. isReady
  741. };
  742. };
  743. const pure$1 = (a) => nu$d((callback) => {
  744. callback(a);
  745. });
  746. const LazyValue = {
  747. nu: nu$d,
  748. pure: pure$1
  749. };
  750. const errorReporter = (err) => {
  751. // we can not throw the error in the reporter as it will just be black-holed
  752. // by the Promise so we use a setTimeout to escape the Promise.
  753. setTimeout(() => {
  754. throw err;
  755. }, 0);
  756. };
  757. const make$8 = (run) => {
  758. const get = (callback) => {
  759. run().then(callback, errorReporter);
  760. };
  761. /** map :: this Future a -> (a -> b) -> Future b */
  762. const map = (fab) => {
  763. return make$8(() => run().then(fab));
  764. };
  765. /** bind :: this Future a -> (a -> Future b) -> Future b */
  766. const bind = (aFutureB) => {
  767. return make$8(() => run().then((v) => aFutureB(v).toPromise()));
  768. };
  769. /** anonBind :: this Future a -> Future b -> Future b
  770. * Returns a future, which evaluates the first future, ignores the result, then evaluates the second.
  771. */
  772. const anonBind = (futureB) => {
  773. return make$8(() => run().then(() => futureB.toPromise()));
  774. };
  775. const toLazy = () => {
  776. return LazyValue.nu(get);
  777. };
  778. const toCached = () => {
  779. let cache = null;
  780. return make$8(() => {
  781. if (cache === null) {
  782. cache = run();
  783. }
  784. return cache;
  785. });
  786. };
  787. const toPromise = run;
  788. return {
  789. map,
  790. bind,
  791. anonBind,
  792. toLazy,
  793. toCached,
  794. toPromise,
  795. get
  796. };
  797. };
  798. const nu$c = (baseFn) => {
  799. return make$8(() => new Promise(baseFn));
  800. };
  801. /** a -> Future a */
  802. const pure = (a) => {
  803. return make$8(() => Promise.resolve(a));
  804. };
  805. const Future = {
  806. nu: nu$c,
  807. pure
  808. };
  809. /**
  810. * Creates a new `Result<T, E>` that **does** contain a value.
  811. */
  812. const value$4 = (value) => {
  813. const applyHelper = (fn) => fn(value);
  814. const constHelper = constant$1(value);
  815. const outputHelper = () => output;
  816. const output = {
  817. // Debug info
  818. tag: true,
  819. inner: value,
  820. // Actual Result methods
  821. fold: (_onError, onValue) => onValue(value),
  822. isValue: always,
  823. isError: never,
  824. map: (mapper) => Result.value(mapper(value)),
  825. mapError: outputHelper,
  826. bind: applyHelper,
  827. exists: applyHelper,
  828. forall: applyHelper,
  829. getOr: constHelper,
  830. or: outputHelper,
  831. getOrThunk: constHelper,
  832. orThunk: outputHelper,
  833. getOrDie: constHelper,
  834. each: (fn) => {
  835. // Can't write the function inline because we don't want to return something by mistake
  836. fn(value);
  837. },
  838. toOptional: () => Optional.some(value),
  839. };
  840. return output;
  841. };
  842. /**
  843. * Creates a new `Result<T, E>` that **does not** contain a value, and therefore
  844. * contains an error.
  845. */
  846. const error$1 = (error) => {
  847. const outputHelper = () => output;
  848. const output = {
  849. // Debug info
  850. tag: false,
  851. inner: error,
  852. // Actual Result methods
  853. fold: (onError, _onValue) => onError(error),
  854. isValue: never,
  855. isError: always,
  856. map: outputHelper,
  857. mapError: (mapper) => Result.error(mapper(error)),
  858. bind: outputHelper,
  859. exists: never,
  860. forall: always,
  861. getOr: identity,
  862. or: identity,
  863. getOrThunk: apply$1,
  864. orThunk: apply$1,
  865. getOrDie: die(String(error)),
  866. each: noop,
  867. toOptional: Optional.none,
  868. };
  869. return output;
  870. };
  871. /**
  872. * Creates a new `Result<T, E>` from an `Optional<T>` and an `E`. If the
  873. * `Optional` contains a value, so will the outputted `Result`. If it does not,
  874. * the outputted `Result` will contain an error (and that error will be the
  875. * error passed in).
  876. */
  877. const fromOption = (optional, err) => optional.fold(() => error$1(err), value$4);
  878. const Result = {
  879. value: value$4,
  880. error: error$1,
  881. fromOption
  882. };
  883. const wrap$2 = (delegate) => {
  884. const toCached = () => {
  885. return wrap$2(delegate.toCached());
  886. };
  887. const bindFuture = (f) => {
  888. return wrap$2(delegate.bind((resA) => resA.fold((err) => (Future.pure(Result.error(err))), (a) => f(a))));
  889. };
  890. const bindResult = (f) => {
  891. return wrap$2(delegate.map((resA) => resA.bind(f)));
  892. };
  893. const mapResult = (f) => {
  894. return wrap$2(delegate.map((resA) => resA.map(f)));
  895. };
  896. const mapError = (f) => {
  897. return wrap$2(delegate.map((resA) => resA.mapError(f)));
  898. };
  899. const foldResult = (whenError, whenValue) => {
  900. return delegate.map((res) => res.fold(whenError, whenValue));
  901. };
  902. const withTimeout = (timeout, errorThunk) => {
  903. return wrap$2(Future.nu((callback) => {
  904. let timedOut = false;
  905. const timer = setTimeout(() => {
  906. timedOut = true;
  907. callback(Result.error(errorThunk()));
  908. }, timeout);
  909. delegate.get((result) => {
  910. if (!timedOut) {
  911. clearTimeout(timer);
  912. callback(result);
  913. }
  914. });
  915. }));
  916. };
  917. return {
  918. ...delegate,
  919. toCached,
  920. bindFuture,
  921. bindResult,
  922. mapResult,
  923. mapError,
  924. foldResult,
  925. withTimeout
  926. };
  927. };
  928. const nu$b = (worker) => {
  929. return wrap$2(Future.nu(worker));
  930. };
  931. const value$3 = (value) => {
  932. return wrap$2(Future.pure(Result.value(value)));
  933. };
  934. const error = (error) => {
  935. return wrap$2(Future.pure(Result.error(error)));
  936. };
  937. const fromResult$1 = (result) => {
  938. return wrap$2(Future.pure(result));
  939. };
  940. const fromFuture = (future) => {
  941. return wrap$2(future.map(Result.value));
  942. };
  943. const fromPromise = (promise) => {
  944. return nu$b((completer) => {
  945. promise.then((value) => {
  946. completer(Result.value(value));
  947. }, (error) => {
  948. completer(Result.error(error));
  949. });
  950. });
  951. };
  952. const FutureResult = {
  953. nu: nu$b,
  954. wrap: wrap$2,
  955. pure: value$3,
  956. value: value$3,
  957. error,
  958. fromResult: fromResult$1,
  959. fromFuture,
  960. fromPromise
  961. };
  962. // Use window object as the global if it's available since CSP will block script evals
  963. // eslint-disable-next-line @typescript-eslint/no-implied-eval
  964. const Global = typeof window !== 'undefined' ? window : Function('return this;')();
  965. /**
  966. * Adds two numbers, and wrap to a range.
  967. * If the result overflows to the right, snap to the left.
  968. * If the result overflows to the left, snap to the right.
  969. */
  970. const cycleBy = (value, delta, min, max) => {
  971. const r = value + delta;
  972. if (r > max) {
  973. return min;
  974. }
  975. else if (r < min) {
  976. return max;
  977. }
  978. else {
  979. return r;
  980. }
  981. };
  982. // ASSUMPTION: Max will always be larger than min
  983. const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
  984. // the division is meant to get a number between 0 and 1 for more information check this discussion: https://stackoverflow.com/questions/58285941/how-to-replace-math-random-with-crypto-getrandomvalues-and-keep-same-result
  985. const random = () => window.crypto.getRandomValues(new Uint32Array(1))[0] / 4294967295;
  986. /**
  987. * Generate a unique identifier.
  988. *
  989. * The unique portion of the identifier only contains an underscore
  990. * and digits, so that it may safely be used within HTML attributes.
  991. *
  992. * The chance of generating a non-unique identifier has been minimized
  993. * by combining the current time, a random number and a one-up counter.
  994. *
  995. * generate :: String -> String
  996. */
  997. let unique = 0;
  998. const generate$6 = (prefix) => {
  999. const date = new Date();
  1000. const time = date.getTime();
  1001. const random$1 = Math.floor(random() * 1000000000);
  1002. unique++;
  1003. return prefix + '_' + random$1 + unique + String(time);
  1004. };
  1005. const shallow$1 = (old, nu) => {
  1006. return nu;
  1007. };
  1008. const deep$1 = (old, nu) => {
  1009. const bothObjects = isPlainObject(old) && isPlainObject(nu);
  1010. return bothObjects ? deepMerge(old, nu) : nu;
  1011. };
  1012. const baseMerge = (merger) => {
  1013. return (...objects) => {
  1014. if (objects.length === 0) {
  1015. throw new Error(`Can't merge zero objects`);
  1016. }
  1017. const ret = {};
  1018. for (let j = 0; j < objects.length; j++) {
  1019. const curObject = objects[j];
  1020. for (const key in curObject) {
  1021. if (has$2(curObject, key)) {
  1022. ret[key] = merger(ret[key], curObject[key]);
  1023. }
  1024. }
  1025. }
  1026. return ret;
  1027. };
  1028. };
  1029. const deepMerge = baseMerge(deep$1);
  1030. const merge$1 = baseMerge(shallow$1);
  1031. /**
  1032. * **Is** the value stored inside this Optional object equal to `rhs`?
  1033. */
  1034. const is$1 = (lhs, rhs, comparator = tripleEquals) => lhs.exists((left) => comparator(left, rhs));
  1035. /**
  1036. * Are these two Optional objects equal? Equality here means either they're both
  1037. * `Some` (and the values are equal under the comparator) or they're both `None`.
  1038. */
  1039. const equals = (lhs, rhs, comparator = tripleEquals) => lift2(lhs, rhs, comparator).getOr(lhs.isNone() && rhs.isNone());
  1040. const cat = (arr) => {
  1041. const r = [];
  1042. const push = (x) => {
  1043. r.push(x);
  1044. };
  1045. for (let i = 0; i < arr.length; i++) {
  1046. arr[i].each(push);
  1047. }
  1048. return r;
  1049. };
  1050. const sequence = (arr) => {
  1051. const r = [];
  1052. for (let i = 0; i < arr.length; i++) {
  1053. const x = arr[i];
  1054. if (x.isSome()) {
  1055. r.push(x.getOrDie());
  1056. }
  1057. else {
  1058. return Optional.none();
  1059. }
  1060. }
  1061. return Optional.some(r);
  1062. };
  1063. /*
  1064. Notes on the lift functions:
  1065. - We used to have a generic liftN, but we were concerned about its type-safety, and the below variants were faster in microbenchmarks.
  1066. - The getOrDie calls are partial functions, but are checked beforehand. This is faster and more convenient (but less safe) than folds.
  1067. - && is used instead of a loop for simplicity and performance.
  1068. */
  1069. const lift2 = (oa, ob, f) => oa.isSome() && ob.isSome() ? Optional.some(f(oa.getOrDie(), ob.getOrDie())) : Optional.none();
  1070. const lift3 = (oa, ob, oc, f) => oa.isSome() && ob.isSome() && oc.isSome() ? Optional.some(f(oa.getOrDie(), ob.getOrDie(), oc.getOrDie())) : Optional.none();
  1071. const mapFrom = (a, f) => (a !== undefined && a !== null) ? Optional.some(f(a)) : Optional.none();
  1072. // This can help with type inference, by specifying the type param on the none case, so the caller doesn't have to.
  1073. const someIf = (b, a) => b ? Optional.some(a) : Optional.none();
  1074. // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
  1075. const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  1076. /** path :: ([String], JsObj?) -> JsObj */
  1077. const path$1 = (parts, scope) => {
  1078. let o = scope !== undefined && scope !== null ? scope : Global;
  1079. for (let i = 0; i < parts.length && o !== undefined && o !== null; ++i) {
  1080. o = o[parts[i]];
  1081. }
  1082. return o;
  1083. };
  1084. /** resolve :: (String, JsObj?) -> JsObj */
  1085. const resolve = (p, scope) => {
  1086. const parts = p.split('.');
  1087. return path$1(parts, scope);
  1088. };
  1089. Adt.generate([
  1090. { bothErrors: ['error1', 'error2'] },
  1091. { firstError: ['error1', 'value2'] },
  1092. { secondError: ['value1', 'error2'] },
  1093. { bothValues: ['value1', 'value2'] }
  1094. ]);
  1095. /** partition :: [Result a] -> { errors: [String], values: [a] } */
  1096. const partition$2 = (results) => {
  1097. const errors = [];
  1098. const values = [];
  1099. each$1(results, (result) => {
  1100. result.fold((err) => {
  1101. errors.push(err);
  1102. }, (value) => {
  1103. values.push(value);
  1104. });
  1105. });
  1106. return { errors, values };
  1107. };
  1108. const singleton$1 = (doRevoke) => {
  1109. const subject = Cell(Optional.none());
  1110. const revoke = () => subject.get().each(doRevoke);
  1111. const clear = () => {
  1112. revoke();
  1113. subject.set(Optional.none());
  1114. };
  1115. const isSet = () => subject.get().isSome();
  1116. const get = () => subject.get();
  1117. const set = (s) => {
  1118. revoke();
  1119. subject.set(Optional.some(s));
  1120. };
  1121. return {
  1122. clear,
  1123. isSet,
  1124. get,
  1125. set
  1126. };
  1127. };
  1128. const destroyable = () => singleton$1((s) => s.destroy());
  1129. const unbindable = () => singleton$1((s) => s.unbind());
  1130. const value$2 = () => {
  1131. const subject = singleton$1(noop);
  1132. const on = (f) => subject.get().each(f);
  1133. return {
  1134. ...subject,
  1135. on
  1136. };
  1137. };
  1138. const addToEnd = (str, suffix) => {
  1139. return str + suffix;
  1140. };
  1141. const removeFromStart = (str, numChars) => {
  1142. return str.substring(numChars);
  1143. };
  1144. const checkRange = (str, substr, start) => substr === '' || str.length >= substr.length && str.substr(start, start + substr.length) === substr;
  1145. const removeLeading = (str, prefix) => {
  1146. return startsWith(str, prefix) ? removeFromStart(str, prefix.length) : str;
  1147. };
  1148. const ensureTrailing = (str, suffix) => {
  1149. return endsWith(str, suffix) ? str : addToEnd(str, suffix);
  1150. };
  1151. const contains$1 = (str, substr, start = 0, end) => {
  1152. const idx = str.indexOf(substr, start);
  1153. if (idx !== -1) {
  1154. return isUndefined(end) ? true : idx + substr.length <= end;
  1155. }
  1156. else {
  1157. return false;
  1158. }
  1159. };
  1160. /** Does 'str' start with 'prefix'?
  1161. * Note: all strings start with the empty string.
  1162. * More formally, for all strings x, startsWith(x, "").
  1163. * This is so that for all strings x and y, startsWith(y + x, y)
  1164. */
  1165. const startsWith = (str, prefix) => {
  1166. return checkRange(str, prefix, 0);
  1167. };
  1168. /** Does 'str' end with 'suffix'?
  1169. * Note: all strings end with the empty string.
  1170. * More formally, for all strings x, endsWith(x, "").
  1171. * This is so that for all strings x and y, endsWith(x + y, y)
  1172. */
  1173. const endsWith = (str, suffix) => {
  1174. return checkRange(str, suffix, str.length - suffix.length);
  1175. };
  1176. const blank = (r) => (s) => s.replace(r, '');
  1177. /** removes all leading and trailing spaces */
  1178. const trim$1 = blank(/^\s+|\s+$/g);
  1179. const isNotEmpty = (s) => s.length > 0;
  1180. const isEmpty = (s) => !isNotEmpty(s);
  1181. const toFloat = (value) => {
  1182. const num = parseFloat(value);
  1183. return isNaN(num) ? Optional.none() : Optional.some(num);
  1184. };
  1185. // Run a function fn after rate ms. If another invocation occurs
  1186. // during the time it is waiting, update the arguments f will run
  1187. // with (but keep the current schedule)
  1188. const adaptable = (fn, rate) => {
  1189. let timer = null;
  1190. let args = null;
  1191. const cancel = () => {
  1192. if (!isNull(timer)) {
  1193. clearTimeout(timer);
  1194. timer = null;
  1195. args = null;
  1196. }
  1197. };
  1198. const throttle = (...newArgs) => {
  1199. args = newArgs;
  1200. if (isNull(timer)) {
  1201. timer = setTimeout(() => {
  1202. const tempArgs = args;
  1203. timer = null;
  1204. args = null;
  1205. fn.apply(null, tempArgs);
  1206. }, rate);
  1207. }
  1208. };
  1209. return {
  1210. cancel,
  1211. throttle
  1212. };
  1213. };
  1214. // Run a function fn after rate ms. If another invocation occurs
  1215. // during the time it is waiting, ignore it completely.
  1216. const first$1 = (fn, rate) => {
  1217. let timer = null;
  1218. const cancel = () => {
  1219. if (!isNull(timer)) {
  1220. clearTimeout(timer);
  1221. timer = null;
  1222. }
  1223. };
  1224. const throttle = (...args) => {
  1225. if (isNull(timer)) {
  1226. timer = setTimeout(() => {
  1227. timer = null;
  1228. fn.apply(null, args);
  1229. }, rate);
  1230. }
  1231. };
  1232. return {
  1233. cancel,
  1234. throttle
  1235. };
  1236. };
  1237. // Run a function fn after rate ms. If another invocation occurs
  1238. // during the time it is waiting, reschedule the function again
  1239. // with the new arguments.
  1240. const last = (fn, rate) => {
  1241. let timer = null;
  1242. const cancel = () => {
  1243. if (!isNull(timer)) {
  1244. clearTimeout(timer);
  1245. timer = null;
  1246. }
  1247. };
  1248. const throttle = (...args) => {
  1249. cancel();
  1250. timer = setTimeout(() => {
  1251. timer = null;
  1252. fn.apply(null, args);
  1253. }, rate);
  1254. };
  1255. return {
  1256. cancel,
  1257. throttle
  1258. };
  1259. };
  1260. const cached = (f) => {
  1261. let called = false;
  1262. let r;
  1263. return (...args) => {
  1264. if (!called) {
  1265. called = true;
  1266. r = f.apply(null, args);
  1267. }
  1268. return r;
  1269. };
  1270. };
  1271. const zeroWidth = '\uFEFF';
  1272. const nbsp = '\u00A0';
  1273. const fromHtml$2 = (html, scope) => {
  1274. const doc = scope || document;
  1275. const div = doc.createElement('div');
  1276. div.innerHTML = html;
  1277. if (!div.hasChildNodes() || div.childNodes.length > 1) {
  1278. const message = 'HTML does not have a single root node';
  1279. // eslint-disable-next-line no-console
  1280. console.error(message, html);
  1281. throw new Error(message);
  1282. }
  1283. return fromDom(div.childNodes[0]);
  1284. };
  1285. const fromTag = (tag, scope) => {
  1286. const doc = scope || document;
  1287. const node = doc.createElement(tag);
  1288. return fromDom(node);
  1289. };
  1290. const fromText = (text, scope) => {
  1291. const doc = scope || document;
  1292. const node = doc.createTextNode(text);
  1293. return fromDom(node);
  1294. };
  1295. const fromDom = (node) => {
  1296. // TODO: Consider removing this check, but left atm for safety
  1297. if (node === null || node === undefined) {
  1298. throw new Error('Node cannot be null or undefined');
  1299. }
  1300. return {
  1301. dom: node
  1302. };
  1303. };
  1304. const fromPoint = (docElm, x, y) => Optional.from(docElm.dom.elementFromPoint(x, y)).map(fromDom);
  1305. // tslint:disable-next-line:variable-name
  1306. const SugarElement = {
  1307. fromHtml: fromHtml$2,
  1308. fromTag,
  1309. fromText,
  1310. fromDom,
  1311. fromPoint
  1312. };
  1313. // NOTE: Mutates the range.
  1314. const setStart = (rng, situ) => {
  1315. situ.fold((e) => {
  1316. rng.setStartBefore(e.dom);
  1317. }, (e, o) => {
  1318. rng.setStart(e.dom, o);
  1319. }, (e) => {
  1320. rng.setStartAfter(e.dom);
  1321. });
  1322. };
  1323. const setFinish = (rng, situ) => {
  1324. situ.fold((e) => {
  1325. rng.setEndBefore(e.dom);
  1326. }, (e, o) => {
  1327. rng.setEnd(e.dom, o);
  1328. }, (e) => {
  1329. rng.setEndAfter(e.dom);
  1330. });
  1331. };
  1332. const relativeToNative = (win, startSitu, finishSitu) => {
  1333. const range = win.document.createRange();
  1334. setStart(range, startSitu);
  1335. setFinish(range, finishSitu);
  1336. return range;
  1337. };
  1338. const exactToNative = (win, start, soffset, finish, foffset) => {
  1339. const rng = win.document.createRange();
  1340. rng.setStart(start.dom, soffset);
  1341. rng.setEnd(finish.dom, foffset);
  1342. return rng;
  1343. };
  1344. const toRect = (rect) => ({
  1345. left: rect.left,
  1346. top: rect.top,
  1347. right: rect.right,
  1348. bottom: rect.bottom,
  1349. width: rect.width,
  1350. height: rect.height
  1351. });
  1352. const getFirstRect$1 = (rng) => {
  1353. const rects = rng.getClientRects();
  1354. // ASSUMPTION: The first rectangle is the start of the selection
  1355. const rect = rects.length > 0 ? rects[0] : rng.getBoundingClientRect();
  1356. return rect.width > 0 || rect.height > 0 ? Optional.some(rect).map(toRect) : Optional.none();
  1357. };
  1358. const getBounds$3 = (rng) => {
  1359. const rect = rng.getBoundingClientRect();
  1360. return rect.width > 0 || rect.height > 0 ? Optional.some(rect).map(toRect) : Optional.none();
  1361. };
  1362. const adt$a = Adt.generate([
  1363. { ltr: ['start', 'soffset', 'finish', 'foffset'] },
  1364. { rtl: ['start', 'soffset', 'finish', 'foffset'] }
  1365. ]);
  1366. const fromRange = (win, type, range) => type(SugarElement.fromDom(range.startContainer), range.startOffset, SugarElement.fromDom(range.endContainer), range.endOffset);
  1367. const getRanges = (win, selection) => selection.match({
  1368. domRange: (rng) => {
  1369. return {
  1370. ltr: constant$1(rng),
  1371. rtl: Optional.none
  1372. };
  1373. },
  1374. relative: (startSitu, finishSitu) => {
  1375. return {
  1376. ltr: cached(() => relativeToNative(win, startSitu, finishSitu)),
  1377. rtl: cached(() => Optional.some(relativeToNative(win, finishSitu, startSitu)))
  1378. };
  1379. },
  1380. exact: (start, soffset, finish, foffset) => {
  1381. return {
  1382. ltr: cached(() => exactToNative(win, start, soffset, finish, foffset)),
  1383. rtl: cached(() => Optional.some(exactToNative(win, finish, foffset, start, soffset)))
  1384. };
  1385. }
  1386. });
  1387. const doDiagnose = (win, ranges) => {
  1388. // If we cannot create a ranged selection from start > finish, it could be RTL
  1389. const rng = ranges.ltr();
  1390. if (rng.collapsed) {
  1391. // Let's check if it's RTL ... if it is, then reversing the direction will not be collapsed
  1392. const reversed = ranges.rtl().filter((rev) => rev.collapsed === false);
  1393. return reversed.map((rev) =>
  1394. // We need to use "reversed" here, because the original only has one point (collapsed)
  1395. adt$a.rtl(SugarElement.fromDom(rev.endContainer), rev.endOffset, SugarElement.fromDom(rev.startContainer), rev.startOffset)).getOrThunk(() => fromRange(win, adt$a.ltr, rng));
  1396. }
  1397. else {
  1398. return fromRange(win, adt$a.ltr, rng);
  1399. }
  1400. };
  1401. const diagnose = (win, selection) => {
  1402. const ranges = getRanges(win, selection);
  1403. return doDiagnose(win, ranges);
  1404. };
  1405. const asLtrRange = (win, selection) => {
  1406. const diagnosis = diagnose(win, selection);
  1407. return diagnosis.match({
  1408. ltr: (start, soffset, finish, foffset) => {
  1409. const rng = win.document.createRange();
  1410. rng.setStart(start.dom, soffset);
  1411. rng.setEnd(finish.dom, foffset);
  1412. return rng;
  1413. },
  1414. rtl: (start, soffset, finish, foffset) => {
  1415. // NOTE: Reversing start and finish
  1416. const rng = win.document.createRange();
  1417. rng.setStart(finish.dom, foffset);
  1418. rng.setEnd(start.dom, soffset);
  1419. return rng;
  1420. }
  1421. });
  1422. };
  1423. adt$a.ltr;
  1424. adt$a.rtl;
  1425. const DOCUMENT = 9;
  1426. const DOCUMENT_FRAGMENT = 11;
  1427. const ELEMENT = 1;
  1428. const TEXT = 3;
  1429. const is = (element, selector) => {
  1430. const dom = element.dom;
  1431. if (dom.nodeType !== ELEMENT) {
  1432. return false;
  1433. }
  1434. else {
  1435. const elem = dom;
  1436. if (elem.matches !== undefined) {
  1437. return elem.matches(selector);
  1438. }
  1439. else if (elem.msMatchesSelector !== undefined) {
  1440. return elem.msMatchesSelector(selector);
  1441. }
  1442. else if (elem.webkitMatchesSelector !== undefined) {
  1443. return elem.webkitMatchesSelector(selector);
  1444. }
  1445. else if (elem.mozMatchesSelector !== undefined) {
  1446. // cast to any as mozMatchesSelector doesn't exist in TS DOM lib
  1447. return elem.mozMatchesSelector(selector);
  1448. }
  1449. else {
  1450. throw new Error('Browser lacks native selectors');
  1451. } // unfortunately we can't throw this on startup :(
  1452. }
  1453. };
  1454. const bypassSelector = (dom) =>
  1455. // Only elements, documents and shadow roots support querySelector
  1456. // shadow root element type is DOCUMENT_FRAGMENT
  1457. dom.nodeType !== ELEMENT && dom.nodeType !== DOCUMENT && dom.nodeType !== DOCUMENT_FRAGMENT ||
  1458. // IE fix for complex queries on empty nodes: http://jsfiddle.net/spyder/fv9ptr5L/
  1459. dom.childElementCount === 0;
  1460. const all$3 = (selector, scope) => {
  1461. const base = scope === undefined ? document : scope.dom;
  1462. return bypassSelector(base) ? [] : map$2(base.querySelectorAll(selector), SugarElement.fromDom);
  1463. };
  1464. const one = (selector, scope) => {
  1465. const base = scope === undefined ? document : scope.dom;
  1466. return bypassSelector(base) ? Optional.none() : Optional.from(base.querySelector(selector)).map(SugarElement.fromDom);
  1467. };
  1468. const eq = (e1, e2) => e1.dom === e2.dom;
  1469. // Returns: true if node e1 contains e2, otherwise false.
  1470. // (returns false if e1===e2: A node does not contain itself).
  1471. const contains = (e1, e2) => {
  1472. const d1 = e1.dom;
  1473. const d2 = e2.dom;
  1474. return d1 === d2 ? false : d1.contains(d2);
  1475. };
  1476. const DeviceType = (os, browser, userAgent, mediaMatch) => {
  1477. const isiPad = os.isiOS() && /ipad/i.test(userAgent) === true;
  1478. const isiPhone = os.isiOS() && !isiPad;
  1479. const isMobile = os.isiOS() || os.isAndroid();
  1480. const isTouch = isMobile || mediaMatch('(pointer:coarse)');
  1481. const isTablet = isiPad || !isiPhone && isMobile && mediaMatch('(min-device-width:768px)');
  1482. const isPhone = isiPhone || isMobile && !isTablet;
  1483. const iOSwebview = browser.isSafari() && os.isiOS() && /safari/i.test(userAgent) === false;
  1484. const isDesktop = !isPhone && !isTablet && !iOSwebview;
  1485. return {
  1486. isiPad: constant$1(isiPad),
  1487. isiPhone: constant$1(isiPhone),
  1488. isTablet: constant$1(isTablet),
  1489. isPhone: constant$1(isPhone),
  1490. isTouch: constant$1(isTouch),
  1491. isAndroid: os.isAndroid,
  1492. isiOS: os.isiOS,
  1493. isWebView: constant$1(iOSwebview),
  1494. isDesktop: constant$1(isDesktop)
  1495. };
  1496. };
  1497. const firstMatch = (regexes, s) => {
  1498. for (let i = 0; i < regexes.length; i++) {
  1499. const x = regexes[i];
  1500. if (x.test(s)) {
  1501. return x;
  1502. }
  1503. }
  1504. return undefined;
  1505. };
  1506. const find$3 = (regexes, agent) => {
  1507. const r = firstMatch(regexes, agent);
  1508. if (!r) {
  1509. return { major: 0, minor: 0 };
  1510. }
  1511. const group = (i) => {
  1512. return Number(agent.replace(r, '$' + i));
  1513. };
  1514. return nu$a(group(1), group(2));
  1515. };
  1516. const detect$4 = (versionRegexes, agent) => {
  1517. const cleanedAgent = String(agent).toLowerCase();
  1518. if (versionRegexes.length === 0) {
  1519. return unknown$3();
  1520. }
  1521. return find$3(versionRegexes, cleanedAgent);
  1522. };
  1523. const unknown$3 = () => {
  1524. return nu$a(0, 0);
  1525. };
  1526. const nu$a = (major, minor) => {
  1527. return { major, minor };
  1528. };
  1529. const Version = {
  1530. nu: nu$a,
  1531. detect: detect$4,
  1532. unknown: unknown$3
  1533. };
  1534. const detectBrowser$1 = (browsers, userAgentData) => {
  1535. return findMap(userAgentData.brands, (uaBrand) => {
  1536. const lcBrand = uaBrand.brand.toLowerCase();
  1537. return find$5(browsers, (browser) => { var _a; return lcBrand === ((_a = browser.brand) === null || _a === void 0 ? void 0 : _a.toLowerCase()); })
  1538. .map((info) => ({
  1539. current: info.name,
  1540. version: Version.nu(parseInt(uaBrand.version, 10), 0)
  1541. }));
  1542. });
  1543. };
  1544. const detect$3 = (candidates, userAgent) => {
  1545. const agent = String(userAgent).toLowerCase();
  1546. return find$5(candidates, (candidate) => {
  1547. return candidate.search(agent);
  1548. });
  1549. };
  1550. // They (browser and os) are the same at the moment, but they might
  1551. // not stay that way.
  1552. const detectBrowser = (browsers, userAgent) => {
  1553. return detect$3(browsers, userAgent).map((browser) => {
  1554. const version = Version.detect(browser.versionRegexes, userAgent);
  1555. return {
  1556. current: browser.name,
  1557. version
  1558. };
  1559. });
  1560. };
  1561. const detectOs = (oses, userAgent) => {
  1562. return detect$3(oses, userAgent).map((os) => {
  1563. const version = Version.detect(os.versionRegexes, userAgent);
  1564. return {
  1565. current: os.name,
  1566. version
  1567. };
  1568. });
  1569. };
  1570. const normalVersionRegex = /.*?version\/\ ?([0-9]+)\.([0-9]+).*/;
  1571. const checkContains = (target) => {
  1572. return (uastring) => {
  1573. return contains$1(uastring, target);
  1574. };
  1575. };
  1576. const browsers = [
  1577. // This is legacy Edge
  1578. {
  1579. name: 'Edge',
  1580. versionRegexes: [/.*?edge\/ ?([0-9]+)\.([0-9]+)$/],
  1581. search: (uastring) => {
  1582. return contains$1(uastring, 'edge/') && contains$1(uastring, 'chrome') && contains$1(uastring, 'safari') && contains$1(uastring, 'applewebkit');
  1583. }
  1584. },
  1585. // This is Google Chrome and Chromium Edge
  1586. {
  1587. name: 'Chromium',
  1588. brand: 'Chromium',
  1589. versionRegexes: [/.*?chrome\/([0-9]+)\.([0-9]+).*/, normalVersionRegex],
  1590. search: (uastring) => {
  1591. return contains$1(uastring, 'chrome') && !contains$1(uastring, 'chromeframe');
  1592. }
  1593. },
  1594. {
  1595. name: 'IE',
  1596. versionRegexes: [/.*?msie\ ?([0-9]+)\.([0-9]+).*/, /.*?rv:([0-9]+)\.([0-9]+).*/],
  1597. search: (uastring) => {
  1598. return contains$1(uastring, 'msie') || contains$1(uastring, 'trident');
  1599. }
  1600. },
  1601. // INVESTIGATE: Is this still the Opera user agent?
  1602. {
  1603. name: 'Opera',
  1604. versionRegexes: [normalVersionRegex, /.*?opera\/([0-9]+)\.([0-9]+).*/],
  1605. search: checkContains('opera')
  1606. },
  1607. {
  1608. name: 'Firefox',
  1609. versionRegexes: [/.*?firefox\/\ ?([0-9]+)\.([0-9]+).*/],
  1610. search: checkContains('firefox')
  1611. },
  1612. {
  1613. name: 'Safari',
  1614. versionRegexes: [normalVersionRegex, /.*?cpu os ([0-9]+)_([0-9]+).*/],
  1615. search: (uastring) => {
  1616. return (contains$1(uastring, 'safari') || contains$1(uastring, 'mobile/')) && contains$1(uastring, 'applewebkit');
  1617. }
  1618. }
  1619. ];
  1620. const oses = [
  1621. {
  1622. name: 'Windows',
  1623. search: checkContains('win'),
  1624. versionRegexes: [/.*?windows\ nt\ ?([0-9]+)\.([0-9]+).*/]
  1625. },
  1626. {
  1627. name: 'iOS',
  1628. search: (uastring) => {
  1629. return contains$1(uastring, 'iphone') || contains$1(uastring, 'ipad');
  1630. },
  1631. versionRegexes: [/.*?version\/\ ?([0-9]+)\.([0-9]+).*/, /.*cpu os ([0-9]+)_([0-9]+).*/, /.*cpu iphone os ([0-9]+)_([0-9]+).*/]
  1632. },
  1633. {
  1634. name: 'Android',
  1635. search: checkContains('android'),
  1636. versionRegexes: [/.*?android\ ?([0-9]+)\.([0-9]+).*/]
  1637. },
  1638. {
  1639. name: 'macOS',
  1640. search: checkContains('mac os x'),
  1641. versionRegexes: [/.*?mac\ os\ x\ ?([0-9]+)_([0-9]+).*/]
  1642. },
  1643. {
  1644. name: 'Linux',
  1645. search: checkContains('linux'),
  1646. versionRegexes: []
  1647. },
  1648. { name: 'Solaris',
  1649. search: checkContains('sunos'),
  1650. versionRegexes: []
  1651. },
  1652. {
  1653. name: 'FreeBSD',
  1654. search: checkContains('freebsd'),
  1655. versionRegexes: []
  1656. },
  1657. {
  1658. name: 'ChromeOS',
  1659. search: checkContains('cros'),
  1660. versionRegexes: [/.*?chrome\/([0-9]+)\.([0-9]+).*/]
  1661. }
  1662. ];
  1663. const PlatformInfo = {
  1664. browsers: constant$1(browsers),
  1665. oses: constant$1(oses)
  1666. };
  1667. const edge = 'Edge';
  1668. const chromium = 'Chromium';
  1669. const ie = 'IE';
  1670. const opera = 'Opera';
  1671. const firefox = 'Firefox';
  1672. const safari = 'Safari';
  1673. const unknown$2 = () => {
  1674. return nu$9({
  1675. current: undefined,
  1676. version: Version.unknown()
  1677. });
  1678. };
  1679. const nu$9 = (info) => {
  1680. const current = info.current;
  1681. const version = info.version;
  1682. const isBrowser = (name) => () => current === name;
  1683. return {
  1684. current,
  1685. version,
  1686. isEdge: isBrowser(edge),
  1687. isChromium: isBrowser(chromium),
  1688. // NOTE: isIe just looks too weird
  1689. isIE: isBrowser(ie),
  1690. isOpera: isBrowser(opera),
  1691. isFirefox: isBrowser(firefox),
  1692. isSafari: isBrowser(safari)
  1693. };
  1694. };
  1695. const Browser = {
  1696. unknown: unknown$2,
  1697. nu: nu$9,
  1698. edge: constant$1(edge),
  1699. chromium: constant$1(chromium),
  1700. ie: constant$1(ie),
  1701. opera: constant$1(opera),
  1702. firefox: constant$1(firefox),
  1703. safari: constant$1(safari)
  1704. };
  1705. const windows = 'Windows';
  1706. const ios = 'iOS';
  1707. const android = 'Android';
  1708. const linux = 'Linux';
  1709. const macos = 'macOS';
  1710. const solaris = 'Solaris';
  1711. const freebsd = 'FreeBSD';
  1712. const chromeos = 'ChromeOS';
  1713. // Though there is a bit of dupe with this and Browser, trying to
  1714. // reuse code makes it much harder to follow and change.
  1715. const unknown$1 = () => {
  1716. return nu$8({
  1717. current: undefined,
  1718. version: Version.unknown()
  1719. });
  1720. };
  1721. const nu$8 = (info) => {
  1722. const current = info.current;
  1723. const version = info.version;
  1724. const isOS = (name) => () => current === name;
  1725. return {
  1726. current,
  1727. version,
  1728. isWindows: isOS(windows),
  1729. // TODO: Fix capitalisation
  1730. isiOS: isOS(ios),
  1731. isAndroid: isOS(android),
  1732. isMacOS: isOS(macos),
  1733. isLinux: isOS(linux),
  1734. isSolaris: isOS(solaris),
  1735. isFreeBSD: isOS(freebsd),
  1736. isChromeOS: isOS(chromeos)
  1737. };
  1738. };
  1739. const OperatingSystem = {
  1740. unknown: unknown$1,
  1741. nu: nu$8,
  1742. windows: constant$1(windows),
  1743. ios: constant$1(ios),
  1744. android: constant$1(android),
  1745. linux: constant$1(linux),
  1746. macos: constant$1(macos),
  1747. solaris: constant$1(solaris),
  1748. freebsd: constant$1(freebsd),
  1749. chromeos: constant$1(chromeos)
  1750. };
  1751. const detect$2 = (userAgent, userAgentDataOpt, mediaMatch) => {
  1752. const browsers = PlatformInfo.browsers();
  1753. const oses = PlatformInfo.oses();
  1754. const browser = userAgentDataOpt.bind((userAgentData) => detectBrowser$1(browsers, userAgentData))
  1755. .orThunk(() => detectBrowser(browsers, userAgent))
  1756. .fold(Browser.unknown, Browser.nu);
  1757. const os = detectOs(oses, userAgent).fold(OperatingSystem.unknown, OperatingSystem.nu);
  1758. const deviceType = DeviceType(os, browser, userAgent, mediaMatch);
  1759. return {
  1760. browser,
  1761. os,
  1762. deviceType
  1763. };
  1764. };
  1765. const PlatformDetection = {
  1766. detect: detect$2
  1767. };
  1768. const mediaMatch = (query) => window.matchMedia(query).matches;
  1769. // IMPORTANT: Must be in a thunk, otherwise rollup thinks calling this immediately
  1770. // causes side effects and won't tree shake this away
  1771. // Note: navigator.userAgentData is not part of the native typescript types yet
  1772. let platform = cached(() => PlatformDetection.detect(window.navigator.userAgent, Optional.from((window.navigator.userAgentData)), mediaMatch));
  1773. const detect$1 = () => platform();
  1774. const unsafe = (name, scope) => {
  1775. return resolve(name, scope);
  1776. };
  1777. const getOrDie$1 = (name, scope) => {
  1778. const actual = unsafe(name, scope);
  1779. if (actual === undefined || actual === null) {
  1780. throw new Error(name + ' not available on this browser');
  1781. }
  1782. return actual;
  1783. };
  1784. const getPrototypeOf$1 = Object.getPrototypeOf;
  1785. /*
  1786. * IE9 and above
  1787. *
  1788. * MDN no use on this one, but here's the link anyway:
  1789. * https://developer.mozilla.org/en/docs/Web/API/HTMLElement
  1790. */
  1791. const sandHTMLElement = (scope) => {
  1792. return getOrDie$1('HTMLElement', scope);
  1793. };
  1794. const isPrototypeOf = (x) => {
  1795. // use Resolve to get the window object for x and just return undefined if it can't find it.
  1796. // undefined scope later triggers using the global window.
  1797. const scope = resolve('ownerDocument.defaultView', x);
  1798. // TINY-7374: We can't rely on looking at the owner window HTMLElement as the element may have
  1799. // been constructed in a different window and then appended to the current window document.
  1800. return isObject(x) && (sandHTMLElement(scope).prototype.isPrototypeOf(x) || /^HTML\w*Element$/.test(getPrototypeOf$1(x).constructor.name));
  1801. };
  1802. const name$3 = (element) => {
  1803. const r = element.dom.nodeName;
  1804. return r.toLowerCase();
  1805. };
  1806. const type$1 = (element) => element.dom.nodeType;
  1807. const isType = (t) => (element) => type$1(element) === t;
  1808. const isHTMLElement = (element) => isElement$1(element) && isPrototypeOf(element.dom);
  1809. const isElement$1 = isType(ELEMENT);
  1810. const isText = isType(TEXT);
  1811. const isDocument = isType(DOCUMENT);
  1812. const isDocumentFragment = isType(DOCUMENT_FRAGMENT);
  1813. const isTag = (tag) => (e) => isElement$1(e) && name$3(e) === tag;
  1814. /**
  1815. * The document associated with the current element
  1816. * NOTE: this will throw if the owner is null.
  1817. */
  1818. const owner$4 = (element) => SugarElement.fromDom(element.dom.ownerDocument);
  1819. /**
  1820. * If the element is a document, return it. Otherwise, return its ownerDocument.
  1821. * @param dos
  1822. */
  1823. const documentOrOwner = (dos) => isDocument(dos) ? dos : owner$4(dos);
  1824. const documentElement = (element) => SugarElement.fromDom(documentOrOwner(element).dom.documentElement);
  1825. /**
  1826. * The window element associated with the element
  1827. * NOTE: this will throw if the defaultView is null.
  1828. */
  1829. const defaultView = (element) => SugarElement.fromDom(documentOrOwner(element).dom.defaultView);
  1830. const parent = (element) => Optional.from(element.dom.parentNode).map(SugarElement.fromDom);
  1831. // Cast down to just be SugarElement<Node>
  1832. const parentNode = (element) => parent(element);
  1833. const parentElement = (element) => Optional.from(element.dom.parentElement).map(SugarElement.fromDom);
  1834. const parents = (element, isRoot) => {
  1835. const stop = isFunction(isRoot) ? isRoot : never;
  1836. // This is used a *lot* so it needs to be performant, not recursive
  1837. let dom = element.dom;
  1838. const ret = [];
  1839. while (dom.parentNode !== null && dom.parentNode !== undefined) {
  1840. const rawParent = dom.parentNode;
  1841. const p = SugarElement.fromDom(rawParent);
  1842. ret.push(p);
  1843. if (stop(p) === true) {
  1844. break;
  1845. }
  1846. else {
  1847. dom = rawParent;
  1848. }
  1849. }
  1850. return ret;
  1851. };
  1852. const offsetParent = (element) => Optional.from(element.dom.offsetParent).map(SugarElement.fromDom);
  1853. const prevSibling = (element) => Optional.from(element.dom.previousSibling).map(SugarElement.fromDom);
  1854. const nextSibling = (element) => Optional.from(element.dom.nextSibling).map(SugarElement.fromDom);
  1855. const children = (element) => map$2(element.dom.childNodes, SugarElement.fromDom);
  1856. const child$2 = (element, index) => {
  1857. const cs = element.dom.childNodes;
  1858. return Optional.from(cs[index]).map(SugarElement.fromDom);
  1859. };
  1860. const firstChild = (element) => child$2(element, 0);
  1861. const spot = (element, offset) => ({
  1862. element,
  1863. offset
  1864. });
  1865. const leaf = (element, offset) => {
  1866. const cs = children(element);
  1867. return cs.length > 0 && offset < cs.length ? spot(cs[offset], 0) : spot(element, offset);
  1868. };
  1869. const makeRange = (start, soffset, finish, foffset) => {
  1870. const doc = owner$4(start);
  1871. // TODO: We need to think about a better place to put native range creation code. Does it even belong in sugar?
  1872. // Could the `Compare` checks (node.compareDocumentPosition) handle these situations better?
  1873. const rng = doc.dom.createRange();
  1874. rng.setStart(start.dom, soffset);
  1875. rng.setEnd(finish.dom, foffset);
  1876. return rng;
  1877. };
  1878. const after$2 = (start, soffset, finish, foffset) => {
  1879. const r = makeRange(start, soffset, finish, foffset);
  1880. const same = eq(start, finish) && soffset === foffset;
  1881. return r.collapsed && !same;
  1882. };
  1883. /**
  1884. * Is the element a ShadowRoot?
  1885. *
  1886. * Note: this is insufficient to test if any element is a shadow root, but it is sufficient to differentiate between
  1887. * a Document and a ShadowRoot.
  1888. */
  1889. const isShadowRoot = (dos) => isDocumentFragment(dos) && isNonNullable(dos.dom.host);
  1890. const getRootNode = (e) => SugarElement.fromDom(e.dom.getRootNode());
  1891. /** Where content needs to go. ShadowRoot or document body */
  1892. const getContentContainer = (dos) =>
  1893. // Can't use SugarBody.body without causing a circular module reference (since SugarBody.inBody uses SugarShadowDom)
  1894. isShadowRoot(dos) ? dos : SugarElement.fromDom(documentOrOwner(dos).dom.body);
  1895. /** Is this element either a ShadowRoot or a descendent of a ShadowRoot. */
  1896. const isInShadowRoot = (e) => getShadowRoot(e).isSome();
  1897. /** If this element is in a ShadowRoot, return it. */
  1898. const getShadowRoot = (e) => {
  1899. const r = getRootNode(e);
  1900. return isShadowRoot(r) ? Optional.some(r) : Optional.none();
  1901. };
  1902. /** Return the host of a ShadowRoot.
  1903. *
  1904. * This function will throw if Shadow DOM is unsupported in the browser, or if the host is null.
  1905. * If you actually have a ShadowRoot, this shouldn't happen.
  1906. */
  1907. const getShadowHost = (e) => SugarElement.fromDom(e.dom.host);
  1908. /**
  1909. * When Events bubble up through a ShadowRoot, the browser changes the target to be the shadow host.
  1910. * This function gets the "original" event target if possible.
  1911. * This only works if the shadow tree is open - if the shadow tree is closed, event.target is returned.
  1912. * See: https://developers.google.com/web/fundamentals/web-components/shadowdom#events
  1913. */
  1914. const getOriginalEventTarget = (event) => {
  1915. if (isNonNullable(event.target)) {
  1916. const el = SugarElement.fromDom(event.target);
  1917. if (isElement$1(el) && isOpenShadowHost(el)) {
  1918. // When target element is inside Shadow DOM we need to take first element from composedPath
  1919. // otherwise we'll get Shadow Root parent, not actual target element.
  1920. if (event.composed && event.composedPath) {
  1921. const composedPath = event.composedPath();
  1922. if (composedPath) {
  1923. return head(composedPath);
  1924. }
  1925. }
  1926. }
  1927. }
  1928. return Optional.from(event.target);
  1929. };
  1930. /** Return true if the element is a host of an open shadow root.
  1931. * Return false if the element is a host of a closed shadow root, or if the element is not a host.
  1932. */
  1933. const isOpenShadowHost = (element) => isNonNullable(element.dom.shadowRoot);
  1934. const mkEvent = (target, x, y, stop, prevent, kill, raw) => ({
  1935. target,
  1936. x,
  1937. y,
  1938. stop,
  1939. prevent,
  1940. kill,
  1941. raw
  1942. });
  1943. /** Wraps an Event in an EventArgs structure.
  1944. * The returned EventArgs structure has its target set to the "original" target if possible.
  1945. * See SugarShadowDom.getOriginalEventTarget
  1946. */
  1947. const fromRawEvent$1 = (rawEvent) => {
  1948. const target = SugarElement.fromDom(getOriginalEventTarget(rawEvent).getOr(rawEvent.target));
  1949. const stop = () => rawEvent.stopPropagation();
  1950. const prevent = () => rawEvent.preventDefault();
  1951. const kill = compose(prevent, stop); // more of a sequence than a compose, but same effect
  1952. // FIX: Don't just expose the raw event. Need to identify what needs standardisation.
  1953. return mkEvent(target, rawEvent.clientX, rawEvent.clientY, stop, prevent, kill, rawEvent);
  1954. };
  1955. const handle = (filter, handler) => (rawEvent) => {
  1956. if (filter(rawEvent)) {
  1957. handler(fromRawEvent$1(rawEvent));
  1958. }
  1959. };
  1960. const binder = (element, event, filter, handler, useCapture) => {
  1961. const wrapped = handle(filter, handler);
  1962. // IE9 minimum
  1963. element.dom.addEventListener(event, wrapped, useCapture);
  1964. return {
  1965. unbind: curry(unbind, element, event, wrapped, useCapture)
  1966. };
  1967. };
  1968. const bind$2 = (element, event, filter, handler) => binder(element, event, filter, handler, false);
  1969. const capture$1 = (element, event, filter, handler) => binder(element, event, filter, handler, true);
  1970. const unbind = (element, event, handler, useCapture) => {
  1971. // IE9 minimum
  1972. element.dom.removeEventListener(event, handler, useCapture);
  1973. };
  1974. const filter = always; // no filter on plain DomEvents
  1975. const bind$1 = (element, event, handler) => bind$2(element, event, filter, handler);
  1976. const capture = (element, event, handler) => capture$1(element, event, filter, handler);
  1977. const fromRawEvent = fromRawEvent$1;
  1978. const getDocument = () => SugarElement.fromDom(document);
  1979. const focus$4 = (element, preventScroll = false) => element.dom.focus({ preventScroll });
  1980. const blur$1 = (element) => element.dom.blur();
  1981. const hasFocus = (element) => {
  1982. const root = getRootNode(element).dom;
  1983. return element.dom === root.activeElement;
  1984. };
  1985. // Note: assuming that activeElement will always be a HTMLElement (maybe we should add a runtime check?)
  1986. const active$1 = (root = getDocument()) => Optional.from(root.dom.activeElement).map(SugarElement.fromDom);
  1987. /**
  1988. * Return the descendant element that has focus.
  1989. * Use instead of SelectorFind.descendant(container, ':focus')
  1990. * because the :focus selector relies on keyboard focus.
  1991. */
  1992. const search = (element) => active$1(getRootNode(element))
  1993. .filter((e) => element.dom.contains(e.dom));
  1994. const before$1 = (marker, element) => {
  1995. const parent$1 = parent(marker);
  1996. parent$1.each((v) => {
  1997. v.dom.insertBefore(element.dom, marker.dom);
  1998. });
  1999. };
  2000. const after$1 = (marker, element) => {
  2001. const sibling = nextSibling(marker);
  2002. sibling.fold(() => {
  2003. const parent$1 = parent(marker);
  2004. parent$1.each((v) => {
  2005. append$2(v, element);
  2006. });
  2007. }, (v) => {
  2008. before$1(v, element);
  2009. });
  2010. };
  2011. const prepend$1 = (parent, element) => {
  2012. const firstChild$1 = firstChild(parent);
  2013. firstChild$1.fold(() => {
  2014. append$2(parent, element);
  2015. }, (v) => {
  2016. parent.dom.insertBefore(element.dom, v.dom);
  2017. });
  2018. };
  2019. const append$2 = (parent, element) => {
  2020. parent.dom.appendChild(element.dom);
  2021. };
  2022. const appendAt = (parent, element, index) => {
  2023. child$2(parent, index).fold(() => {
  2024. append$2(parent, element);
  2025. }, (v) => {
  2026. before$1(v, element);
  2027. });
  2028. };
  2029. const append$1 = (parent, elements) => {
  2030. each$1(elements, (x) => {
  2031. append$2(parent, x);
  2032. });
  2033. };
  2034. const rawSet = (dom, key, value) => {
  2035. /*
  2036. * JQuery coerced everything to a string, and silently did nothing on text node/null/undefined.
  2037. *
  2038. * We fail on those invalid cases, only allowing numbers and booleans.
  2039. */
  2040. if (isString(value) || isBoolean(value) || isNumber(value)) {
  2041. dom.setAttribute(key, value + '');
  2042. }
  2043. else {
  2044. // eslint-disable-next-line no-console
  2045. console.error('Invalid call to Attribute.set. Key ', key, ':: Value ', value, ':: Element ', dom);
  2046. throw new Error('Attribute value was not simple');
  2047. }
  2048. };
  2049. const set$9 = (element, key, value) => {
  2050. rawSet(element.dom, key, value);
  2051. };
  2052. const setAll$1 = (element, attrs) => {
  2053. const dom = element.dom;
  2054. each(attrs, (v, k) => {
  2055. rawSet(dom, k, v);
  2056. });
  2057. };
  2058. const get$g = (element, key) => {
  2059. const v = element.dom.getAttribute(key);
  2060. // undefined is the more appropriate value for JS, and this matches JQuery
  2061. return v === null ? undefined : v;
  2062. };
  2063. const getOpt = (element, key) => Optional.from(get$g(element, key));
  2064. const has$1 = (element, key) => {
  2065. const dom = element.dom;
  2066. // return false for non-element nodes, no point in throwing an error
  2067. return dom && dom.hasAttribute ? dom.hasAttribute(key) : false;
  2068. };
  2069. const remove$8 = (element, key) => {
  2070. element.dom.removeAttribute(key);
  2071. };
  2072. const clone$2 = (element) => foldl(element.dom.attributes, (acc, attr) => {
  2073. acc[attr.name] = attr.value;
  2074. return acc;
  2075. }, {});
  2076. const empty = (element) => {
  2077. // shortcut "empty node" trick. Requires IE 9.
  2078. element.dom.textContent = '';
  2079. // If the contents was a single empty text node, the above doesn't remove it. But, it's still faster in general
  2080. // than removing every child node manually.
  2081. // The following is (probably) safe for performance as 99.9% of the time the trick works and
  2082. // Traverse.children will return an empty array.
  2083. each$1(children(element), (rogue) => {
  2084. remove$7(rogue);
  2085. });
  2086. };
  2087. const remove$7 = (element) => {
  2088. const dom = element.dom;
  2089. if (dom.parentNode !== null) {
  2090. dom.parentNode.removeChild(dom);
  2091. }
  2092. };
  2093. const clone$1 = (original, isDeep) => SugarElement.fromDom(original.dom.cloneNode(isDeep));
  2094. /** Shallow clone - just the tag, no children */
  2095. const shallow = (original) => clone$1(original, false);
  2096. /** Deep clone - everything copied including children */
  2097. const deep = (original) => clone$1(original, true);
  2098. const fromHtml$1 = (html, scope) => {
  2099. const doc = scope || document;
  2100. const div = doc.createElement('div');
  2101. div.innerHTML = html;
  2102. return children(SugarElement.fromDom(div));
  2103. };
  2104. const get$f = (element) => element.dom.innerHTML;
  2105. const set$8 = (element, content) => {
  2106. const owner = owner$4(element);
  2107. const docDom = owner.dom;
  2108. // FireFox has *terrible* performance when using innerHTML = x
  2109. const fragment = SugarElement.fromDom(docDom.createDocumentFragment());
  2110. const contentElements = fromHtml$1(content, docDom);
  2111. append$1(fragment, contentElements);
  2112. empty(element);
  2113. append$2(element, fragment);
  2114. };
  2115. const getOuter$2 = (element) => {
  2116. const container = SugarElement.fromTag('div');
  2117. const clone = SugarElement.fromDom(element.dom.cloneNode(true));
  2118. append$2(container, clone);
  2119. return get$f(container);
  2120. };
  2121. const getHtml = (element) => {
  2122. if (isShadowRoot(element)) {
  2123. return '#shadow-root';
  2124. }
  2125. else {
  2126. const clone = shallow(element);
  2127. return getOuter$2(clone);
  2128. }
  2129. };
  2130. const image = (image) => new Promise((resolve, reject) => {
  2131. const loaded = () => {
  2132. destroy();
  2133. resolve(image);
  2134. };
  2135. const listeners = [
  2136. bind$1(image, 'load', loaded),
  2137. bind$1(image, 'error', () => {
  2138. destroy();
  2139. reject('Unable to load data from image: ' + image.dom.src);
  2140. }),
  2141. ];
  2142. const destroy = () => each$1(listeners, (l) => l.unbind());
  2143. if (image.dom.complete) {
  2144. loaded();
  2145. }
  2146. });
  2147. // some elements, such as mathml, don't have style attributes
  2148. // others, such as angular elements, have style attributes that aren't a CSSStyleDeclaration
  2149. const isSupported = (dom) => dom.style !== undefined && isFunction(dom.style.getPropertyValue);
  2150. // Node.contains() is very, very, very good performance
  2151. // http://jsperf.com/closest-vs-contains/5
  2152. const inBody = (element) => {
  2153. // Technically this is only required on IE, where contains() returns false for text nodes.
  2154. // But it's cheap enough to run everywhere and Sugar doesn't have platform detection (yet).
  2155. const dom = isText(element) ? element.dom.parentNode : element.dom;
  2156. // use ownerDocument.body to ensure this works inside iframes.
  2157. // Normally contains is bad because an element "contains" itself, but here we want that.
  2158. if (dom === undefined || dom === null || dom.ownerDocument === null) {
  2159. return false;
  2160. }
  2161. const doc = dom.ownerDocument;
  2162. return getShadowRoot(SugarElement.fromDom(dom)).fold(() => doc.body.contains(dom), compose1(inBody, getShadowHost));
  2163. };
  2164. const body = () => getBody(SugarElement.fromDom(document));
  2165. const getBody = (doc) => {
  2166. const b = doc.dom.body;
  2167. if (b === null || b === undefined) {
  2168. throw new Error('Body is not available yet');
  2169. }
  2170. return SugarElement.fromDom(b);
  2171. };
  2172. const internalSet = (dom, property, value) => {
  2173. // This is going to hurt. Apologies.
  2174. // JQuery coerces numbers to pixels for certain property names, and other times lets numbers through.
  2175. // we're going to be explicit; strings only.
  2176. if (!isString(value)) {
  2177. // eslint-disable-next-line no-console
  2178. console.error('Invalid call to CSS.set. Property ', property, ':: Value ', value, ':: Element ', dom);
  2179. throw new Error('CSS value must be a string: ' + value);
  2180. }
  2181. // removed: support for dom().style[property] where prop is camel case instead of normal property name
  2182. if (isSupported(dom)) {
  2183. dom.style.setProperty(property, value);
  2184. }
  2185. };
  2186. const internalRemove = (dom, property) => {
  2187. /*
  2188. * IE9 and above - MDN doesn't have details, but here's a couple of random internet claims
  2189. *
  2190. * http://help.dottoro.com/ljopsjck.php
  2191. * http://stackoverflow.com/a/7901886/7546
  2192. */
  2193. if (isSupported(dom)) {
  2194. dom.style.removeProperty(property);
  2195. }
  2196. };
  2197. const set$7 = (element, property, value) => {
  2198. const dom = element.dom;
  2199. internalSet(dom, property, value);
  2200. };
  2201. const setAll = (element, css) => {
  2202. const dom = element.dom;
  2203. each(css, (v, k) => {
  2204. internalSet(dom, k, v);
  2205. });
  2206. };
  2207. const setOptions = (element, css) => {
  2208. const dom = element.dom;
  2209. each(css, (v, k) => {
  2210. v.fold(() => {
  2211. internalRemove(dom, k);
  2212. }, (value) => {
  2213. internalSet(dom, k, value);
  2214. });
  2215. });
  2216. };
  2217. /*
  2218. * NOTE: For certain properties, this returns the "used value" which is subtly different to the "computed value" (despite calling getComputedStyle).
  2219. * Blame CSS 2.0.
  2220. *
  2221. * https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
  2222. */
  2223. const get$e = (element, property) => {
  2224. const dom = element.dom;
  2225. /*
  2226. * IE9 and above per
  2227. * https://developer.mozilla.org/en/docs/Web/API/window.getComputedStyle
  2228. *
  2229. * Not in numerosity, because it doesn't memoize and looking this up dynamically in performance critical code would be horrendous.
  2230. *
  2231. * JQuery has some magic here for IE popups, but we don't really need that.
  2232. * It also uses element.ownerDocument.defaultView to handle iframes but that hasn't been required since FF 3.6.
  2233. */
  2234. const styles = window.getComputedStyle(dom);
  2235. const r = styles.getPropertyValue(property);
  2236. // 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.
  2237. // Turns out we do this a lot.
  2238. return (r === '' && !inBody(element)) ? getUnsafeProperty(dom, property) : r;
  2239. };
  2240. // removed: support for dom().style[property] where prop is camel case instead of normal property name
  2241. // empty string is what the browsers (IE11 and Chrome) return when the propertyValue doesn't exists.
  2242. const getUnsafeProperty = (dom, property) => isSupported(dom) ? dom.style.getPropertyValue(property) : '';
  2243. /*
  2244. * Gets the raw value from the style attribute. Useful for retrieving "used values" from the DOM:
  2245. * https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
  2246. *
  2247. * Returns NONE if the property isn't set, or the value is an empty string.
  2248. */
  2249. const getRaw = (element, property) => {
  2250. const dom = element.dom;
  2251. const raw = getUnsafeProperty(dom, property);
  2252. return Optional.from(raw).filter((r) => r.length > 0);
  2253. };
  2254. const getAllRaw = (element) => {
  2255. const css = {};
  2256. const dom = element.dom;
  2257. if (isSupported(dom)) {
  2258. for (let i = 0; i < dom.style.length; i++) {
  2259. const ruleName = dom.style.item(i);
  2260. css[ruleName] = dom.style[ruleName];
  2261. }
  2262. }
  2263. return css;
  2264. };
  2265. const isValidValue$1 = (tag, property, value) => {
  2266. const element = SugarElement.fromTag(tag);
  2267. set$7(element, property, value);
  2268. const style = getRaw(element, property);
  2269. return style.isSome();
  2270. };
  2271. const remove$6 = (element, property) => {
  2272. const dom = element.dom;
  2273. internalRemove(dom, property);
  2274. if (is$1(getOpt(element, 'style').map(trim$1), '')) {
  2275. // No more styles left, remove the style attribute as well
  2276. remove$8(element, 'style');
  2277. }
  2278. };
  2279. /* NOTE: This function is here for the side effect it triggers.
  2280. The value itself is not used.
  2281. Be sure to not use the return value, and that it is not removed by a minifier.
  2282. */
  2283. const reflow = (e) => e.dom.offsetWidth;
  2284. const Dimension = (name, getOffset) => {
  2285. const set = (element, h) => {
  2286. if (!isNumber(h) && !h.match(/^[0-9]+$/)) {
  2287. throw new Error(name + '.set accepts only positive integer values. Value was ' + h);
  2288. }
  2289. const dom = element.dom;
  2290. if (isSupported(dom)) {
  2291. dom.style[name] = h + 'px';
  2292. }
  2293. };
  2294. /*
  2295. * jQuery supports querying width and height on the document and window objects.
  2296. *
  2297. * TBIO doesn't do this, so the code is removed to save space, but left here just in case.
  2298. */
  2299. /*
  2300. var getDocumentWidth = (element) => {
  2301. var dom = element.dom;
  2302. if (Node.isDocument(element)) {
  2303. var body = dom.body;
  2304. var doc = dom.documentElement;
  2305. return Math.max(
  2306. body.scrollHeight,
  2307. doc.scrollHeight,
  2308. body.offsetHeight,
  2309. doc.offsetHeight,
  2310. doc.clientHeight
  2311. );
  2312. }
  2313. };
  2314. var getWindowWidth = (element) => {
  2315. var dom = element.dom;
  2316. if (dom.window === dom) {
  2317. // There is no offsetHeight on a window, so use the clientHeight of the document
  2318. return dom.document.documentElement.clientHeight;
  2319. }
  2320. };
  2321. */
  2322. const get = (element) => {
  2323. const r = getOffset(element);
  2324. // zero or null means non-standard or disconnected, fall back to CSS
  2325. if (r <= 0 || r === null) {
  2326. const css = get$e(element, name);
  2327. // ugh this feels dirty, but it saves cycles
  2328. return parseFloat(css) || 0;
  2329. }
  2330. return r;
  2331. };
  2332. // in jQuery, getOuter replicates (or uses) box-sizing: border-box calculations
  2333. // although these calculations only seem relevant for quirks mode, and edge cases TBIO doesn't rely on
  2334. const getOuter = get;
  2335. const aggregate = (element, properties) => foldl(properties, (acc, property) => {
  2336. const val = get$e(element, property);
  2337. const value = val === undefined ? 0 : parseInt(val, 10);
  2338. return isNaN(value) ? acc : acc + value;
  2339. }, 0);
  2340. const max = (element, value, properties) => {
  2341. const cumulativeInclusions = aggregate(element, properties);
  2342. // if max-height is 100px and your cumulativeInclusions is 150px, there is no way max-height can be 100px, so we return 0.
  2343. const absoluteMax = value > cumulativeInclusions ? value - cumulativeInclusions : 0;
  2344. return absoluteMax;
  2345. };
  2346. return {
  2347. set,
  2348. get,
  2349. getOuter,
  2350. aggregate,
  2351. max
  2352. };
  2353. };
  2354. const api$2 = Dimension('height', (element) => {
  2355. // getBoundingClientRect gives better results than offsetHeight for tables with captions on Firefox
  2356. const dom = element.dom;
  2357. return inBody(element) ? dom.getBoundingClientRect().height : dom.offsetHeight;
  2358. });
  2359. const get$d = (element) => api$2.get(element);
  2360. const getOuter$1 = (element) => api$2.getOuter(element);
  2361. const setMax$1 = (element, value) => {
  2362. // These properties affect the absolute max-height, they are not counted natively, we want to include these properties.
  2363. const inclusions = ['margin-top', 'border-top-width', 'padding-top', 'padding-bottom', 'border-bottom-width', 'margin-bottom'];
  2364. const absMax = api$2.max(element, value, inclusions);
  2365. set$7(element, 'max-height', absMax + 'px');
  2366. };
  2367. const isHidden$1 = (dom) => dom.offsetWidth <= 0 && dom.offsetHeight <= 0;
  2368. const isVisible = (element) => !isHidden$1(element.dom);
  2369. const api$1 = Dimension('width', (element) => {
  2370. const dom = element.dom;
  2371. return inBody(element) ? dom.getBoundingClientRect().width : dom.offsetWidth;
  2372. });
  2373. const set$6 = (element, h) => api$1.set(element, h);
  2374. const get$c = (element) => api$1.get(element);
  2375. const getOuter = (element) => api$1.getOuter(element);
  2376. const setMax = (element, value) => {
  2377. // These properties affect the absolute max-height, they are not counted natively, we want to include these properties.
  2378. const inclusions = ['margin-left', 'border-left-width', 'padding-left', 'padding-right', 'border-right-width', 'margin-right'];
  2379. const absMax = api$1.max(element, value, inclusions);
  2380. set$7(element, 'max-width', absMax + 'px');
  2381. };
  2382. const r$1 = (left, top) => {
  2383. const translate = (x, y) => r$1(left + x, top + y);
  2384. return {
  2385. left,
  2386. top,
  2387. translate
  2388. };
  2389. };
  2390. // tslint:disable-next-line:variable-name
  2391. const SugarPosition = r$1;
  2392. const boxPosition = (dom) => {
  2393. const box = dom.getBoundingClientRect();
  2394. return SugarPosition(box.left, box.top);
  2395. };
  2396. // Avoids falsy false fallthrough
  2397. const firstDefinedOrZero = (a, b) => {
  2398. if (a !== undefined) {
  2399. return a;
  2400. }
  2401. else {
  2402. return b !== undefined ? b : 0;
  2403. }
  2404. };
  2405. const absolute$3 = (element) => {
  2406. const doc = element.dom.ownerDocument;
  2407. const body = doc.body;
  2408. const win = doc.defaultView;
  2409. const html = doc.documentElement;
  2410. if (body === element.dom) {
  2411. return SugarPosition(body.offsetLeft, body.offsetTop);
  2412. }
  2413. const scrollTop = firstDefinedOrZero(win === null || win === void 0 ? void 0 : win.pageYOffset, html.scrollTop);
  2414. const scrollLeft = firstDefinedOrZero(win === null || win === void 0 ? void 0 : win.pageXOffset, html.scrollLeft);
  2415. const clientTop = firstDefinedOrZero(html.clientTop, body.clientTop);
  2416. const clientLeft = firstDefinedOrZero(html.clientLeft, body.clientLeft);
  2417. return viewport$1(element).translate(scrollLeft - clientLeft, scrollTop - clientTop);
  2418. };
  2419. const viewport$1 = (element) => {
  2420. const dom = element.dom;
  2421. const doc = dom.ownerDocument;
  2422. const body = doc.body;
  2423. if (body === dom) {
  2424. return SugarPosition(body.offsetLeft, body.offsetTop);
  2425. }
  2426. if (!inBody(element)) {
  2427. return SugarPosition(0, 0);
  2428. }
  2429. return boxPosition(dom);
  2430. };
  2431. // get scroll position (x,y) relative to document _doc (or global if not supplied)
  2432. const get$b = (_DOC) => {
  2433. const doc = _DOC !== undefined ? _DOC.dom : document;
  2434. // ASSUMPTION: This is for cross-browser support, body works for Safari & EDGE, and when we have an iframe body scroller
  2435. const x = doc.body.scrollLeft || doc.documentElement.scrollLeft;
  2436. const y = doc.body.scrollTop || doc.documentElement.scrollTop;
  2437. return SugarPosition(x, y);
  2438. };
  2439. // Scroll content to (x,y) relative to document _doc (or global if not supplied)
  2440. const to = (x, y, _DOC) => {
  2441. const doc = _DOC !== undefined ? _DOC.dom : document;
  2442. const win = doc.defaultView;
  2443. if (win) {
  2444. win.scrollTo(x, y);
  2445. }
  2446. };
  2447. const NodeValue = (is, name) => {
  2448. const get = (element) => {
  2449. if (!is(element)) {
  2450. throw new Error('Can only get ' + name + ' value of a ' + name + ' node');
  2451. }
  2452. return getOption(element).getOr('');
  2453. };
  2454. const getOption = (element) => is(element) ? Optional.from(element.dom.nodeValue) : Optional.none();
  2455. const set = (element, value) => {
  2456. if (!is(element)) {
  2457. throw new Error('Can only set raw ' + name + ' value of a ' + name + ' node');
  2458. }
  2459. element.dom.nodeValue = value;
  2460. };
  2461. return {
  2462. get,
  2463. getOption,
  2464. set
  2465. };
  2466. };
  2467. const api = NodeValue(isText, 'text');
  2468. const get$a = (element) => api.get(element);
  2469. const onDirection = (isLtr, isRtl) => (element) => getDirection(element) === 'rtl' ? isRtl : isLtr;
  2470. const getDirection = (element) => get$e(element, 'direction') === 'rtl' ? 'rtl' : 'ltr';
  2471. // Methods for handling attributes that contain a list of values <div foo="alpha beta theta">
  2472. const read$2 = (element, attr) => {
  2473. const value = get$g(element, attr);
  2474. return value === undefined || value === '' ? [] : value.split(' ');
  2475. };
  2476. const add$4 = (element, attr, id) => {
  2477. const old = read$2(element, attr);
  2478. const nu = old.concat([id]);
  2479. set$9(element, attr, nu.join(' '));
  2480. return true;
  2481. };
  2482. const remove$5 = (element, attr, id) => {
  2483. const nu = filter$2(read$2(element, attr), (v) => v !== id);
  2484. if (nu.length > 0) {
  2485. set$9(element, attr, nu.join(' '));
  2486. }
  2487. else {
  2488. remove$8(element, attr);
  2489. }
  2490. return false;
  2491. };
  2492. var ClosestOrAncestor = (is, ancestor, scope, a, isRoot) => {
  2493. if (is(scope, a)) {
  2494. return Optional.some(scope);
  2495. }
  2496. else if (isFunction(isRoot) && isRoot(scope)) {
  2497. return Optional.none();
  2498. }
  2499. else {
  2500. return ancestor(scope, a, isRoot);
  2501. }
  2502. };
  2503. const ancestor$2 = (scope, predicate, isRoot) => {
  2504. let element = scope.dom;
  2505. const stop = isFunction(isRoot) ? isRoot : never;
  2506. while (element.parentNode) {
  2507. element = element.parentNode;
  2508. const el = SugarElement.fromDom(element);
  2509. if (predicate(el)) {
  2510. return Optional.some(el);
  2511. }
  2512. else if (stop(el)) {
  2513. break;
  2514. }
  2515. }
  2516. return Optional.none();
  2517. };
  2518. const closest$4 = (scope, predicate, isRoot) => {
  2519. // This is required to avoid ClosestOrAncestor passing the predicate to itself
  2520. const is = (s, test) => test(s);
  2521. return ClosestOrAncestor(is, ancestor$2, scope, predicate, isRoot);
  2522. };
  2523. const sibling$1 = (scope, predicate) => {
  2524. const element = scope.dom;
  2525. if (!element.parentNode) {
  2526. return Optional.none();
  2527. }
  2528. return child$1(SugarElement.fromDom(element.parentNode), (x) => !eq(scope, x) && predicate(x));
  2529. };
  2530. const child$1 = (scope, predicate) => {
  2531. const pred = (node) => predicate(SugarElement.fromDom(node));
  2532. const result = find$5(scope.dom.childNodes, pred);
  2533. return result.map(SugarElement.fromDom);
  2534. };
  2535. const descendant$1 = (scope, predicate) => {
  2536. const descend = (node) => {
  2537. // tslint:disable-next-line:prefer-for-of
  2538. for (let i = 0; i < node.childNodes.length; i++) {
  2539. const child = SugarElement.fromDom(node.childNodes[i]);
  2540. if (predicate(child)) {
  2541. return Optional.some(child);
  2542. }
  2543. const res = descend(node.childNodes[i]);
  2544. if (res.isSome()) {
  2545. return res;
  2546. }
  2547. }
  2548. return Optional.none();
  2549. };
  2550. return descend(scope.dom);
  2551. };
  2552. // TODO: An internal SelectorFilter module that doesn't SugarElement.fromDom() everything
  2553. const first = (selector) => one(selector);
  2554. const ancestor$1 = (scope, selector, isRoot) => ancestor$2(scope, (e) => is(e, selector), isRoot);
  2555. const sibling = (scope, selector) => sibling$1(scope, (e) => is(e, selector));
  2556. const child = (scope, selector) => child$1(scope, (e) => is(e, selector));
  2557. const descendant = (scope, selector) => one(selector, scope);
  2558. // Returns Some(closest ancestor element (sugared)) matching 'selector' up to isRoot, or None() otherwise
  2559. const closest$3 = (scope, selector, isRoot) => {
  2560. const is$1 = (element, selector) => is(element, selector);
  2561. return ClosestOrAncestor(is$1, ancestor$1, scope, selector, isRoot);
  2562. };
  2563. const set$5 = (element, status) => {
  2564. element.dom.checked = status;
  2565. };
  2566. const get$9 = (element) => element.dom.checked;
  2567. // 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.
  2568. const supports = (element) => element.dom.classList !== undefined;
  2569. const get$8 = (element) => read$2(element, 'class');
  2570. const add$3 = (element, clazz) => add$4(element, 'class', clazz);
  2571. const remove$4 = (element, clazz) => remove$5(element, 'class', clazz);
  2572. const toggle$5 = (element, clazz) => {
  2573. if (contains$2(get$8(element), clazz)) {
  2574. return remove$4(element, clazz);
  2575. }
  2576. else {
  2577. return add$3(element, clazz);
  2578. }
  2579. };
  2580. /*
  2581. * ClassList is IE10 minimum:
  2582. * https://developer.mozilla.org/en-US/docs/Web/API/Element.classList
  2583. *
  2584. * Note that IE doesn't support the second argument to toggle (at all).
  2585. * If it did, the toggler could be better.
  2586. */
  2587. const add$2 = (element, clazz) => {
  2588. if (supports(element)) {
  2589. element.dom.classList.add(clazz);
  2590. }
  2591. else {
  2592. add$3(element, clazz);
  2593. }
  2594. };
  2595. const cleanClass = (element) => {
  2596. const classList = supports(element) ? element.dom.classList : get$8(element);
  2597. // classList is a "live list", so this is up to date already
  2598. if (classList.length === 0) {
  2599. // No more classes left, remove the class attribute as well
  2600. remove$8(element, 'class');
  2601. }
  2602. };
  2603. const remove$3 = (element, clazz) => {
  2604. if (supports(element)) {
  2605. const classList = element.dom.classList;
  2606. classList.remove(clazz);
  2607. }
  2608. else {
  2609. remove$4(element, clazz);
  2610. }
  2611. cleanClass(element);
  2612. };
  2613. const toggle$4 = (element, clazz) => {
  2614. const result = supports(element) ? element.dom.classList.toggle(clazz) : toggle$5(element, clazz);
  2615. cleanClass(element);
  2616. return result;
  2617. };
  2618. const has = (element, clazz) => supports(element) && element.dom.classList.contains(clazz);
  2619. /*
  2620. * ClassList is IE10 minimum:
  2621. * https://developer.mozilla.org/en-US/docs/Web/API/Element.classList
  2622. */
  2623. const add$1 = (element, classes) => {
  2624. each$1(classes, (x) => {
  2625. add$2(element, x);
  2626. });
  2627. };
  2628. const remove$2 = (element, classes) => {
  2629. each$1(classes, (x) => {
  2630. remove$3(element, x);
  2631. });
  2632. };
  2633. const toggle$3 = (element, classes) => {
  2634. each$1(classes, (x) => {
  2635. toggle$4(element, x);
  2636. });
  2637. };
  2638. const hasAll = (element, classes) => forall(classes, (clazz) => has(element, clazz));
  2639. const getNative = (element) => {
  2640. const classList = element.dom.classList;
  2641. const r = new Array(classList.length);
  2642. for (let i = 0; i < classList.length; i++) {
  2643. const item = classList.item(i);
  2644. if (item !== null) {
  2645. r[i] = item;
  2646. }
  2647. }
  2648. return r;
  2649. };
  2650. const get$7 = (element) => supports(element) ? getNative(element) : get$8(element);
  2651. const get$6 = (element) => element.dom.textContent;
  2652. const get$5 = (element) => element.dom.value;
  2653. const set$4 = (element, value) => {
  2654. if (value === undefined) {
  2655. throw new Error('Value.set was undefined');
  2656. }
  2657. element.dom.value = value;
  2658. };
  2659. const ancestors = (scope, predicate, isRoot) => filter$2(parents(scope, isRoot), predicate);
  2660. const descendants = (scope, selector) => all$3(selector, scope);
  2661. const closest$2 = (scope, predicate, isRoot) => closest$4(scope, predicate, isRoot).isSome();
  2662. const closest$1 = (scope, selector, isRoot) => closest$3(scope, selector, isRoot).isSome();
  2663. const ensureIsRoot = (isRoot) => isFunction(isRoot) ? isRoot : never;
  2664. const ancestor = (scope, transform, isRoot) => {
  2665. let element = scope.dom;
  2666. const stop = ensureIsRoot(isRoot);
  2667. while (element.parentNode) {
  2668. element = element.parentNode;
  2669. const el = SugarElement.fromDom(element);
  2670. const transformed = transform(el);
  2671. if (transformed.isSome()) {
  2672. return transformed;
  2673. }
  2674. else if (stop(el)) {
  2675. break;
  2676. }
  2677. }
  2678. return Optional.none();
  2679. };
  2680. const closest = (scope, transform, isRoot) => {
  2681. const current = transform(scope);
  2682. const stop = ensureIsRoot(isRoot);
  2683. return current.orThunk(() => stop(scope) ? Optional.none() : ancestor(scope, transform, stop));
  2684. };
  2685. const create$5 = (start, soffset, finish, foffset) => ({
  2686. start,
  2687. soffset,
  2688. finish,
  2689. foffset
  2690. });
  2691. // tslint:disable-next-line:variable-name
  2692. const SimRange = {
  2693. create: create$5
  2694. };
  2695. const adt$9 = Adt.generate([
  2696. { before: ['element'] },
  2697. { on: ['element', 'offset'] },
  2698. { after: ['element'] }
  2699. ]);
  2700. // Probably don't need this given that we now have "match"
  2701. const cata$2 = (subject, onBefore, onOn, onAfter) => subject.fold(onBefore, onOn, onAfter);
  2702. const getStart$1 = (situ) => situ.fold(identity, identity, identity);
  2703. const before = adt$9.before;
  2704. const on$1 = adt$9.on;
  2705. const after = adt$9.after;
  2706. // tslint:disable-next-line:variable-name
  2707. const Situ = {
  2708. before,
  2709. on: on$1,
  2710. after,
  2711. cata: cata$2,
  2712. getStart: getStart$1
  2713. };
  2714. // Consider adding a type for "element"
  2715. const adt$8 = Adt.generate([
  2716. { domRange: ['rng'] },
  2717. { relative: ['startSitu', 'finishSitu'] },
  2718. { exact: ['start', 'soffset', 'finish', 'foffset'] }
  2719. ]);
  2720. const exactFromRange = (simRange) => adt$8.exact(simRange.start, simRange.soffset, simRange.finish, simRange.foffset);
  2721. const getStart = (selection) => selection.match({
  2722. domRange: (rng) => SugarElement.fromDom(rng.startContainer),
  2723. relative: (startSitu, _finishSitu) => Situ.getStart(startSitu),
  2724. exact: (start, _soffset, _finish, _foffset) => start
  2725. });
  2726. const domRange = adt$8.domRange;
  2727. const relative$1 = adt$8.relative;
  2728. const exact = adt$8.exact;
  2729. const getWin = (selection) => {
  2730. const start = getStart(selection);
  2731. return defaultView(start);
  2732. };
  2733. // This is out of place but it's API so I can't remove it
  2734. const range$1 = SimRange.create;
  2735. // tslint:disable-next-line:variable-name
  2736. const SimSelection = {
  2737. domRange,
  2738. relative: relative$1,
  2739. exact,
  2740. exactFromRange,
  2741. getWin,
  2742. range: range$1
  2743. };
  2744. const getNativeSelection = (win) => Optional.from(win.getSelection());
  2745. // NOTE: We are still reading the range because it gives subtly different behaviour
  2746. // than using the anchorNode and focusNode. I'm not sure if this behaviour is any
  2747. // better or worse; it's just different.
  2748. const readRange = (selection) => {
  2749. if (selection.rangeCount > 0) {
  2750. const firstRng = selection.getRangeAt(0);
  2751. const lastRng = selection.getRangeAt(selection.rangeCount - 1);
  2752. return Optional.some(SimRange.create(SugarElement.fromDom(firstRng.startContainer), firstRng.startOffset, SugarElement.fromDom(lastRng.endContainer), lastRng.endOffset));
  2753. }
  2754. else {
  2755. return Optional.none();
  2756. }
  2757. };
  2758. const doGetExact = (selection) => {
  2759. if (selection.anchorNode === null || selection.focusNode === null) {
  2760. return readRange(selection);
  2761. }
  2762. else {
  2763. const anchor = SugarElement.fromDom(selection.anchorNode);
  2764. const focus = SugarElement.fromDom(selection.focusNode);
  2765. // if this returns true anchor is _after_ focus, so we need a custom selection object to maintain the RTL selection
  2766. return after$2(anchor, selection.anchorOffset, focus, selection.focusOffset) ? Optional.some(SimRange.create(anchor, selection.anchorOffset, focus, selection.focusOffset)) : readRange(selection);
  2767. }
  2768. };
  2769. const getExact = (win) =>
  2770. // We want to retrieve the selection as it is.
  2771. getNativeSelection(win)
  2772. .filter((sel) => sel.rangeCount > 0)
  2773. .bind(doGetExact);
  2774. const getFirstRect = (win, selection) => {
  2775. const rng = asLtrRange(win, selection);
  2776. return getFirstRect$1(rng);
  2777. };
  2778. const getBounds$2 = (win, selection) => {
  2779. const rng = asLtrRange(win, selection);
  2780. return getBounds$3(rng);
  2781. };
  2782. const units = {
  2783. // we don't really support all of these different ways to express a length
  2784. unsupportedLength: [
  2785. 'em',
  2786. 'ex',
  2787. 'cap',
  2788. 'ch',
  2789. 'ic',
  2790. 'rem',
  2791. 'lh',
  2792. 'rlh',
  2793. 'vw',
  2794. 'vh',
  2795. 'vi',
  2796. 'vb',
  2797. 'vmin',
  2798. 'vmax',
  2799. 'cm',
  2800. 'mm',
  2801. 'Q',
  2802. 'in',
  2803. 'pc',
  2804. 'pt',
  2805. 'px'
  2806. ],
  2807. // these are the length values we do support
  2808. fixed: ['px', 'pt'],
  2809. relative: ['%'],
  2810. empty: ['']
  2811. };
  2812. // Built from https://tc39.es/ecma262/#prod-StrDecimalLiteral
  2813. // Matches a float followed by a trailing set of characters
  2814. const pattern = (() => {
  2815. const decimalDigits = '[0-9]+';
  2816. const signedInteger = '[+-]?' + decimalDigits;
  2817. const exponentPart = '[eE]' + signedInteger;
  2818. const dot = '\\.';
  2819. const opt = (input) => `(?:${input})?`;
  2820. const unsignedDecimalLiteral = [
  2821. 'Infinity',
  2822. decimalDigits + dot + opt(decimalDigits) + opt(exponentPart),
  2823. dot + decimalDigits + opt(exponentPart),
  2824. decimalDigits + opt(exponentPart)
  2825. ].join('|');
  2826. const float = `[+-]?(?:${unsignedDecimalLiteral})`;
  2827. return new RegExp(`^(${float})(.*)$`);
  2828. })();
  2829. const isUnit = (unit, accepted) => exists(accepted, (acc) => exists(units[acc], (check) => unit === check));
  2830. const parse = (input, accepted) => {
  2831. const match = Optional.from(pattern.exec(input));
  2832. return match.bind((array) => {
  2833. const value = Number(array[1]);
  2834. const unitRaw = array[2];
  2835. if (isUnit(unitRaw, accepted)) {
  2836. return Optional.some({
  2837. value,
  2838. unit: unitRaw
  2839. });
  2840. }
  2841. else {
  2842. return Optional.none();
  2843. }
  2844. });
  2845. };
  2846. const normalise = (input, accepted) => parse(input, accepted).map(({ value, unit }) => value + unit);
  2847. const get$4 = (_win) => {
  2848. const win = _win === undefined ? window : _win;
  2849. if (detect$1().browser.isFirefox()) {
  2850. // TINY-7984: Firefox 91 is returning incorrect values for visualViewport.pageTop, so disable it for now
  2851. return Optional.none();
  2852. }
  2853. else {
  2854. return Optional.from(win.visualViewport);
  2855. }
  2856. };
  2857. const bounds$1 = (x, y, width, height) => ({
  2858. x,
  2859. y,
  2860. width,
  2861. height,
  2862. right: x + width,
  2863. bottom: y + height
  2864. });
  2865. const getBounds$1 = (_win) => {
  2866. const win = _win === undefined ? window : _win;
  2867. const doc = win.document;
  2868. const scroll = get$b(SugarElement.fromDom(doc));
  2869. return get$4(win).fold(() => {
  2870. const html = win.document.documentElement;
  2871. // Don't use window.innerWidth/innerHeight here, as we don't want to include scrollbars
  2872. // since the right/bottom position is based on the edge of the scrollbar not the window
  2873. const width = html.clientWidth;
  2874. const height = html.clientHeight;
  2875. return bounds$1(scroll.left, scroll.top, width, height);
  2876. }, (visualViewport) =>
  2877. // iOS doesn't update the pageTop/pageLeft when element.scrollIntoView() is called, so we need to fallback to the
  2878. // scroll position which will always be less than the page top/left values when page top/left are accurate/correct.
  2879. bounds$1(Math.max(visualViewport.pageLeft, scroll.left), Math.max(visualViewport.pageTop, scroll.top), visualViewport.width, visualViewport.height));
  2880. };
  2881. const walkUp = (navigation, doc) => {
  2882. const frame = navigation.view(doc);
  2883. return frame.fold(constant$1([]), (f) => {
  2884. const parent = navigation.owner(f);
  2885. const rest = walkUp(navigation, parent);
  2886. return [f].concat(rest);
  2887. });
  2888. };
  2889. // TODO: Why is this an option if it is always some?
  2890. const pathTo = (element, navigation) => {
  2891. const d = navigation.owner(element);
  2892. const paths = walkUp(navigation, d);
  2893. return Optional.some(paths);
  2894. };
  2895. const view = (doc) => {
  2896. var _a;
  2897. // Only walk up to the document this script is defined in.
  2898. // This prevents walking up to the parent window when the editor is in an iframe.
  2899. const element = doc.dom === document ? Optional.none() : Optional.from((_a = doc.dom.defaultView) === null || _a === void 0 ? void 0 : _a.frameElement);
  2900. return element.map(SugarElement.fromDom);
  2901. };
  2902. const owner$3 = (element) => owner$4(element);
  2903. var Navigation = /*#__PURE__*/Object.freeze({
  2904. __proto__: null,
  2905. view: view,
  2906. owner: owner$3
  2907. });
  2908. const find$2 = (element) => {
  2909. const doc = getDocument();
  2910. const scroll = get$b(doc);
  2911. // Get the path of iframe elements to this element.
  2912. const path = pathTo(element, Navigation);
  2913. return path.fold(curry(absolute$3, element), (frames) => {
  2914. const offset = viewport$1(element);
  2915. const r = foldr(frames, (b, a) => {
  2916. const loc = viewport$1(a);
  2917. return {
  2918. left: b.left + loc.left,
  2919. top: b.top + loc.top
  2920. };
  2921. }, { left: 0, top: 0 });
  2922. return SugarPosition(r.left + offset.left + scroll.left, r.top + offset.top + scroll.top);
  2923. });
  2924. };
  2925. const pointed = (point, width, height) => ({
  2926. point,
  2927. width,
  2928. height
  2929. });
  2930. const rect = (x, y, width, height) => ({
  2931. x,
  2932. y,
  2933. width,
  2934. height
  2935. });
  2936. const bounds = (x, y, width, height) => ({
  2937. x,
  2938. y,
  2939. width,
  2940. height,
  2941. right: x + width,
  2942. bottom: y + height
  2943. });
  2944. const box$1 = (element) => {
  2945. const xy = absolute$3(element);
  2946. const w = getOuter(element);
  2947. const h = getOuter$1(element);
  2948. return bounds(xy.left, xy.top, w, h);
  2949. };
  2950. // NOTE: We used to use AriaFocus.preserve here, but there is no reason to do that now that
  2951. // we are not changing the visibility of the element. Hopefully (2015-09-29).
  2952. const absolute$2 = (element) => {
  2953. const position = find$2(element);
  2954. const width = getOuter(element);
  2955. const height = getOuter$1(element);
  2956. return bounds(position.left, position.top, width, height);
  2957. };
  2958. const constrain = (original, constraint) => {
  2959. const left = Math.max(original.x, constraint.x);
  2960. const top = Math.max(original.y, constraint.y);
  2961. const right = Math.min(original.right, constraint.right);
  2962. const bottom = Math.min(original.bottom, constraint.bottom);
  2963. const width = right - left;
  2964. const height = bottom - top;
  2965. return bounds(left, top, width, height);
  2966. };
  2967. const constrainByMany = (original, constraints) => {
  2968. return foldl(constraints, (acc, c) => constrain(acc, c), original);
  2969. };
  2970. const win = () => getBounds$1(window);
  2971. const isSource = (component, simulatedEvent) => eq(component.element, simulatedEvent.event.target);
  2972. const getOffsetParent = (element) => {
  2973. // Firefox sets the offsetParent to the body when fixed instead of null like
  2974. // all other browsers. So we need to check if the element is fixed and if so then
  2975. // disregard the elements offsetParent.
  2976. const isFixed = is$1(getRaw(element, 'position'), 'fixed');
  2977. const offsetParent$1 = isFixed ? Optional.none() : offsetParent(element);
  2978. return offsetParent$1.orThunk(() => {
  2979. const marker = SugarElement.fromTag('span');
  2980. // PERFORMANCE: Append the marker to the parent element, as adding it before the current element will
  2981. // trigger the styles to be recalculated which is a little costly (particularly in scroll/resize events)
  2982. return parent(element).bind((parent) => {
  2983. append$2(parent, marker);
  2984. const offsetParent$1 = offsetParent(marker);
  2985. remove$7(marker);
  2986. return offsetParent$1;
  2987. });
  2988. });
  2989. };
  2990. /*
  2991. * This allows the absolute coordinates to be obtained by adding the
  2992. * origin to the offset coordinates and not needing to know scroll.
  2993. */
  2994. const getOrigin = (element) => getOffsetParent(element).map(absolute$3).getOrThunk(() => SugarPosition(0, 0));
  2995. const describedBy = (describedElement, describeElement) => {
  2996. const describeId = Optional.from(get$g(describedElement, 'id'))
  2997. .getOrThunk(() => {
  2998. const id = generate$6('aria');
  2999. set$9(describeElement, 'id', id);
  3000. return id;
  3001. });
  3002. set$9(describedElement, 'aria-describedby', describeId);
  3003. };
  3004. const remove$1 = (describedElement) => {
  3005. remove$8(describedElement, 'aria-describedby');
  3006. };
  3007. var SimpleResultType;
  3008. (function (SimpleResultType) {
  3009. SimpleResultType[SimpleResultType["Error"] = 0] = "Error";
  3010. SimpleResultType[SimpleResultType["Value"] = 1] = "Value";
  3011. })(SimpleResultType || (SimpleResultType = {}));
  3012. const fold$1 = (res, onError, onValue) => res.stype === SimpleResultType.Error ? onError(res.serror) : onValue(res.svalue);
  3013. const partition$1 = (results) => {
  3014. const values = [];
  3015. const errors = [];
  3016. each$1(results, (obj) => {
  3017. fold$1(obj, (err) => errors.push(err), (val) => values.push(val));
  3018. });
  3019. return { values, errors };
  3020. };
  3021. const mapError = (res, f) => {
  3022. if (res.stype === SimpleResultType.Error) {
  3023. return { stype: SimpleResultType.Error, serror: f(res.serror) };
  3024. }
  3025. else {
  3026. return res;
  3027. }
  3028. };
  3029. const map = (res, f) => {
  3030. if (res.stype === SimpleResultType.Value) {
  3031. return { stype: SimpleResultType.Value, svalue: f(res.svalue) };
  3032. }
  3033. else {
  3034. return res;
  3035. }
  3036. };
  3037. const bind = (res, f) => {
  3038. if (res.stype === SimpleResultType.Value) {
  3039. return f(res.svalue);
  3040. }
  3041. else {
  3042. return res;
  3043. }
  3044. };
  3045. const bindError = (res, f) => {
  3046. if (res.stype === SimpleResultType.Error) {
  3047. return f(res.serror);
  3048. }
  3049. else {
  3050. return res;
  3051. }
  3052. };
  3053. const svalue = (v) => ({ stype: SimpleResultType.Value, svalue: v });
  3054. const serror = (e) => ({ stype: SimpleResultType.Error, serror: e });
  3055. const toResult$1 = (res) => fold$1(res, Result.error, Result.value);
  3056. const fromResult = (res) => res.fold(serror, svalue);
  3057. const SimpleResult = {
  3058. fromResult,
  3059. toResult: toResult$1,
  3060. svalue,
  3061. partition: partition$1,
  3062. serror,
  3063. bind,
  3064. bindError,
  3065. map,
  3066. mapError,
  3067. fold: fold$1
  3068. };
  3069. const formatObj = (input) => {
  3070. return isObject(input) && keys(input).length > 100 ? ' removed due to size' : JSON.stringify(input, null, 2);
  3071. };
  3072. const formatErrors = (errors) => {
  3073. const es = errors.length > 10 ? errors.slice(0, 10).concat([
  3074. {
  3075. path: [],
  3076. getErrorInfo: constant$1('... (only showing first ten failures)')
  3077. }
  3078. ]) : errors;
  3079. // TODO: Work out a better split between PrettyPrinter and SchemaError
  3080. return map$2(es, (e) => {
  3081. return 'Failed path: (' + e.path.join(' > ') + ')\n' + e.getErrorInfo();
  3082. });
  3083. };
  3084. const nu$7 = (path, getErrorInfo) => {
  3085. return SimpleResult.serror([{
  3086. path,
  3087. // This is lazy so that it isn't calculated unnecessarily
  3088. getErrorInfo
  3089. }]);
  3090. };
  3091. const missingRequired = (path, key, obj) => nu$7(path, () => 'Could not find valid *required* value for "' + key + '" in ' + formatObj(obj));
  3092. const missingKey = (path, key) => nu$7(path, () => 'Choice schema did not contain choice key: "' + key + '"');
  3093. const missingBranch = (path, branches, branch) => nu$7(path, () => 'The chosen schema: "' + branch + '" did not exist in branches: ' + formatObj(branches));
  3094. const unsupportedFields = (path, unsupported) => nu$7(path, () => 'There are unsupported fields: [' + unsupported.join(', ') + '] specified');
  3095. const custom = (path, err) => nu$7(path, constant$1(err));
  3096. const value$1 = (validator) => {
  3097. const extract = (path, val) => {
  3098. return SimpleResult.bindError(validator(val), (err) => custom(path, err));
  3099. };
  3100. const toString = constant$1('val');
  3101. return {
  3102. extract,
  3103. toString
  3104. };
  3105. };
  3106. const anyValue$1 = value$1(SimpleResult.svalue);
  3107. const anyValue = constant$1(anyValue$1);
  3108. const typedValue = (validator, expectedType) => value$1((a) => {
  3109. const actualType = typeof a;
  3110. return validator(a) ? SimpleResult.svalue(a) : SimpleResult.serror(`Expected type: ${expectedType} but got: ${actualType}`);
  3111. });
  3112. const number = typedValue(isNumber, 'number');
  3113. const string = typedValue(isString, 'string');
  3114. const boolean = typedValue(isBoolean, 'boolean');
  3115. const functionProcessor = typedValue(isFunction, 'function');
  3116. // Test if a value can be copied by the structured clone algorithm and hence sendable via postMessage
  3117. // https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
  3118. // from https://stackoverflow.com/a/32673910/7377237 with adjustments for typescript
  3119. const isPostMessageable = (val) => {
  3120. if (Object(val) !== val) { // Primitive value
  3121. return true;
  3122. }
  3123. switch ({}.toString.call(val).slice(8, -1)) { // Class
  3124. case 'Boolean':
  3125. case 'Number':
  3126. case 'String':
  3127. case 'Date':
  3128. case 'RegExp':
  3129. case 'Blob':
  3130. case 'FileList':
  3131. case 'ImageData':
  3132. case 'ImageBitmap':
  3133. case 'ArrayBuffer':
  3134. return true;
  3135. case 'Array':
  3136. case 'Object':
  3137. return Object.keys(val).every((prop) => isPostMessageable(val[prop]));
  3138. default:
  3139. return false;
  3140. }
  3141. };
  3142. const postMessageable = value$1((a) => {
  3143. if (isPostMessageable(a)) {
  3144. return SimpleResult.svalue(a);
  3145. }
  3146. else {
  3147. return SimpleResult.serror('Expected value to be acceptable for sending via postMessage');
  3148. }
  3149. });
  3150. const required$2 = () => ({ tag: "required" /* FieldPresenceTag.Required */, process: {} });
  3151. const defaultedThunk = (fallbackThunk) => ({ tag: "defaultedThunk" /* FieldPresenceTag.DefaultedThunk */, process: fallbackThunk });
  3152. const defaulted$1 = (fallback) => defaultedThunk(constant$1(fallback));
  3153. const asOption = () => ({ tag: "option" /* FieldPresenceTag.Option */, process: {} });
  3154. const mergeWithThunk = (baseThunk) => ({ tag: "mergeWithThunk" /* FieldPresenceTag.MergeWithThunk */, process: baseThunk });
  3155. const mergeWith = (base) => mergeWithThunk(constant$1(base));
  3156. const field$2 = (key, newKey, presence, prop) => ({ tag: "field" /* FieldTag.Field */, key, newKey, presence, prop });
  3157. const customField$1 = (newKey, instantiator) => ({ tag: "custom" /* FieldTag.CustomField */, newKey, instantiator });
  3158. const fold = (value, ifField, ifCustom) => {
  3159. switch (value.tag) {
  3160. case "field" /* FieldTag.Field */:
  3161. return ifField(value.key, value.newKey, value.presence, value.prop);
  3162. case "custom" /* FieldTag.CustomField */:
  3163. return ifCustom(value.newKey, value.instantiator);
  3164. }
  3165. };
  3166. const mergeValues$1 = (values, base) => {
  3167. return SimpleResult.svalue(deepMerge(base, merge$1.apply(undefined, values)));
  3168. };
  3169. const mergeErrors$1 = (errors) => compose(SimpleResult.serror, flatten)(errors);
  3170. const consolidateObj = (objects, base) => {
  3171. const partition = SimpleResult.partition(objects);
  3172. return partition.errors.length > 0 ? mergeErrors$1(partition.errors) : mergeValues$1(partition.values, base);
  3173. };
  3174. const consolidateArr = (objects) => {
  3175. const partitions = SimpleResult.partition(objects);
  3176. return partitions.errors.length > 0 ? mergeErrors$1(partitions.errors) : SimpleResult.svalue(partitions.values);
  3177. };
  3178. const ResultCombine = {
  3179. consolidateObj,
  3180. consolidateArr
  3181. };
  3182. const requiredAccess = (path, obj, key, bundle) =>
  3183. // In required mode, if it is undefined, it is an error.
  3184. get$h(obj, key).fold(() => missingRequired(path, key, obj), bundle);
  3185. const fallbackAccess = (obj, key, fallback, bundle) => {
  3186. const v = get$h(obj, key).getOrThunk(() => fallback(obj));
  3187. return bundle(v);
  3188. };
  3189. const optionAccess = (obj, key, bundle) => bundle(get$h(obj, key));
  3190. const optionDefaultedAccess = (obj, key, fallback, bundle) => {
  3191. const opt = get$h(obj, key).map((val) => val === true ? fallback(obj) : val);
  3192. return bundle(opt);
  3193. };
  3194. const extractField = (field, path, obj, key, prop) => {
  3195. const bundle = (av) => prop.extract(path.concat([key]), av);
  3196. const bundleAsOption = (optValue) => optValue.fold(() => SimpleResult.svalue(Optional.none()), (ov) => {
  3197. const result = prop.extract(path.concat([key]), ov);
  3198. return SimpleResult.map(result, Optional.some);
  3199. });
  3200. switch (field.tag) {
  3201. case "required" /* FieldPresenceTag.Required */:
  3202. return requiredAccess(path, obj, key, bundle);
  3203. case "defaultedThunk" /* FieldPresenceTag.DefaultedThunk */:
  3204. return fallbackAccess(obj, key, field.process, bundle);
  3205. case "option" /* FieldPresenceTag.Option */:
  3206. return optionAccess(obj, key, bundleAsOption);
  3207. case "defaultedOptionThunk" /* FieldPresenceTag.DefaultedOptionThunk */:
  3208. return optionDefaultedAccess(obj, key, field.process, bundleAsOption);
  3209. case "mergeWithThunk" /* FieldPresenceTag.MergeWithThunk */: {
  3210. return fallbackAccess(obj, key, constant$1({}), (v) => {
  3211. const result = deepMerge(field.process(obj), v);
  3212. return bundle(result);
  3213. });
  3214. }
  3215. }
  3216. };
  3217. const extractFields = (path, obj, fields) => {
  3218. const success = {};
  3219. const errors = [];
  3220. // PERFORMANCE: We use a for loop here instead of Arr.each as this is a hot code path
  3221. for (const field of fields) {
  3222. fold(field, (key, newKey, presence, prop) => {
  3223. const result = extractField(presence, path, obj, key, prop);
  3224. SimpleResult.fold(result, (err) => {
  3225. errors.push(...err);
  3226. }, (res) => {
  3227. success[newKey] = res;
  3228. });
  3229. }, (newKey, instantiator) => {
  3230. success[newKey] = instantiator(obj);
  3231. });
  3232. }
  3233. return errors.length > 0 ? SimpleResult.serror(errors) : SimpleResult.svalue(success);
  3234. };
  3235. const valueThunk = (getDelegate) => {
  3236. const extract = (path, val) => getDelegate().extract(path, val);
  3237. const toString = () => getDelegate().toString();
  3238. return {
  3239. extract,
  3240. toString
  3241. };
  3242. };
  3243. // This is because Obj.keys can return things where the key is set to undefined.
  3244. const getSetKeys = (obj) => keys(filter$1(obj, isNonNullable));
  3245. const objOfOnly = (fields) => {
  3246. const delegate = objOf(fields);
  3247. const fieldNames = foldr(fields, (acc, value) => {
  3248. return fold(value, (key) => deepMerge(acc, { [key]: true }), constant$1(acc));
  3249. }, {});
  3250. const extract = (path, o) => {
  3251. const keys = isBoolean(o) ? [] : getSetKeys(o);
  3252. const extra = filter$2(keys, (k) => !hasNonNullableKey(fieldNames, k));
  3253. return extra.length === 0 ? delegate.extract(path, o) : unsupportedFields(path, extra);
  3254. };
  3255. return {
  3256. extract,
  3257. toString: delegate.toString
  3258. };
  3259. };
  3260. const objOf = (values) => {
  3261. const extract = (path, o) => extractFields(path, o, values);
  3262. const toString = () => {
  3263. const fieldStrings = map$2(values, (value) => fold(value, (key, _okey, _presence, prop) => key + ' -> ' + prop.toString(), (newKey, _instantiator) => 'state(' + newKey + ')'));
  3264. return 'obj{\n' + fieldStrings.join('\n') + '}';
  3265. };
  3266. return {
  3267. extract,
  3268. toString
  3269. };
  3270. };
  3271. const arrOf = (prop) => {
  3272. const extract = (path, array) => {
  3273. const results = map$2(array, (a, i) => prop.extract(path.concat(['[' + i + ']']), a));
  3274. return ResultCombine.consolidateArr(results);
  3275. };
  3276. const toString = () => 'array(' + prop.toString() + ')';
  3277. return {
  3278. extract,
  3279. toString
  3280. };
  3281. };
  3282. const oneOf = (props, rawF) => {
  3283. // If f is not supplied, then use identity.
  3284. const f = rawF !== undefined ? rawF : identity;
  3285. const extract = (path, val) => {
  3286. const errors = [];
  3287. // Return on first match
  3288. for (const prop of props) {
  3289. const res = prop.extract(path, val);
  3290. if (res.stype === SimpleResultType.Value) {
  3291. return {
  3292. stype: SimpleResultType.Value,
  3293. svalue: f(res.svalue)
  3294. };
  3295. }
  3296. errors.push(res);
  3297. }
  3298. // All failed, return errors
  3299. return ResultCombine.consolidateArr(errors);
  3300. };
  3301. const toString = () => 'oneOf(' + map$2(props, (prop) => prop.toString()).join(', ') + ')';
  3302. return {
  3303. extract,
  3304. toString
  3305. };
  3306. };
  3307. const setOf$1 = (validator, prop) => {
  3308. const validateKeys = (path, keys) => arrOf(value$1(validator)).extract(path, keys);
  3309. const extract = (path, o) => {
  3310. //
  3311. const keys$1 = keys(o);
  3312. const validatedKeys = validateKeys(path, keys$1);
  3313. return SimpleResult.bind(validatedKeys, (validKeys) => {
  3314. const schema = map$2(validKeys, (vk) => {
  3315. return field$2(vk, vk, required$2(), prop);
  3316. });
  3317. return objOf(schema).extract(path, o);
  3318. });
  3319. };
  3320. const toString = () => 'setOf(' + prop.toString() + ')';
  3321. return {
  3322. extract,
  3323. toString
  3324. };
  3325. };
  3326. const thunk = (_desc, processor) => {
  3327. const getP = cached(processor);
  3328. const extract = (path, val) => getP().extract(path, val);
  3329. const toString = () => getP().toString();
  3330. return {
  3331. extract,
  3332. toString
  3333. };
  3334. };
  3335. const arrOfObj = compose(arrOf, objOf);
  3336. const chooseFrom = (path, input, branches, ch) => {
  3337. const fields = get$h(branches, ch);
  3338. return fields.fold(() => missingBranch(path, branches, ch), (vp) => vp.extract(path.concat(['branch: ' + ch]), input));
  3339. };
  3340. // The purpose of choose is to have a key which picks which of the schemas to follow.
  3341. // The key will index into the object of schemas: branches
  3342. const choose$2 = (key, branches) => {
  3343. const extract = (path, input) => {
  3344. const choice = get$h(input, key);
  3345. return choice.fold(() => missingKey(path, key), (chosen) => chooseFrom(path, input, branches, chosen));
  3346. };
  3347. const toString = () => 'chooseOn(' + key + '). Possible values: ' + keys(branches);
  3348. return {
  3349. extract,
  3350. toString
  3351. };
  3352. };
  3353. const arrOfVal = () => arrOf(anyValue$1);
  3354. const valueOf = (validator) => value$1((v) => validator(v).fold(SimpleResult.serror, SimpleResult.svalue));
  3355. const setOf = (validator, prop) => setOf$1((v) => SimpleResult.fromResult(validator(v)), prop);
  3356. const extractValue = (label, prop, obj) => {
  3357. const res = prop.extract([label], obj);
  3358. return SimpleResult.mapError(res, (errs) => ({ input: obj, errors: errs }));
  3359. };
  3360. const asRaw = (label, prop, obj) => SimpleResult.toResult(extractValue(label, prop, obj));
  3361. const getOrDie = (extraction) => {
  3362. return extraction.fold((errInfo) => {
  3363. // A readable version of the error.
  3364. throw new Error(formatError(errInfo));
  3365. }, identity);
  3366. };
  3367. const asRawOrDie$1 = (label, prop, obj) => getOrDie(asRaw(label, prop, obj));
  3368. const formatError = (errInfo) => {
  3369. return 'Errors: \n' + formatErrors(errInfo.errors).join('\n') +
  3370. '\n\nInput object: ' + formatObj(errInfo.input);
  3371. };
  3372. const choose$1 = (key, branches) => choose$2(key, map$1(branches, objOf));
  3373. const thunkOf = (desc, schema) => thunk(desc, schema);
  3374. const field$1 = field$2;
  3375. const customField = customField$1;
  3376. const validateEnum = (values) => valueOf((value) => contains$2(values, value) ?
  3377. Result.value(value) :
  3378. Result.error(`Unsupported value: "${value}", choose one of "${values.join(', ')}".`));
  3379. const required$1 = (key) => field$1(key, key, required$2(), anyValue());
  3380. const requiredOf = (key, schema) => field$1(key, key, required$2(), schema);
  3381. const requiredNumber = (key) => requiredOf(key, number);
  3382. const requiredString = (key) => requiredOf(key, string);
  3383. const requiredStringEnum = (key, values) => field$1(key, key, required$2(), validateEnum(values));
  3384. const requiredFunction = (key) => requiredOf(key, functionProcessor);
  3385. const forbid = (key, message) => field$1(key, key, asOption(), value$1((_v) => SimpleResult.serror('The field: ' + key + ' is forbidden. ' + message)));
  3386. const requiredObjOf = (key, objSchema) => field$1(key, key, required$2(), objOf(objSchema));
  3387. const requiredArrayOfObj = (key, objFields) => field$1(key, key, required$2(), arrOfObj(objFields));
  3388. const requiredArrayOf = (key, schema) => field$1(key, key, required$2(), arrOf(schema));
  3389. const option$3 = (key) => field$1(key, key, asOption(), anyValue());
  3390. const optionOf = (key, schema) => field$1(key, key, asOption(), schema);
  3391. const optionNumber = (key) => optionOf(key, number);
  3392. const optionString = (key) => optionOf(key, string);
  3393. const optionStringEnum = (key, values) => optionOf(key, validateEnum(values));
  3394. const optionBoolean = (key) => optionOf(key, boolean);
  3395. const optionFunction = (key) => optionOf(key, functionProcessor);
  3396. const optionArrayOf = (key, schema) => optionOf(key, arrOf(schema));
  3397. const optionObjOf = (key, objSchema) => optionOf(key, objOf(objSchema));
  3398. const optionObjOfOnly = (key, objSchema) => optionOf(key, objOfOnly(objSchema));
  3399. const defaulted = (key, fallback) => field$1(key, key, defaulted$1(fallback), anyValue());
  3400. const defaultedOf = (key, fallback, schema) => field$1(key, key, defaulted$1(fallback), schema);
  3401. const defaultedNumber = (key, fallback) => defaultedOf(key, fallback, number);
  3402. const defaultedString = (key, fallback) => defaultedOf(key, fallback, string);
  3403. const defaultedStringEnum = (key, fallback, values) => defaultedOf(key, fallback, validateEnum(values));
  3404. const defaultedBoolean = (key, fallback) => defaultedOf(key, fallback, boolean);
  3405. const defaultedFunction = (key, fallback) => defaultedOf(key, fallback, functionProcessor);
  3406. const defaultedPostMsg = (key, fallback) => defaultedOf(key, fallback, postMessageable);
  3407. const defaultedArrayOf = (key, fallback, schema) => defaultedOf(key, fallback, arrOf(schema));
  3408. const defaultedObjOf = (key, fallback, objSchema) => defaultedOf(key, fallback, objOf(objSchema));
  3409. const exclude$1 = (obj, fields) => {
  3410. const r = {};
  3411. each(obj, (v, k) => {
  3412. if (!contains$2(fields, k)) {
  3413. r[k] = v;
  3414. }
  3415. });
  3416. return r;
  3417. };
  3418. const wrap$1 = (key, value) => ({ [key]: value });
  3419. const wrapAll$1 = (keyvalues) => {
  3420. const r = {};
  3421. each$1(keyvalues, (kv) => {
  3422. r[kv.key] = kv.value;
  3423. });
  3424. return r;
  3425. };
  3426. const exclude = (obj, fields) => exclude$1(obj, fields);
  3427. const wrap = (key, value) => wrap$1(key, value);
  3428. const wrapAll = (keyvalues) => wrapAll$1(keyvalues);
  3429. const mergeValues = (values, base) => {
  3430. return values.length === 0 ? Result.value(base) : Result.value(deepMerge(base, merge$1.apply(undefined, values))
  3431. // Merger.deepMerge.apply(undefined, [ base ].concat(values))
  3432. );
  3433. };
  3434. const mergeErrors = (errors) => Result.error(flatten(errors));
  3435. const consolidate = (objs, base) => {
  3436. const partitions = partition$2(objs);
  3437. return partitions.errors.length > 0 ? mergeErrors(partitions.errors) : mergeValues(partitions.values, base);
  3438. };
  3439. const constant = constant$1;
  3440. const touchstart = constant('touchstart');
  3441. const touchmove = constant('touchmove');
  3442. const touchend = constant('touchend');
  3443. const touchcancel = constant('touchcancel');
  3444. const mousedown = constant('mousedown');
  3445. const mousemove = constant('mousemove');
  3446. const mouseout = constant('mouseout');
  3447. const mouseup = constant('mouseup');
  3448. const mouseover = constant('mouseover');
  3449. // Not really a native event as it has to be simulated
  3450. const focusin = constant('focusin');
  3451. const focusout = constant('focusout');
  3452. const keydown = constant('keydown');
  3453. const keyup = constant('keyup');
  3454. const input = constant('input');
  3455. const change = constant('change');
  3456. const click = constant('click');
  3457. const transitioncancel = constant('transitioncancel');
  3458. const transitionend = constant('transitionend');
  3459. const transitionstart = constant('transitionstart');
  3460. const selectstart = constant('selectstart');
  3461. const prefixName = (name) => constant$1('alloy.' + name);
  3462. const alloy = { tap: prefixName('tap') };
  3463. // This is used to pass focus to a component. A component might interpret
  3464. // this event and pass the DOM focus to one of its children, depending on its
  3465. // focus model.
  3466. const focus$3 = prefixName('focus');
  3467. // This event is fired a small amount of time after the blur has fired. This
  3468. // allows the handler to know what was the focused element, and what is now.
  3469. const postBlur = prefixName('blur.post');
  3470. // This event is fired a small amount of time after the paste event has fired.
  3471. const postPaste = prefixName('paste.post');
  3472. // This event is fired by gui.broadcast*. It is defined by 'receivers'
  3473. const receive = prefixName('receive');
  3474. // This event is for executing buttons and things that have (mostly) enter actions
  3475. const execute$5 = prefixName('execute');
  3476. // This event is used by a menu to tell an item to focus itself because it has been
  3477. // selected. This might automatically focus inside the item, it might focus the outer
  3478. // part of the widget etc.
  3479. const focusItem = prefixName('focus.item');
  3480. // This event represents a touchstart and touchend on the same location, and fires on
  3481. // the touchend
  3482. const tap = alloy.tap;
  3483. // This event represents a longpress on the same location
  3484. const longpress = prefixName('longpress');
  3485. // Fire by a child element to tell the outer element to close
  3486. const sandboxClose = prefixName('sandbox.close');
  3487. // Tell the typeahead to cancel any pending fetches (that haven't already executed)
  3488. const typeaheadCancel = prefixName('typeahead.cancel');
  3489. // Fired when adding to a world
  3490. const systemInit = prefixName('system.init');
  3491. // Fired when a touchmove on the document happens
  3492. const documentTouchmove = prefixName('system.touchmove');
  3493. // Fired when a touchend on the document happens
  3494. const documentTouchend = prefixName('system.touchend');
  3495. // Fired when the window scrolls
  3496. const windowScroll = prefixName('system.scroll');
  3497. // Fired when the window resizes
  3498. const windowResize = prefixName('system.resize');
  3499. const attachedToDom = prefixName('system.attached');
  3500. const detachedFromDom = prefixName('system.detached');
  3501. const dismissRequested = prefixName('system.dismissRequested');
  3502. const repositionRequested = prefixName('system.repositionRequested');
  3503. const focusShifted = prefixName('focusmanager.shifted');
  3504. // Fired when slots are made hidden/shown
  3505. const slotVisibility = prefixName('slotcontainer.visibility');
  3506. // Used for containers outside the mothership that scroll. Used by docking.
  3507. const externalElementScroll = prefixName('system.external.element.scroll');
  3508. const changeTab = prefixName('change.tab');
  3509. const dismissTab = prefixName('dismiss.tab');
  3510. const highlight$1 = prefixName('highlight');
  3511. const dehighlight$1 = prefixName('dehighlight');
  3512. const element = (elem) => getHtml(elem);
  3513. const unknown = 'unknown';
  3514. /*
  3515. typescipt qwerk:
  3516. const debugging: boolean = true;
  3517. if (boolean === false) { -> this throws a type error! // TS2365:Operator '===' cannot be applied to types 'false' and 'true'
  3518. https://www.typescriptlang.org/play/#src=const%20foo%3A%20boolean%20%3D%20true%3B%0D%0A%0D%0Aif%20(foo%20%3D%3D%3D%20false)%20%7B%0D%0A%20%20%20%20%0D%0A%7D
  3519. }
  3520. */
  3521. const debugging = true;
  3522. var EventConfiguration;
  3523. (function (EventConfiguration) {
  3524. EventConfiguration[EventConfiguration["STOP"] = 0] = "STOP";
  3525. EventConfiguration[EventConfiguration["NORMAL"] = 1] = "NORMAL";
  3526. EventConfiguration[EventConfiguration["LOGGING"] = 2] = "LOGGING";
  3527. })(EventConfiguration || (EventConfiguration = {}));
  3528. const eventConfig = Cell({});
  3529. const makeEventLogger = (eventName, initialTarget) => {
  3530. const sequence = [];
  3531. const startTime = new Date().getTime();
  3532. return {
  3533. logEventCut: (_name, target, purpose) => {
  3534. sequence.push({ outcome: 'cut', target, purpose });
  3535. },
  3536. logEventStopped: (_name, target, purpose) => {
  3537. sequence.push({ outcome: 'stopped', target, purpose });
  3538. },
  3539. logNoParent: (_name, target, purpose) => {
  3540. sequence.push({ outcome: 'no-parent', target, purpose });
  3541. },
  3542. logEventNoHandlers: (_name, target) => {
  3543. sequence.push({ outcome: 'no-handlers-left', target });
  3544. },
  3545. logEventResponse: (_name, target, purpose) => {
  3546. sequence.push({ outcome: 'response', purpose, target });
  3547. },
  3548. write: () => {
  3549. const finishTime = new Date().getTime();
  3550. if (contains$2(['mousemove', 'mouseover', 'mouseout', systemInit()], eventName)) {
  3551. return;
  3552. }
  3553. // eslint-disable-next-line no-console
  3554. console.log(eventName, {
  3555. event: eventName,
  3556. time: finishTime - startTime,
  3557. target: initialTarget.dom,
  3558. sequence: map$2(sequence, (s) => {
  3559. if (!contains$2(['cut', 'stopped', 'response'], s.outcome)) {
  3560. return s.outcome;
  3561. }
  3562. else {
  3563. return '{' + s.purpose + '} ' + s.outcome + ' at (' + element(s.target) + ')';
  3564. }
  3565. })
  3566. });
  3567. }
  3568. };
  3569. };
  3570. const processEvent = (eventName, initialTarget, f) => {
  3571. const status = get$h(eventConfig.get(), eventName).orThunk(() => {
  3572. const patterns = keys(eventConfig.get());
  3573. return findMap(patterns, (p) => eventName.indexOf(p) > -1 ? Optional.some(eventConfig.get()[p]) : Optional.none());
  3574. }).getOr(EventConfiguration.NORMAL);
  3575. switch (status) {
  3576. case EventConfiguration.NORMAL:
  3577. return f(noLogger());
  3578. case EventConfiguration.LOGGING: {
  3579. const logger = makeEventLogger(eventName, initialTarget);
  3580. const output = f(logger);
  3581. logger.write();
  3582. return output;
  3583. }
  3584. case EventConfiguration.STOP:
  3585. // Does not even run the function to trigger event and listen to handlers
  3586. return true;
  3587. }
  3588. };
  3589. // Ignore these files in the error stack
  3590. const path = [
  3591. 'alloy/data/Fields',
  3592. 'alloy/debugging/Debugging'
  3593. ];
  3594. const getTrace = () => {
  3595. if (debugging === false) {
  3596. return unknown;
  3597. }
  3598. const err = new Error();
  3599. if (err.stack !== undefined) {
  3600. const lines = err.stack.split('\n');
  3601. return find$5(lines, (line) => line.indexOf('alloy') > 0 && !exists(path, (p) => line.indexOf(p) > -1)).getOr(unknown);
  3602. }
  3603. else {
  3604. return unknown;
  3605. }
  3606. };
  3607. const ignoreEvent = {
  3608. logEventCut: noop,
  3609. logEventStopped: noop,
  3610. logNoParent: noop,
  3611. logEventNoHandlers: noop,
  3612. logEventResponse: noop,
  3613. write: noop
  3614. };
  3615. const monitorEvent = (eventName, initialTarget, f) => processEvent(eventName, initialTarget, f);
  3616. const noLogger = constant$1(ignoreEvent);
  3617. const menuFields = constant$1([
  3618. required$1('menu'),
  3619. required$1('selectedMenu')
  3620. ]);
  3621. const itemFields = constant$1([
  3622. required$1('item'),
  3623. required$1('selectedItem')
  3624. ]);
  3625. constant$1(objOf(itemFields().concat(menuFields())));
  3626. const itemSchema$3 = constant$1(objOf(itemFields()));
  3627. const _initSize = requiredObjOf('initSize', [
  3628. required$1('numColumns'),
  3629. required$1('numRows')
  3630. ]);
  3631. const itemMarkers = () => requiredOf('markers', itemSchema$3());
  3632. const tieredMenuMarkers = () => requiredObjOf('markers', [
  3633. required$1('backgroundMenu')
  3634. ].concat(menuFields()).concat(itemFields()));
  3635. const markers$1 = (required) => requiredObjOf('markers', map$2(required, required$1));
  3636. const onPresenceHandler = (label, fieldName, presence) => {
  3637. // We care about where the handler was declared (in terms of which schema)
  3638. getTrace();
  3639. return field$1(fieldName, fieldName, presence,
  3640. // Apply some wrapping to their supplied function
  3641. valueOf((f) => Result.value((...args) => {
  3642. return f.apply(undefined, args);
  3643. })));
  3644. };
  3645. const onHandler = (fieldName) => onPresenceHandler('onHandler', fieldName, defaulted$1(noop));
  3646. const onKeyboardHandler = (fieldName) => onPresenceHandler('onKeyboardHandler', fieldName, defaulted$1(Optional.none));
  3647. const onStrictHandler = (fieldName) => onPresenceHandler('onHandler', fieldName, required$2());
  3648. const onStrictKeyboardHandler = (fieldName) => onPresenceHandler('onKeyboardHandler', fieldName, required$2());
  3649. const output$1 = (name, value) => customField(name, constant$1(value));
  3650. const snapshot = (name) => customField(name, identity);
  3651. const initSize = constant$1(_initSize);
  3652. const markAsBehaviourApi = (f, apiName, apiFunction) => {
  3653. const delegate = apiFunction.toString();
  3654. const endIndex = delegate.indexOf(')') + 1;
  3655. const openBracketIndex = delegate.indexOf('(');
  3656. const parameters = delegate.substring(openBracketIndex + 1, endIndex - 1).split(/,\s*/);
  3657. f.toFunctionAnnotation = () => ({
  3658. name: apiName,
  3659. parameters: cleanParameters(parameters.slice(0, 1).concat(parameters.slice(3)))
  3660. });
  3661. return f;
  3662. };
  3663. // Remove any comment (/*) at end of parameter names
  3664. const cleanParameters = (parameters) => map$2(parameters, (p) => endsWith(p, '/*') ? p.substring(0, p.length - '/*'.length) : p);
  3665. const markAsExtraApi = (f, extraName) => {
  3666. const delegate = f.toString();
  3667. const endIndex = delegate.indexOf(')') + 1;
  3668. const openBracketIndex = delegate.indexOf('(');
  3669. const parameters = delegate.substring(openBracketIndex + 1, endIndex - 1).split(/,\s*/);
  3670. f.toFunctionAnnotation = () => ({
  3671. name: extraName,
  3672. parameters: cleanParameters(parameters)
  3673. });
  3674. return f;
  3675. };
  3676. const markAsSketchApi = (f, apiFunction) => {
  3677. const delegate = apiFunction.toString();
  3678. const endIndex = delegate.indexOf(')') + 1;
  3679. const openBracketIndex = delegate.indexOf('(');
  3680. const parameters = delegate.substring(openBracketIndex + 1, endIndex - 1).split(/,\s*/);
  3681. f.toFunctionAnnotation = () => ({
  3682. name: 'OVERRIDE',
  3683. parameters: cleanParameters(parameters.slice(1))
  3684. });
  3685. return f;
  3686. };
  3687. const DelayedFunction = (fun, delay) => {
  3688. let ref = null;
  3689. const schedule = (...args) => {
  3690. ref = setTimeout(() => {
  3691. fun.apply(null, args);
  3692. ref = null;
  3693. }, delay);
  3694. };
  3695. const cancel = () => {
  3696. if (ref !== null) {
  3697. clearTimeout(ref);
  3698. ref = null;
  3699. }
  3700. };
  3701. return {
  3702. cancel,
  3703. schedule
  3704. };
  3705. };
  3706. const SIGNIFICANT_MOVE = 5;
  3707. const LONGPRESS_DELAY = 400;
  3708. const getTouch = (event) => {
  3709. const raw = event.raw;
  3710. if (raw.touches === undefined || raw.touches.length !== 1) {
  3711. return Optional.none();
  3712. }
  3713. return Optional.some(raw.touches[0]);
  3714. };
  3715. // Check to see if the touch has changed a *significant* amount
  3716. const isFarEnough = (touch, data) => {
  3717. const distX = Math.abs(touch.clientX - data.x);
  3718. const distY = Math.abs(touch.clientY - data.y);
  3719. return distX > SIGNIFICANT_MOVE || distY > SIGNIFICANT_MOVE;
  3720. };
  3721. const monitor = (settings) => {
  3722. /* A tap event is a combination of touchstart and touchend on the same element
  3723. * without a *significant* touchmove in between.
  3724. */
  3725. const startData = value$2();
  3726. const longpressFired = Cell(false);
  3727. const longpress$1 = DelayedFunction((event) => {
  3728. settings.triggerEvent(longpress(), event);
  3729. longpressFired.set(true);
  3730. }, LONGPRESS_DELAY);
  3731. const handleTouchstart = (event) => {
  3732. getTouch(event).each((touch) => {
  3733. longpress$1.cancel();
  3734. const data = {
  3735. x: touch.clientX,
  3736. y: touch.clientY,
  3737. target: event.target
  3738. };
  3739. longpress$1.schedule(event);
  3740. longpressFired.set(false);
  3741. startData.set(data);
  3742. });
  3743. return Optional.none();
  3744. };
  3745. const handleTouchmove = (event) => {
  3746. longpress$1.cancel();
  3747. getTouch(event).each((touch) => {
  3748. startData.on((data) => {
  3749. if (isFarEnough(touch, data)) {
  3750. startData.clear();
  3751. }
  3752. });
  3753. });
  3754. return Optional.none();
  3755. };
  3756. const handleTouchend = (event) => {
  3757. longpress$1.cancel();
  3758. const isSame = (data) => eq(data.target, event.target);
  3759. return startData.get().filter(isSame).map((_data) => {
  3760. if (longpressFired.get()) {
  3761. event.prevent();
  3762. return false;
  3763. }
  3764. else {
  3765. return settings.triggerEvent(tap(), event);
  3766. }
  3767. });
  3768. };
  3769. const handlers = wrapAll([
  3770. { key: touchstart(), value: handleTouchstart },
  3771. { key: touchmove(), value: handleTouchmove },
  3772. { key: touchend(), value: handleTouchend }
  3773. ]);
  3774. const fireIfReady = (event, type) => get$h(handlers, type).bind((handler) => handler(event));
  3775. return {
  3776. fireIfReady
  3777. };
  3778. };
  3779. var FocusInsideModes;
  3780. (function (FocusInsideModes) {
  3781. FocusInsideModes["OnFocusMode"] = "onFocus";
  3782. FocusInsideModes["OnEnterOrSpaceMode"] = "onEnterOrSpace";
  3783. FocusInsideModes["OnApiMode"] = "onApi";
  3784. })(FocusInsideModes || (FocusInsideModes = {}));
  3785. const _placeholder = 'placeholder';
  3786. const adt$7 = Adt.generate([
  3787. { single: ['required', 'valueThunk'] },
  3788. { multiple: ['required', 'valueThunks'] }
  3789. ]);
  3790. const isSubstituted = (spec) => has$2(spec, 'uiType');
  3791. const subPlaceholder = (owner, detail, compSpec, placeholders) => {
  3792. if (owner.exists((o) => o !== compSpec.owner)) {
  3793. return adt$7.single(true, constant$1(compSpec));
  3794. }
  3795. // Ignore having to find something for the time being.
  3796. return get$h(placeholders, compSpec.name).fold(() => {
  3797. throw new Error('Unknown placeholder component: ' + compSpec.name + '\nKnown: [' +
  3798. keys(placeholders) + ']\nNamespace: ' + owner.getOr('none') + '\nSpec: ' + JSON.stringify(compSpec, null, 2));
  3799. }, (newSpec) =>
  3800. // Must return a single/multiple type
  3801. newSpec.replace());
  3802. };
  3803. const scan = (owner, detail, compSpec, placeholders) => {
  3804. if (isSubstituted(compSpec) && compSpec.uiType === _placeholder) {
  3805. return subPlaceholder(owner, detail, compSpec, placeholders);
  3806. }
  3807. else {
  3808. return adt$7.single(false, constant$1(compSpec));
  3809. }
  3810. };
  3811. const substitute = (owner, detail, compSpec, placeholders) => {
  3812. const base = scan(owner, detail, compSpec, placeholders);
  3813. return base.fold((req, valueThunk) => {
  3814. const value = isSubstituted(compSpec) ? valueThunk(detail, compSpec.config, compSpec.validated) : valueThunk(detail);
  3815. const childSpecs = get$h(value, 'components').getOr([]);
  3816. const substituted = bind$3(childSpecs, (c) => substitute(owner, detail, c, placeholders));
  3817. return [
  3818. {
  3819. ...value,
  3820. components: substituted
  3821. }
  3822. ];
  3823. }, (req, valuesThunk) => {
  3824. if (isSubstituted(compSpec)) {
  3825. const values = valuesThunk(detail, compSpec.config, compSpec.validated);
  3826. // Allow a preprocessing step for groups before returning the components
  3827. const preprocessor = compSpec.validated.preprocess.getOr(identity);
  3828. return preprocessor(values);
  3829. }
  3830. else {
  3831. return valuesThunk(detail);
  3832. }
  3833. });
  3834. };
  3835. const substituteAll = (owner, detail, components, placeholders) => bind$3(components, (c) => substitute(owner, detail, c, placeholders));
  3836. const oneReplace = (label, replacements) => {
  3837. let called = false;
  3838. const used = () => called;
  3839. const replace = () => {
  3840. if (called) {
  3841. throw new Error('Trying to use the same placeholder more than once: ' + label);
  3842. }
  3843. called = true;
  3844. return replacements;
  3845. };
  3846. const required = () => replacements.fold((req, _) => req, (req, _) => req);
  3847. return {
  3848. name: constant$1(label),
  3849. required,
  3850. used,
  3851. replace
  3852. };
  3853. };
  3854. const substitutePlaces = (owner, detail, components, placeholders) => {
  3855. const ps = map$1(placeholders, (ph, name) => oneReplace(name, ph));
  3856. const outcome = substituteAll(owner, detail, components, ps);
  3857. each(ps, (p) => {
  3858. if (p.used() === false && p.required()) {
  3859. throw new Error('Placeholder: ' + p.name() + ' was not found in components list\nNamespace: ' + owner.getOr('none') + '\nComponents: ' +
  3860. JSON.stringify(detail.components, null, 2));
  3861. }
  3862. });
  3863. return outcome;
  3864. };
  3865. const single$2 = adt$7.single;
  3866. const multiple = adt$7.multiple;
  3867. const placeholder = constant$1(_placeholder);
  3868. const adt$6 = Adt.generate([
  3869. { required: ['data'] },
  3870. { external: ['data'] },
  3871. { optional: ['data'] },
  3872. { group: ['data'] }
  3873. ]);
  3874. const fFactory = defaulted('factory', { sketch: identity });
  3875. const fSchema = defaulted('schema', []);
  3876. const fName = required$1('name');
  3877. const fPname = field$1('pname', 'pname', defaultedThunk((typeSpec) => '<alloy.' + generate$6(typeSpec.name) + '>'), anyValue());
  3878. // Groups cannot choose their schema.
  3879. const fGroupSchema = customField('schema', () => [
  3880. option$3('preprocess')
  3881. ]);
  3882. const fDefaults = defaulted('defaults', constant$1({}));
  3883. const fOverrides = defaulted('overrides', constant$1({}));
  3884. const requiredSpec = objOf([
  3885. fFactory, fSchema, fName, fPname, fDefaults, fOverrides
  3886. ]);
  3887. const externalSpec = objOf([
  3888. fFactory, fSchema, fName, fDefaults, fOverrides
  3889. ]);
  3890. const optionalSpec = objOf([
  3891. fFactory, fSchema, fName, fPname, fDefaults, fOverrides
  3892. ]);
  3893. const groupSpec = objOf([
  3894. fFactory, fGroupSchema, fName,
  3895. required$1('unit'),
  3896. fPname, fDefaults, fOverrides
  3897. ]);
  3898. const asNamedPart = (part) => {
  3899. return part.fold(Optional.some, Optional.none, Optional.some, Optional.some);
  3900. };
  3901. const name$2 = (part) => {
  3902. const get = (data) => data.name;
  3903. return part.fold(get, get, get, get);
  3904. };
  3905. const asCommon = (part) => {
  3906. return part.fold(identity, identity, identity, identity);
  3907. };
  3908. const convert = (adtConstructor, partSchema) => (spec) => {
  3909. const data = asRawOrDie$1('Converting part type', partSchema, spec);
  3910. return adtConstructor(data);
  3911. };
  3912. const required = convert(adt$6.required, requiredSpec);
  3913. const external$1 = convert(adt$6.external, externalSpec);
  3914. const optional = convert(adt$6.optional, optionalSpec);
  3915. const group = convert(adt$6.group, groupSpec);
  3916. const original = constant$1('entirety');
  3917. var PartType = /*#__PURE__*/Object.freeze({
  3918. __proto__: null,
  3919. required: required,
  3920. external: external$1,
  3921. optional: optional,
  3922. group: group,
  3923. asNamedPart: asNamedPart,
  3924. name: name$2,
  3925. asCommon: asCommon,
  3926. original: original
  3927. });
  3928. const combine$2 = (detail, data, partSpec, partValidated) =>
  3929. // Extremely confusing names and types :(
  3930. deepMerge(data.defaults(detail, partSpec, partValidated), partSpec, { uid: detail.partUids[data.name] }, data.overrides(detail, partSpec, partValidated));
  3931. const subs = (owner, detail, parts) => {
  3932. const internals = {};
  3933. const externals = {};
  3934. each$1(parts, (part) => {
  3935. part.fold(
  3936. // Internal
  3937. (data) => {
  3938. internals[data.pname] = single$2(true, (detail, partSpec, partValidated) => data.factory.sketch(combine$2(detail, data, partSpec, partValidated)));
  3939. },
  3940. // External
  3941. (data) => {
  3942. const partSpec = detail.parts[data.name];
  3943. externals[data.name] = constant$1(data.factory.sketch(combine$2(detail, data, partSpec[original()]), partSpec) // This is missing partValidated
  3944. );
  3945. // no placeholders
  3946. },
  3947. // Optional
  3948. (data) => {
  3949. internals[data.pname] = single$2(false, (detail, partSpec, partValidated) => data.factory.sketch(combine$2(detail, data, partSpec, partValidated)));
  3950. },
  3951. // Group
  3952. (data) => {
  3953. internals[data.pname] = multiple(true, (detail, _partSpec, _partValidated) => {
  3954. const units = detail[data.name];
  3955. return map$2(units, (u) =>
  3956. // Group multiples do not take the uid because there is more than one.
  3957. data.factory.sketch(deepMerge(data.defaults(detail, u, _partValidated), u, data.overrides(detail, u))));
  3958. });
  3959. });
  3960. });
  3961. return {
  3962. internals: constant$1(internals),
  3963. externals: constant$1(externals)
  3964. };
  3965. };
  3966. // TODO: Make more functional if performance isn't an issue.
  3967. const generate$5 = (owner, parts) => {
  3968. const r = {};
  3969. each$1(parts, (part) => {
  3970. asNamedPart(part).each((np) => {
  3971. const g = doGenerateOne(owner, np.pname);
  3972. r[np.name] = (config) => {
  3973. const validated = asRawOrDie$1('Part: ' + np.name + ' in ' + owner, objOf(np.schema), config);
  3974. return {
  3975. ...g,
  3976. config,
  3977. validated
  3978. };
  3979. };
  3980. });
  3981. });
  3982. return r;
  3983. };
  3984. // Does not have the config.
  3985. const doGenerateOne = (owner, pname) => ({
  3986. uiType: placeholder(),
  3987. owner,
  3988. name: pname
  3989. });
  3990. const generateOne$1 = (owner, pname, config) => ({
  3991. uiType: placeholder(),
  3992. owner,
  3993. name: pname,
  3994. config,
  3995. validated: {}
  3996. });
  3997. const schemas = (parts) =>
  3998. // This actually has to change. It needs to return the schemas for things that will
  3999. // not appear in the components list, which is only externals
  4000. bind$3(parts, (part) => part.fold(Optional.none, Optional.some, Optional.none, Optional.none).map((data) => requiredObjOf(data.name, data.schema.concat([
  4001. snapshot(original())
  4002. ]))).toArray());
  4003. const names = (parts) => map$2(parts, name$2);
  4004. const substitutes = (owner, detail, parts) => subs(owner, detail, parts);
  4005. const components$1 = (owner, detail, internals) => substitutePlaces(Optional.some(owner), detail, detail.components, internals);
  4006. const getPart = (component, detail, partKey) => {
  4007. const uid = detail.partUids[partKey];
  4008. return component.getSystem().getByUid(uid).toOptional();
  4009. };
  4010. const getPartOrDie = (component, detail, partKey) => getPart(component, detail, partKey).getOrDie('Could not find part: ' + partKey);
  4011. const getParts = (component, detail, partKeys) => {
  4012. const r = {};
  4013. const uids = detail.partUids;
  4014. const system = component.getSystem();
  4015. each$1(partKeys, (pk) => {
  4016. r[pk] = constant$1(system.getByUid(uids[pk]));
  4017. });
  4018. return r;
  4019. };
  4020. const getAllParts = (component, detail) => {
  4021. const system = component.getSystem();
  4022. return map$1(detail.partUids, (pUid, _k) => constant$1(system.getByUid(pUid)));
  4023. };
  4024. const getAllPartNames = (detail) => keys(detail.partUids);
  4025. const getPartsOrDie = (component, detail, partKeys) => {
  4026. const r = {};
  4027. const uids = detail.partUids;
  4028. const system = component.getSystem();
  4029. each$1(partKeys, (pk) => {
  4030. r[pk] = constant$1(system.getByUid(uids[pk]).getOrDie());
  4031. });
  4032. return r;
  4033. };
  4034. const defaultUids = (baseUid, partTypes) => {
  4035. const partNames = names(partTypes);
  4036. return wrapAll(map$2(partNames, (pn) => ({ key: pn, value: baseUid + '-' + pn })));
  4037. };
  4038. const defaultUidsSchema = (partTypes) => field$1('partUids', 'partUids', mergeWithThunk((spec) => defaultUids(spec.uid, partTypes)), anyValue());
  4039. var AlloyParts = /*#__PURE__*/Object.freeze({
  4040. __proto__: null,
  4041. generate: generate$5,
  4042. generateOne: generateOne$1,
  4043. schemas: schemas,
  4044. names: names,
  4045. substitutes: substitutes,
  4046. components: components$1,
  4047. defaultUids: defaultUids,
  4048. defaultUidsSchema: defaultUidsSchema,
  4049. getAllParts: getAllParts,
  4050. getAllPartNames: getAllPartNames,
  4051. getPart: getPart,
  4052. getPartOrDie: getPartOrDie,
  4053. getParts: getParts,
  4054. getPartsOrDie: getPartsOrDie
  4055. });
  4056. const allAlignments = [
  4057. 'valignCentre',
  4058. 'alignLeft',
  4059. 'alignRight',
  4060. 'alignCentre',
  4061. 'top',
  4062. 'bottom',
  4063. 'left',
  4064. 'right',
  4065. 'inset'
  4066. ];
  4067. const nu$6 = (xOffset, yOffset, classes, insetModifier = 1) => {
  4068. const insetXOffset = xOffset * insetModifier;
  4069. const insetYOffset = yOffset * insetModifier;
  4070. const getClasses = (prop) => get$h(classes, prop).getOr([]);
  4071. const make = (xDelta, yDelta, alignmentsOn) => {
  4072. const alignmentsOff = difference(allAlignments, alignmentsOn);
  4073. return {
  4074. offset: SugarPosition(xDelta, yDelta),
  4075. classesOn: bind$3(alignmentsOn, getClasses),
  4076. classesOff: bind$3(alignmentsOff, getClasses)
  4077. };
  4078. };
  4079. return {
  4080. southeast: () => make(-xOffset, yOffset, ['top', 'alignLeft']),
  4081. southwest: () => make(xOffset, yOffset, ['top', 'alignRight']),
  4082. south: () => make(-xOffset / 2, yOffset, ['top', 'alignCentre']),
  4083. northeast: () => make(-xOffset, -yOffset, ['bottom', 'alignLeft']),
  4084. northwest: () => make(xOffset, -yOffset, ['bottom', 'alignRight']),
  4085. north: () => make(-xOffset / 2, -yOffset, ['bottom', 'alignCentre']),
  4086. east: () => make(xOffset, -yOffset / 2, ['valignCentre', 'left']),
  4087. west: () => make(-xOffset, -yOffset / 2, ['valignCentre', 'right']),
  4088. insetNortheast: () => make(insetXOffset, insetYOffset, ['top', 'alignLeft', 'inset']),
  4089. insetNorthwest: () => make(-insetXOffset, insetYOffset, ['top', 'alignRight', 'inset']),
  4090. insetNorth: () => make(-insetXOffset / 2, insetYOffset, ['top', 'alignCentre', 'inset']),
  4091. insetSoutheast: () => make(insetXOffset, -insetYOffset, ['bottom', 'alignLeft', 'inset']),
  4092. insetSouthwest: () => make(-insetXOffset, -insetYOffset, ['bottom', 'alignRight', 'inset']),
  4093. insetSouth: () => make(-insetXOffset / 2, -insetYOffset, ['bottom', 'alignCentre', 'inset']),
  4094. insetEast: () => make(-insetXOffset, -insetYOffset / 2, ['valignCentre', 'right', 'inset']),
  4095. insetWest: () => make(insetXOffset, -insetYOffset / 2, ['valignCentre', 'left', 'inset'])
  4096. };
  4097. };
  4098. const fallback = () => nu$6(0, 0, {});
  4099. const nu$5 = (x, y, bubble, direction, placement, boundsRestriction, labelPrefix, alwaysFit = false) => ({
  4100. x,
  4101. y,
  4102. bubble,
  4103. direction,
  4104. placement,
  4105. restriction: boundsRestriction,
  4106. label: `${labelPrefix}-${placement}`,
  4107. alwaysFit
  4108. });
  4109. const adt$5 = Adt.generate([
  4110. { southeast: [] },
  4111. { southwest: [] },
  4112. { northeast: [] },
  4113. { northwest: [] },
  4114. { south: [] },
  4115. { north: [] },
  4116. { east: [] },
  4117. { west: [] }
  4118. ]);
  4119. const cata$1 = (subject, southeast, southwest, northeast, northwest, south, north, east, west) => subject.fold(southeast, southwest, northeast, northwest, south, north, east, west);
  4120. const cataVertical = (subject, south, middle, north) => subject.fold(south, south, north, north, south, north, middle, middle);
  4121. const cataHorizontal = (subject, east, middle, west) => subject.fold(east, west, east, west, middle, middle, east, west);
  4122. // TODO: Simplify with the typescript approach.
  4123. const southeast$3 = adt$5.southeast;
  4124. const southwest$3 = adt$5.southwest;
  4125. const northeast$3 = adt$5.northeast;
  4126. const northwest$3 = adt$5.northwest;
  4127. const south$3 = adt$5.south;
  4128. const north$3 = adt$5.north;
  4129. const east$3 = adt$5.east;
  4130. const west$3 = adt$5.west;
  4131. const getRestriction = (anchor, restriction) => {
  4132. switch (restriction) {
  4133. case 1 /* AnchorBoxBounds.LeftEdge */:
  4134. return anchor.x;
  4135. case 0 /* AnchorBoxBounds.RightEdge */:
  4136. return anchor.x + anchor.width;
  4137. case 2 /* AnchorBoxBounds.TopEdge */:
  4138. return anchor.y;
  4139. case 3 /* AnchorBoxBounds.BottomEdge */:
  4140. return anchor.y + anchor.height;
  4141. }
  4142. };
  4143. const boundsRestriction = (anchor, restrictions) => mapToObject(['left', 'right', 'top', 'bottom'], (dir) => get$h(restrictions, dir).map((restriction) => getRestriction(anchor, restriction)));
  4144. const adjustBounds = (bounds$1, restriction, bubbleOffset) => {
  4145. const applyRestriction = (dir, current) => restriction[dir].map((pos) => {
  4146. const isVerticalAxis = dir === 'top' || dir === 'bottom';
  4147. const offset = isVerticalAxis ? bubbleOffset.top : bubbleOffset.left;
  4148. const comparator = dir === 'left' || dir === 'top' ? Math.max : Math.min;
  4149. const newPos = comparator(pos, current) + offset;
  4150. // Ensure the new restricted position is within the current bounds
  4151. return isVerticalAxis ? clamp(newPos, bounds$1.y, bounds$1.bottom) : clamp(newPos, bounds$1.x, bounds$1.right);
  4152. }).getOr(current);
  4153. const adjustedLeft = applyRestriction('left', bounds$1.x);
  4154. const adjustedTop = applyRestriction('top', bounds$1.y);
  4155. const adjustedRight = applyRestriction('right', bounds$1.right);
  4156. const adjustedBottom = applyRestriction('bottom', bounds$1.bottom);
  4157. return bounds(adjustedLeft, adjustedTop, adjustedRight - adjustedLeft, adjustedBottom - adjustedTop);
  4158. };
  4159. /*
  4160. Layout for menus and inline context dialogs;
  4161. Either above or below. Never left or right.
  4162. Aligned to the left or right of the anchor as appropriate.
  4163. */
  4164. const labelPrefix$2 = 'layout';
  4165. // display element to the right, left edge against the anchor
  4166. const eastX$1 = (anchor) => anchor.x;
  4167. // element centre aligned horizontally with the anchor
  4168. const middleX$1 = (anchor, element) => anchor.x + (anchor.width / 2) - (element.width / 2);
  4169. // display element to the left, right edge against the right of the anchor
  4170. const westX$1 = (anchor, element) => anchor.x + anchor.width - element.width;
  4171. // display element above, bottom edge against the top of the anchor
  4172. const northY$2 = (anchor, element) => anchor.y - element.height;
  4173. // display element below, top edge against the bottom of the anchor
  4174. const southY$2 = (anchor) => anchor.y + anchor.height;
  4175. // display element below, top edge against the bottom of the anchor
  4176. const centreY$1 = (anchor, element) => anchor.y + (anchor.height / 2) - (element.height / 2);
  4177. const eastEdgeX$1 = (anchor) => anchor.x + anchor.width;
  4178. const westEdgeX$1 = (anchor, element) => anchor.x - element.width;
  4179. const southeast$2 = (anchor, element, bubbles) => nu$5(eastX$1(anchor), southY$2(anchor), bubbles.southeast(), southeast$3(), "southeast" /* Placement.Southeast */, boundsRestriction(anchor, { left: 1 /* AnchorBoxBounds.LeftEdge */, top: 3 /* AnchorBoxBounds.BottomEdge */ }), labelPrefix$2);
  4180. const southwest$2 = (anchor, element, bubbles) => nu$5(westX$1(anchor, element), southY$2(anchor), bubbles.southwest(), southwest$3(), "southwest" /* Placement.Southwest */, boundsRestriction(anchor, { right: 0 /* AnchorBoxBounds.RightEdge */, top: 3 /* AnchorBoxBounds.BottomEdge */ }), labelPrefix$2);
  4181. const northeast$2 = (anchor, element, bubbles) => nu$5(eastX$1(anchor), northY$2(anchor, element), bubbles.northeast(), northeast$3(), "northeast" /* Placement.Northeast */, boundsRestriction(anchor, { left: 1 /* AnchorBoxBounds.LeftEdge */, bottom: 2 /* AnchorBoxBounds.TopEdge */ }), labelPrefix$2);
  4182. const northwest$2 = (anchor, element, bubbles) => nu$5(westX$1(anchor, element), northY$2(anchor, element), bubbles.northwest(), northwest$3(), "northwest" /* Placement.Northwest */, boundsRestriction(anchor, { right: 0 /* AnchorBoxBounds.RightEdge */, bottom: 2 /* AnchorBoxBounds.TopEdge */ }), labelPrefix$2);
  4183. const north$2 = (anchor, element, bubbles) => nu$5(middleX$1(anchor, element), northY$2(anchor, element), bubbles.north(), north$3(), "north" /* Placement.North */, boundsRestriction(anchor, { bottom: 2 /* AnchorBoxBounds.TopEdge */ }), labelPrefix$2);
  4184. const south$2 = (anchor, element, bubbles) => nu$5(middleX$1(anchor, element), southY$2(anchor), bubbles.south(), south$3(), "south" /* Placement.South */, boundsRestriction(anchor, { top: 3 /* AnchorBoxBounds.BottomEdge */ }), labelPrefix$2);
  4185. const east$2 = (anchor, element, bubbles) => nu$5(eastEdgeX$1(anchor), centreY$1(anchor, element), bubbles.east(), east$3(), "east" /* Placement.East */, boundsRestriction(anchor, { left: 0 /* AnchorBoxBounds.RightEdge */ }), labelPrefix$2);
  4186. const west$2 = (anchor, element, bubbles) => nu$5(westEdgeX$1(anchor, element), centreY$1(anchor, element), bubbles.west(), west$3(), "west" /* Placement.West */, boundsRestriction(anchor, { right: 1 /* AnchorBoxBounds.LeftEdge */ }), labelPrefix$2);
  4187. const all$2 = () => [southeast$2, southwest$2, northeast$2, northwest$2, south$2, north$2, east$2, west$2];
  4188. const allRtl$1 = () => [southwest$2, southeast$2, northwest$2, northeast$2, south$2, north$2, east$2, west$2];
  4189. const aboveOrBelow = () => [northeast$2, northwest$2, southeast$2, southwest$2, north$2, south$2];
  4190. const aboveOrBelowRtl = () => [northwest$2, northeast$2, southwest$2, southeast$2, north$2, south$2];
  4191. const belowOrAbove = () => [southeast$2, southwest$2, northeast$2, northwest$2, south$2, north$2];
  4192. const belowOrAboveRtl = () => [southwest$2, southeast$2, northwest$2, northeast$2, south$2, north$2];
  4193. const placementAttribute = 'data-alloy-placement';
  4194. const setPlacement$1 = (element, placement) => {
  4195. set$9(element, placementAttribute, placement);
  4196. };
  4197. const getPlacement = (element) => getOpt(element, placementAttribute);
  4198. const reset$2 = (element) => remove$8(element, placementAttribute);
  4199. /*
  4200. Layouts for things that overlay over the anchor element/box. These are designed to mirror
  4201. the `Layout` logic.
  4202. As an example `Layout.north` will appear horizontally centered above the anchor, whereas
  4203. `LayoutInset.north` will appear horizontally centered overlapping the top of the anchor.
  4204. */
  4205. const labelPrefix$1 = 'layout-inset';
  4206. // returns left edge of anchor - used to display element to the left, left edge against the anchor
  4207. const westEdgeX = (anchor) => anchor.x;
  4208. // returns middle of anchor minus half the element width - used to horizontally centre element to the anchor
  4209. const middleX = (anchor, element) => anchor.x + (anchor.width / 2) - (element.width / 2);
  4210. // returns right edge of anchor minus element width - used to display element to the right, right edge against the anchor
  4211. const eastEdgeX = (anchor, element) => anchor.x + anchor.width - element.width;
  4212. // returns top edge - used to display element to the top, top edge against the anchor
  4213. const northY$1 = (anchor) => anchor.y;
  4214. // returns bottom edge minus element height - used to display element at the bottom, bottom edge against the anchor
  4215. const southY$1 = (anchor, element) => anchor.y + anchor.height - element.height;
  4216. // returns centre of anchor minus half the element height - used to vertically centre element to the anchor
  4217. const centreY = (anchor, element) => anchor.y + (anchor.height / 2) - (element.height / 2);
  4218. // positions element relative to the bottom right of the anchor
  4219. const southwest$1 = (anchor, element, bubbles) => nu$5(eastEdgeX(anchor, element), southY$1(anchor, element), bubbles.insetSouthwest(), northwest$3(), "southwest" /* Placement.Southwest */, boundsRestriction(anchor, { right: 0 /* AnchorBoxBounds.RightEdge */, bottom: 3 /* AnchorBoxBounds.BottomEdge */ }), labelPrefix$1);
  4220. // positions element relative to the bottom left of the anchor
  4221. const southeast$1 = (anchor, element, bubbles) => nu$5(westEdgeX(anchor), southY$1(anchor, element), bubbles.insetSoutheast(), northeast$3(), "southeast" /* Placement.Southeast */, boundsRestriction(anchor, { left: 1 /* AnchorBoxBounds.LeftEdge */, bottom: 3 /* AnchorBoxBounds.BottomEdge */ }), labelPrefix$1);
  4222. // positions element relative to the top right of the anchor
  4223. const northwest$1 = (anchor, element, bubbles) => nu$5(eastEdgeX(anchor, element), northY$1(anchor), bubbles.insetNorthwest(), southwest$3(), "northwest" /* Placement.Northwest */, boundsRestriction(anchor, { right: 0 /* AnchorBoxBounds.RightEdge */, top: 2 /* AnchorBoxBounds.TopEdge */ }), labelPrefix$1);
  4224. // positions element relative to the top left of the anchor
  4225. const northeast$1 = (anchor, element, bubbles) => nu$5(westEdgeX(anchor), northY$1(anchor), bubbles.insetNortheast(), southeast$3(), "northeast" /* Placement.Northeast */, boundsRestriction(anchor, { left: 1 /* AnchorBoxBounds.LeftEdge */, top: 2 /* AnchorBoxBounds.TopEdge */ }), labelPrefix$1);
  4226. // positions element relative to the top of the anchor, horizontally centered
  4227. const north$1 = (anchor, element, bubbles) => nu$5(middleX(anchor, element), northY$1(anchor), bubbles.insetNorth(), south$3(), "north" /* Placement.North */, boundsRestriction(anchor, { top: 2 /* AnchorBoxBounds.TopEdge */ }), labelPrefix$1);
  4228. // positions element relative to the bottom of the anchor, horizontally centered
  4229. const south$1 = (anchor, element, bubbles) => nu$5(middleX(anchor, element), southY$1(anchor, element), bubbles.insetSouth(), north$3(), "south" /* Placement.South */, boundsRestriction(anchor, { bottom: 3 /* AnchorBoxBounds.BottomEdge */ }), labelPrefix$1);
  4230. // positions element with the right edge against the anchor, vertically centered
  4231. const east$1 = (anchor, element, bubbles) => nu$5(eastEdgeX(anchor, element), centreY(anchor, element), bubbles.insetEast(), west$3(), "east" /* Placement.East */, boundsRestriction(anchor, { right: 0 /* AnchorBoxBounds.RightEdge */ }), labelPrefix$1);
  4232. // positions element with the left each against the anchor, vertically centered
  4233. const west$1 = (anchor, element, bubbles) => nu$5(westEdgeX(anchor), centreY(anchor, element), bubbles.insetWest(), east$3(), "west" /* Placement.West */, boundsRestriction(anchor, { left: 1 /* AnchorBoxBounds.LeftEdge */ }), labelPrefix$1);
  4234. const lookupPreserveLayout = (lastPlacement) => {
  4235. switch (lastPlacement) {
  4236. case "north" /* Placement.North */:
  4237. return north$1;
  4238. case "northeast" /* Placement.Northeast */:
  4239. return northeast$1;
  4240. case "northwest" /* Placement.Northwest */:
  4241. return northwest$1;
  4242. case "south" /* Placement.South */:
  4243. return south$1;
  4244. case "southeast" /* Placement.Southeast */:
  4245. return southeast$1;
  4246. case "southwest" /* Placement.Southwest */:
  4247. return southwest$1;
  4248. case "east" /* Placement.East */:
  4249. return east$1;
  4250. case "west" /* Placement.West */:
  4251. return west$1;
  4252. }
  4253. };
  4254. const preserve$1 = (anchor, element, bubbles, placee, bounds) => {
  4255. const layout = getPlacement(placee).map(lookupPreserveLayout).getOr(north$1);
  4256. return layout(anchor, element, bubbles, placee, bounds);
  4257. };
  4258. const lookupFlippedLayout = (lastPlacement) => {
  4259. switch (lastPlacement) {
  4260. case "north" /* Placement.North */:
  4261. return south$1;
  4262. case "northeast" /* Placement.Northeast */:
  4263. return southeast$1;
  4264. case "northwest" /* Placement.Northwest */:
  4265. return southwest$1;
  4266. case "south" /* Placement.South */:
  4267. return north$1;
  4268. case "southeast" /* Placement.Southeast */:
  4269. return northeast$1;
  4270. case "southwest" /* Placement.Southwest */:
  4271. return northwest$1;
  4272. case "east" /* Placement.East */:
  4273. return west$1;
  4274. case "west" /* Placement.West */:
  4275. return east$1;
  4276. }
  4277. };
  4278. const flip = (anchor, element, bubbles, placee, bounds) => {
  4279. const layout = getPlacement(placee).map(lookupFlippedLayout).getOr(north$1);
  4280. return layout(anchor, element, bubbles, placee, bounds);
  4281. };
  4282. // applies the max-height as determined by Bounder
  4283. const setMaxHeight = (element, maxHeight) => {
  4284. setMax$1(element, Math.floor(maxHeight));
  4285. };
  4286. // adds both max-height and overflow to constrain it
  4287. const anchored = constant$1((element, available) => {
  4288. setMaxHeight(element, available);
  4289. setAll(element, {
  4290. 'overflow-x': 'hidden',
  4291. 'overflow-y': 'auto'
  4292. });
  4293. });
  4294. /*
  4295. * This adds max height, but not overflow - the effect of this is that elements can grow beyond the max height,
  4296. * but if they run off the top they're pushed down.
  4297. *
  4298. * If the element expands below the screen height it will be cut off, but we were already doing that.
  4299. */
  4300. const expandable$1 = constant$1((element, available) => {
  4301. setMaxHeight(element, available);
  4302. });
  4303. // applies the max-width as determined by Bounder
  4304. const expandable = constant$1((element, available) => {
  4305. setMax(element, Math.floor(available));
  4306. });
  4307. var AttributeValue;
  4308. (function (AttributeValue) {
  4309. AttributeValue["TopToBottom"] = "toptobottom";
  4310. AttributeValue["BottomToTop"] = "bottomtotop";
  4311. })(AttributeValue || (AttributeValue = {}));
  4312. const Attribute = 'data-alloy-vertical-dir';
  4313. const isBottomToTopDir = (el) => closest$2(el, (current) => isElement$1(current) && get$g(current, 'data-alloy-vertical-dir') === AttributeValue.BottomToTop);
  4314. var HighlightOnOpen;
  4315. (function (HighlightOnOpen) {
  4316. HighlightOnOpen[HighlightOnOpen["HighlightMenuAndItem"] = 0] = "HighlightMenuAndItem";
  4317. HighlightOnOpen[HighlightOnOpen["HighlightJustMenu"] = 1] = "HighlightJustMenu";
  4318. HighlightOnOpen[HighlightOnOpen["HighlightNone"] = 2] = "HighlightNone";
  4319. })(HighlightOnOpen || (HighlightOnOpen = {}));
  4320. const NoState = {
  4321. init: () => nu$4({
  4322. readState: constant$1('No State required')
  4323. })
  4324. };
  4325. const nu$4 = (spec) => spec;
  4326. const defaultEventHandler = {
  4327. can: always,
  4328. abort: never,
  4329. run: noop
  4330. };
  4331. const nu$3 = (parts) => {
  4332. if (!hasNonNullableKey(parts, 'can') && !hasNonNullableKey(parts, 'abort') && !hasNonNullableKey(parts, 'run')) {
  4333. throw new Error('EventHandler defined by: ' + JSON.stringify(parts, null, 2) + ' does not have can, abort, or run!');
  4334. }
  4335. return {
  4336. ...defaultEventHandler,
  4337. ...parts
  4338. };
  4339. };
  4340. const all$1 = (handlers, f) => (...args) => foldl(handlers, (acc, handler) => acc && f(handler).apply(undefined, args), true);
  4341. const any = (handlers, f) => (...args) => foldl(handlers, (acc, handler) => acc || f(handler).apply(undefined, args), false);
  4342. const read$1 = (handler) => isFunction(handler) ? {
  4343. can: always,
  4344. abort: never,
  4345. run: handler
  4346. } : handler;
  4347. const fuse$1 = (handlers) => {
  4348. const can = all$1(handlers, (handler) => handler.can);
  4349. const abort = any(handlers, (handler) => handler.abort);
  4350. const run = (...args) => {
  4351. each$1(handlers, (handler) => {
  4352. // ASSUMPTION: Return value is unimportant.
  4353. handler.run.apply(undefined, args);
  4354. });
  4355. };
  4356. return {
  4357. can,
  4358. abort,
  4359. run
  4360. };
  4361. };
  4362. const emit = (component, event) => {
  4363. dispatchWith(component, component.element, event, {});
  4364. };
  4365. const emitWith = (component, event, properties) => {
  4366. dispatchWith(component, component.element, event, properties);
  4367. };
  4368. const emitExecute = (component) => {
  4369. emit(component, execute$5());
  4370. };
  4371. const dispatch = (component, target, event) => {
  4372. dispatchWith(component, target, event, {});
  4373. };
  4374. const dispatchWith = (component, target, event, properties) => {
  4375. // NOTE: The order of spreading here means that it will maintain any target that
  4376. // exists in the current properties. Because this function has been used for situations where
  4377. // properties is either an emulated SugarEvent with no target (see TouchEvent) or
  4378. // for emitting custom events that have no target, this likely hasn't been a problem.
  4379. // But until we verify that nothing is relying on this ordering, there is an alternate
  4380. // function below called retargetAndDispatchWith, which spreads in the other direction.
  4381. const data = {
  4382. target,
  4383. ...properties
  4384. };
  4385. component.getSystem().triggerEvent(event, target, data);
  4386. };
  4387. const retargetAndDispatchWith = (component, target, eventName, properties) => {
  4388. // This is essentially the same as dispatchWith, except the spreading order
  4389. // means that it clobbers anything in the nativeEvent with "target". It also
  4390. // expects what is being passed in to be a real sugar event, not just a data
  4391. // blob
  4392. const data = {
  4393. ...properties,
  4394. target
  4395. };
  4396. component.getSystem().triggerEvent(eventName, target, data);
  4397. };
  4398. const dispatchEvent = (component, target, event, simulatedEvent) => {
  4399. component.getSystem().triggerEvent(event, target, simulatedEvent.event);
  4400. };
  4401. const derive$2 = (configs) => wrapAll(configs);
  4402. // const combine = (configs...);
  4403. const abort = (name, predicate) => {
  4404. return {
  4405. key: name,
  4406. value: nu$3({
  4407. abort: predicate
  4408. })
  4409. };
  4410. };
  4411. const can = (name, predicate) => {
  4412. return {
  4413. key: name,
  4414. value: nu$3({
  4415. can: predicate
  4416. })
  4417. };
  4418. };
  4419. const preventDefault = (name) => {
  4420. return {
  4421. key: name,
  4422. value: nu$3({
  4423. run: (component, simulatedEvent) => {
  4424. simulatedEvent.event.prevent();
  4425. }
  4426. })
  4427. };
  4428. };
  4429. const run$1 = (name, handler) => {
  4430. return {
  4431. key: name,
  4432. value: nu$3({
  4433. run: handler
  4434. })
  4435. };
  4436. };
  4437. // Extra can be used when your handler needs more context, and is declared in one spot
  4438. // It's really just convenient partial application.
  4439. const runActionExtra = (name, action, extra) => {
  4440. return {
  4441. key: name,
  4442. value: nu$3({
  4443. run: (component, simulatedEvent) => {
  4444. action.apply(undefined, [component, simulatedEvent].concat(extra));
  4445. }
  4446. })
  4447. };
  4448. };
  4449. const runOnName = (name) => {
  4450. return (handler) => run$1(name, handler);
  4451. };
  4452. const runOnSourceName = (name) => {
  4453. return (handler) => ({
  4454. key: name,
  4455. value: nu$3({
  4456. run: (component, simulatedEvent) => {
  4457. if (isSource(component, simulatedEvent)) {
  4458. handler(component, simulatedEvent);
  4459. }
  4460. }
  4461. })
  4462. });
  4463. };
  4464. const redirectToUid = (name, uid) => {
  4465. return run$1(name, (component, simulatedEvent) => {
  4466. component.getSystem().getByUid(uid).each((redirectee) => {
  4467. dispatchEvent(redirectee, redirectee.element, name, simulatedEvent);
  4468. });
  4469. });
  4470. };
  4471. const redirectToPart = (name, detail, partName) => {
  4472. const uid = detail.partUids[partName];
  4473. return redirectToUid(name, uid);
  4474. };
  4475. const runWithTarget = (name, f) => {
  4476. return run$1(name, (component, simulatedEvent) => {
  4477. const ev = simulatedEvent.event;
  4478. const target = component.getSystem().getByDom(ev.target).getOrThunk(
  4479. // If we don't find an alloy component for the target, I guess we go up the tree
  4480. // until we find an alloy component? Performance concern?
  4481. // TODO: Write tests for this.
  4482. () => {
  4483. const closest$1 = closest(ev.target, (el) => component.getSystem().getByDom(el).toOptional(), never);
  4484. // If we still found nothing ... fire on component itself;
  4485. return closest$1.getOr(component);
  4486. });
  4487. f(component, target, simulatedEvent);
  4488. });
  4489. };
  4490. const cutter = (name) => {
  4491. return run$1(name, (component, simulatedEvent) => {
  4492. simulatedEvent.cut();
  4493. });
  4494. };
  4495. const stopper = (name) => {
  4496. return run$1(name, (component, simulatedEvent) => {
  4497. simulatedEvent.stop();
  4498. });
  4499. };
  4500. const runOnSource = (name, f) => {
  4501. return runOnSourceName(name)(f);
  4502. };
  4503. const runOnAttached = runOnSourceName(attachedToDom());
  4504. const runOnDetached = runOnSourceName(detachedFromDom());
  4505. const runOnInit = runOnSourceName(systemInit());
  4506. const runOnExecute$1 = runOnName(execute$5());
  4507. // Maybe we'll need to allow add/remove
  4508. const nu$2 = (s) => ({
  4509. classes: isUndefined(s.classes) ? [] : s.classes,
  4510. attributes: isUndefined(s.attributes) ? {} : s.attributes,
  4511. styles: isUndefined(s.styles) ? {} : s.styles
  4512. });
  4513. const merge = (defnA, mod) => ({
  4514. ...defnA,
  4515. attributes: { ...defnA.attributes, ...mod.attributes },
  4516. styles: { ...defnA.styles, ...mod.styles },
  4517. classes: defnA.classes.concat(mod.classes)
  4518. });
  4519. const executeEvent = (bConfig, bState, executor) => runOnExecute$1((component) => {
  4520. executor(component, bConfig, bState);
  4521. });
  4522. const loadEvent = (bConfig, bState, f) => runOnInit((component, _simulatedEvent) => {
  4523. f(component, bConfig, bState);
  4524. });
  4525. const create$4 = (schema, name, active, apis, extra, state) => {
  4526. const configSchema = objOfOnly(schema);
  4527. const schemaSchema = optionObjOf(name, [
  4528. optionObjOfOnly('config', schema)
  4529. ]);
  4530. return doCreate(configSchema, schemaSchema, name, active, apis, extra, state);
  4531. };
  4532. const createModes$1 = (modes, name, active, apis, extra, state) => {
  4533. const configSchema = modes;
  4534. const schemaSchema = optionObjOf(name, [
  4535. optionOf('config', modes)
  4536. ]);
  4537. return doCreate(configSchema, schemaSchema, name, active, apis, extra, state);
  4538. };
  4539. const wrapApi = (bName, apiFunction, apiName) => {
  4540. const f = (component, ...rest) => {
  4541. const args = [component].concat(rest);
  4542. return component.config({
  4543. name: constant$1(bName)
  4544. }).fold(() => {
  4545. throw new Error('We could not find any behaviour configuration for: ' + bName + '. Using API: ' + apiName);
  4546. }, (info) => {
  4547. const rest = Array.prototype.slice.call(args, 1);
  4548. return apiFunction.apply(undefined, [component, info.config, info.state].concat(rest));
  4549. });
  4550. };
  4551. return markAsBehaviourApi(f, apiName, apiFunction);
  4552. };
  4553. // I think the "revoke" idea is fragile at best.
  4554. const revokeBehaviour = (name) => ({
  4555. key: name,
  4556. value: undefined
  4557. });
  4558. const doCreate = (configSchema, schemaSchema, name, active, apis, extra, state) => {
  4559. const getConfig = (info) => hasNonNullableKey(info, name) ? info[name]() : Optional.none();
  4560. const wrappedApis = map$1(apis, (apiF, apiName) => wrapApi(name, apiF, apiName));
  4561. const wrappedExtra = map$1(extra, (extraF, extraName) => markAsExtraApi(extraF, extraName));
  4562. const me = {
  4563. ...wrappedExtra,
  4564. ...wrappedApis,
  4565. revoke: curry(revokeBehaviour, name),
  4566. config: (spec) => {
  4567. const prepared = asRawOrDie$1(name + '-config', configSchema, spec);
  4568. return {
  4569. key: name,
  4570. value: {
  4571. config: prepared,
  4572. me,
  4573. configAsRaw: cached(() => asRawOrDie$1(name + '-config', configSchema, spec)),
  4574. initialConfig: spec,
  4575. state
  4576. }
  4577. };
  4578. },
  4579. schema: constant$1(schemaSchema),
  4580. exhibit: (info, base) => {
  4581. return lift2(getConfig(info), get$h(active, 'exhibit'), (behaviourInfo, exhibitor) => {
  4582. return exhibitor(base, behaviourInfo.config, behaviourInfo.state);
  4583. }).getOrThunk(() => nu$2({}));
  4584. },
  4585. name: constant$1(name),
  4586. handlers: (info) => {
  4587. return getConfig(info).map((behaviourInfo) => {
  4588. const getEvents = get$h(active, 'events').getOr(() => ({}));
  4589. return getEvents(behaviourInfo.config, behaviourInfo.state);
  4590. }).getOr({});
  4591. }
  4592. };
  4593. return me;
  4594. };
  4595. const derive$1 = (capabilities) => wrapAll(capabilities);
  4596. const simpleSchema = objOfOnly([
  4597. required$1('fields'),
  4598. required$1('name'),
  4599. defaulted('active', {}),
  4600. defaulted('apis', {}),
  4601. defaulted('state', NoState),
  4602. defaulted('extra', {})
  4603. ]);
  4604. const create$3 = (data) => {
  4605. const value = asRawOrDie$1('Creating behaviour: ' + data.name, simpleSchema, data);
  4606. return create$4(value.fields, value.name, value.active, value.apis, value.extra, value.state);
  4607. };
  4608. const modeSchema = objOfOnly([
  4609. required$1('branchKey'),
  4610. required$1('branches'),
  4611. required$1('name'),
  4612. defaulted('active', {}),
  4613. defaulted('apis', {}),
  4614. defaulted('state', NoState),
  4615. defaulted('extra', {})
  4616. ]);
  4617. const createModes = (data) => {
  4618. const value = asRawOrDie$1('Creating behaviour: ' + data.name, modeSchema, data);
  4619. return createModes$1(choose$1(value.branchKey, value.branches), value.name, value.active, value.apis, value.extra, value.state);
  4620. };
  4621. const revoke = constant$1(undefined);
  4622. // AlloyEventKeyAndHandler type argument needs to be any here to satisfy an array of handlers
  4623. // where each item can be any subtype of EventFormat we can't use <T extends EventFormat> since
  4624. // then each item would have to be the same type
  4625. const events$i = (name, eventHandlers) => {
  4626. const events = derive$2(eventHandlers);
  4627. return create$3({
  4628. fields: [
  4629. required$1('enabled')
  4630. ],
  4631. name,
  4632. active: {
  4633. events: constant$1(events)
  4634. }
  4635. });
  4636. };
  4637. const config = (name, eventHandlers) => {
  4638. const me = events$i(name, eventHandlers);
  4639. return {
  4640. key: name,
  4641. value: {
  4642. config: {},
  4643. me,
  4644. configAsRaw: constant$1({}),
  4645. initialConfig: {},
  4646. state: NoState
  4647. }
  4648. };
  4649. };
  4650. const SetupBehaviourCellState = (initialState) => {
  4651. const init = () => {
  4652. const cell = Cell(initialState);
  4653. const get = () => cell.get();
  4654. const set = (newState) => cell.set(newState);
  4655. const clear = () => cell.set(initialState);
  4656. const readState = () => cell.get();
  4657. return {
  4658. get,
  4659. set,
  4660. clear,
  4661. readState
  4662. };
  4663. };
  4664. return {
  4665. init
  4666. };
  4667. };
  4668. const focus$2 = (component, focusConfig) => {
  4669. if (!focusConfig.ignore) {
  4670. focus$4(component.element);
  4671. focusConfig.onFocus(component);
  4672. }
  4673. };
  4674. const blur = (component, focusConfig) => {
  4675. if (!focusConfig.ignore) {
  4676. blur$1(component.element);
  4677. }
  4678. };
  4679. const isFocused = (component) => hasFocus(component.element);
  4680. var FocusApis = /*#__PURE__*/Object.freeze({
  4681. __proto__: null,
  4682. focus: focus$2,
  4683. blur: blur,
  4684. isFocused: isFocused
  4685. });
  4686. // TODO: DomModification types
  4687. const exhibit$6 = (base, focusConfig) => {
  4688. const mod = focusConfig.ignore ? {} : {
  4689. attributes: {
  4690. tabindex: '-1'
  4691. }
  4692. };
  4693. return nu$2(mod);
  4694. };
  4695. const events$h = (focusConfig) => derive$2([
  4696. run$1(focus$3(), (component, simulatedEvent) => {
  4697. focus$2(component, focusConfig);
  4698. simulatedEvent.stop();
  4699. })
  4700. ].concat(focusConfig.stopMousedown ? [
  4701. run$1(mousedown(), (_, simulatedEvent) => {
  4702. // This setting is often used in tandem with ignoreFocus. Basically, if you
  4703. // don't prevent default on a menu that has fake focus, then it can transfer
  4704. // focus to the outer body when they click on it, which can break things
  4705. // which dismiss on blur (e.g. typeahead)
  4706. simulatedEvent.event.prevent();
  4707. })
  4708. ] : []));
  4709. var ActiveFocus = /*#__PURE__*/Object.freeze({
  4710. __proto__: null,
  4711. exhibit: exhibit$6,
  4712. events: events$h
  4713. });
  4714. var FocusSchema = [
  4715. // TODO: Work out when we want to call this. Only when it is has changed?
  4716. onHandler('onFocus'),
  4717. defaulted('stopMousedown', false),
  4718. defaulted('ignore', false)
  4719. ];
  4720. const Focusing = create$3({
  4721. fields: FocusSchema,
  4722. name: 'focusing',
  4723. active: ActiveFocus,
  4724. apis: FocusApis
  4725. // Consider adding isFocused an an extra
  4726. });
  4727. const BACKSPACE = [8];
  4728. const TAB = [9];
  4729. const ENTER = [13];
  4730. const ESCAPE = [27];
  4731. const SPACE = [32];
  4732. const LEFT = [37];
  4733. const UP = [38];
  4734. const RIGHT = [39];
  4735. const DOWN = [40];
  4736. const closeTooltips = constant$1('tooltipping.close.all');
  4737. const dismissPopups = constant$1('dismiss.popups');
  4738. const repositionPopups = constant$1('reposition.popups');
  4739. const mouseReleased = constant$1('mouse.released');
  4740. const cyclePrev = (values, index, predicate) => {
  4741. const before = reverse(values.slice(0, index));
  4742. const after = reverse(values.slice(index + 1));
  4743. return find$5(before.concat(after), predicate);
  4744. };
  4745. const tryPrev = (values, index, predicate) => {
  4746. const before = reverse(values.slice(0, index));
  4747. return find$5(before, predicate);
  4748. };
  4749. const cycleNext = (values, index, predicate) => {
  4750. const before = values.slice(0, index);
  4751. const after = values.slice(index + 1);
  4752. return find$5(after.concat(before), predicate);
  4753. };
  4754. const tryNext = (values, index, predicate) => {
  4755. const after = values.slice(index + 1);
  4756. return find$5(after, predicate);
  4757. };
  4758. const inSet = (keys) => (event) => {
  4759. const raw = event.raw;
  4760. return contains$2(keys, raw.which);
  4761. };
  4762. const and = (preds) => (event) => forall(preds, (pred) => pred(event));
  4763. const isShift$1 = (event) => {
  4764. const raw = event.raw;
  4765. return raw.shiftKey === true;
  4766. };
  4767. const isControl = (event) => {
  4768. const raw = event.raw;
  4769. return raw.ctrlKey === true;
  4770. };
  4771. const isNotShift = not(isShift$1);
  4772. const rule = (matches, action) => ({
  4773. matches,
  4774. classification: action
  4775. });
  4776. const choose = (transitions, event) => {
  4777. const transition = find$5(transitions, (t) => t.matches(event));
  4778. return transition.map((t) => t.classification);
  4779. };
  4780. // THIS IS NOT API YET
  4781. const dehighlightAllExcept = (component, hConfig, hState, skip) => {
  4782. const highlighted = descendants(component.element, '.' + hConfig.highlightClass);
  4783. each$1(highlighted, (h) => {
  4784. // We don't want to dehighlight anything that should be skipped.
  4785. // Generally, this is because we are about to highlight that thing.
  4786. const shouldSkip = exists(skip, (skipComp) => eq(skipComp.element, h));
  4787. if (!shouldSkip) {
  4788. remove$3(h, hConfig.highlightClass);
  4789. component.getSystem().getByDom(h).each((target) => {
  4790. hConfig.onDehighlight(component, target);
  4791. emit(target, dehighlight$1());
  4792. });
  4793. }
  4794. });
  4795. };
  4796. const dehighlightAll = (component, hConfig, hState) => dehighlightAllExcept(component, hConfig, hState, []);
  4797. const dehighlight = (component, hConfig, hState, target) => {
  4798. // Only act if it was highlighted.
  4799. if (isHighlighted(component, hConfig, hState, target)) {
  4800. remove$3(target.element, hConfig.highlightClass);
  4801. hConfig.onDehighlight(component, target);
  4802. emit(target, dehighlight$1());
  4803. }
  4804. };
  4805. const highlight = (component, hConfig, hState, target) => {
  4806. // If asked to highlight something, dehighlight everything else first except
  4807. // for the new thing we are going to highlight. It's a rare case, but we don't
  4808. // want to get an onDehighlight, onHighlight for the same item on a highlight call.
  4809. // We also don't want to call onHighlight if it was already highlighted.
  4810. //
  4811. // Note, that there is an important distinction here: highlight is NOT a no-op
  4812. // if target is already highlighted, because it will still dehighlight everything else.
  4813. // However, it won't fire any onHighlight or onDehighlight handlers for the already
  4814. // highlighted item. I'm not sure if this is behaviour we need to maintain, but it is now
  4815. // tested. A simpler approach might just be to not do anything if it's already highlighted,
  4816. // but that could leave us in an inconsistent state, where multiple items have highlights
  4817. // even after a highlight call. This way, highlight validates the highlights in the
  4818. // component, and ensures there is only one thing highlighted.
  4819. dehighlightAllExcept(component, hConfig, hState, [target]);
  4820. if (!isHighlighted(component, hConfig, hState, target)) {
  4821. add$2(target.element, hConfig.highlightClass);
  4822. hConfig.onHighlight(component, target);
  4823. emit(target, highlight$1());
  4824. }
  4825. };
  4826. const highlightFirst = (component, hConfig, hState) => {
  4827. getFirst(component, hConfig).each((firstComp) => {
  4828. highlight(component, hConfig, hState, firstComp);
  4829. });
  4830. };
  4831. const highlightLast = (component, hConfig, hState) => {
  4832. getLast(component, hConfig).each((lastComp) => {
  4833. highlight(component, hConfig, hState, lastComp);
  4834. });
  4835. };
  4836. const highlightAt = (component, hConfig, hState, index) => {
  4837. getByIndex(component, hConfig, hState, index).fold((err) => {
  4838. throw err;
  4839. }, (firstComp) => {
  4840. highlight(component, hConfig, hState, firstComp);
  4841. });
  4842. };
  4843. const highlightBy = (component, hConfig, hState, predicate) => {
  4844. const candidates = getCandidates(component, hConfig);
  4845. const targetComp = find$5(candidates, predicate);
  4846. targetComp.each((c) => {
  4847. highlight(component, hConfig, hState, c);
  4848. });
  4849. };
  4850. const isHighlighted = (component, hConfig, hState, queryTarget) => has(queryTarget.element, hConfig.highlightClass);
  4851. const getHighlighted = (component, hConfig, _hState) => descendant(component.element, '.' + hConfig.highlightClass).bind((e) => component.getSystem().getByDom(e).toOptional());
  4852. const getByIndex = (component, hConfig, hState, index) => {
  4853. const items = descendants(component.element, '.' + hConfig.itemClass);
  4854. return Optional.from(items[index]).fold(() => Result.error(new Error('No element found with index ' + index)), component.getSystem().getByDom);
  4855. };
  4856. const getFirst = (component, hConfig, _hState) => descendant(component.element, '.' + hConfig.itemClass).bind((e) => component.getSystem().getByDom(e).toOptional());
  4857. const getLast = (component, hConfig, _hState) => {
  4858. const items = descendants(component.element, '.' + hConfig.itemClass);
  4859. const last = items.length > 0 ? Optional.some(items[items.length - 1]) : Optional.none();
  4860. return last.bind((c) => component.getSystem().getByDom(c).toOptional());
  4861. };
  4862. const getDelta$2 = (component, hConfig, hState, delta) => {
  4863. const items = descendants(component.element, '.' + hConfig.itemClass);
  4864. const current = findIndex$1(items, (item) => has(item, hConfig.highlightClass));
  4865. return current.bind((selected) => {
  4866. const dest = cycleBy(selected, delta, 0, items.length - 1);
  4867. return component.getSystem().getByDom(items[dest]).toOptional();
  4868. });
  4869. };
  4870. const getPrevious = (component, hConfig, hState) => getDelta$2(component, hConfig, hState, -1);
  4871. const getNext = (component, hConfig, hState) => getDelta$2(component, hConfig, hState, +1);
  4872. const getCandidates = (component, hConfig, _hState) => {
  4873. const items = descendants(component.element, '.' + hConfig.itemClass);
  4874. return cat(map$2(items, (i) => component.getSystem().getByDom(i).toOptional()));
  4875. };
  4876. var HighlightApis = /*#__PURE__*/Object.freeze({
  4877. __proto__: null,
  4878. dehighlightAll: dehighlightAll,
  4879. dehighlight: dehighlight,
  4880. highlight: highlight,
  4881. highlightFirst: highlightFirst,
  4882. highlightLast: highlightLast,
  4883. highlightAt: highlightAt,
  4884. highlightBy: highlightBy,
  4885. isHighlighted: isHighlighted,
  4886. getHighlighted: getHighlighted,
  4887. getFirst: getFirst,
  4888. getLast: getLast,
  4889. getPrevious: getPrevious,
  4890. getNext: getNext,
  4891. getCandidates: getCandidates
  4892. });
  4893. var HighlightSchema = [
  4894. required$1('highlightClass'),
  4895. required$1('itemClass'),
  4896. onHandler('onHighlight'),
  4897. onHandler('onDehighlight')
  4898. ];
  4899. const Highlighting = create$3({
  4900. fields: HighlightSchema,
  4901. name: 'highlighting',
  4902. apis: HighlightApis
  4903. });
  4904. const reportFocusShifting = (component, prevFocus, newFocus) => {
  4905. const noChange = prevFocus.exists((p) => newFocus.exists((n) => eq(n, p)));
  4906. if (!noChange) {
  4907. emitWith(component, focusShifted(), {
  4908. prevFocus,
  4909. newFocus
  4910. });
  4911. }
  4912. };
  4913. const dom$2 = () => {
  4914. const get = (component) => search(component.element);
  4915. const set = (component, focusee) => {
  4916. const prevFocus = get(component);
  4917. component.getSystem().triggerFocus(focusee, component.element);
  4918. const newFocus = get(component);
  4919. reportFocusShifting(component, prevFocus, newFocus);
  4920. };
  4921. return {
  4922. get,
  4923. set
  4924. };
  4925. };
  4926. const highlights = () => {
  4927. const get = (component) => Highlighting.getHighlighted(component).map((item) => item.element);
  4928. const set = (component, element) => {
  4929. const prevFocus = get(component);
  4930. component.getSystem().getByDom(element).fold(noop, (item) => {
  4931. Highlighting.highlight(component, item);
  4932. });
  4933. const newFocus = get(component);
  4934. reportFocusShifting(component, prevFocus, newFocus);
  4935. };
  4936. return {
  4937. get,
  4938. set
  4939. };
  4940. };
  4941. const typical = (infoSchema, stateInit, getKeydownRules, getKeyupRules, optFocusIn) => {
  4942. const schema = () => infoSchema.concat([
  4943. defaulted('focusManager', dom$2()),
  4944. defaultedOf('focusInside', 'onFocus', valueOf((val) => contains$2(['onFocus', 'onEnterOrSpace', 'onApi'], val) ? Result.value(val) : Result.error('Invalid value for focusInside'))),
  4945. output$1('handler', me),
  4946. output$1('state', stateInit),
  4947. output$1('sendFocusIn', optFocusIn)
  4948. ]);
  4949. const processKey = (component, simulatedEvent, getRules, keyingConfig, keyingState) => {
  4950. const rules = getRules(component, simulatedEvent, keyingConfig, keyingState);
  4951. return choose(rules, simulatedEvent.event).bind((rule) => rule(component, simulatedEvent, keyingConfig, keyingState));
  4952. };
  4953. const toEvents = (keyingConfig, keyingState) => {
  4954. const onFocusHandler = keyingConfig.focusInside !== FocusInsideModes.OnFocusMode
  4955. ? Optional.none()
  4956. : optFocusIn(keyingConfig).map((focusIn) => run$1(focus$3(), (component, simulatedEvent) => {
  4957. focusIn(component, keyingConfig, keyingState);
  4958. simulatedEvent.stop();
  4959. }));
  4960. // On enter or space on root element, if using EnterOrSpace focus mode, fire a focusIn on the component
  4961. const tryGoInsideComponent = (component, simulatedEvent) => {
  4962. const isEnterOrSpace = inSet(SPACE.concat(ENTER))(simulatedEvent.event);
  4963. if (keyingConfig.focusInside === FocusInsideModes.OnEnterOrSpaceMode && isEnterOrSpace && isSource(component, simulatedEvent)) {
  4964. optFocusIn(keyingConfig).each((focusIn) => {
  4965. focusIn(component, keyingConfig, keyingState);
  4966. simulatedEvent.stop();
  4967. });
  4968. }
  4969. };
  4970. const keyboardEvents = [
  4971. run$1(keydown(), (component, simulatedEvent) => {
  4972. processKey(component, simulatedEvent, getKeydownRules, keyingConfig, keyingState).fold(() => {
  4973. // Key wasn't handled ... so see if we should enter into the component (focusIn)
  4974. tryGoInsideComponent(component, simulatedEvent);
  4975. }, (_) => {
  4976. simulatedEvent.stop();
  4977. });
  4978. }),
  4979. run$1(keyup(), (component, simulatedEvent) => {
  4980. processKey(component, simulatedEvent, getKeyupRules, keyingConfig, keyingState).each((_) => {
  4981. simulatedEvent.stop();
  4982. });
  4983. })
  4984. ];
  4985. return derive$2(onFocusHandler.toArray().concat(keyboardEvents));
  4986. };
  4987. const me = {
  4988. schema,
  4989. processKey,
  4990. toEvents
  4991. };
  4992. return me;
  4993. };
  4994. const create$2 = (cyclicField) => {
  4995. const schema = [
  4996. option$3('onEscape'),
  4997. option$3('onEnter'),
  4998. defaulted('selector', '[data-alloy-tabstop="true"]:not(:disabled)'),
  4999. defaulted('firstTabstop', 0),
  5000. defaulted('useTabstopAt', always),
  5001. // Maybe later we should just expose isVisible
  5002. option$3('visibilitySelector')
  5003. ].concat([
  5004. cyclicField
  5005. ]);
  5006. // TODO: Test this
  5007. const isVisible = (tabbingConfig, element) => {
  5008. const target = tabbingConfig.visibilitySelector
  5009. .bind((sel) => closest$3(element, sel))
  5010. .getOr(element);
  5011. // NOTE: We can't use Visibility.isVisible, because the toolbar has width when it has closed, just not height.
  5012. return get$d(target) > 0;
  5013. };
  5014. const findInitial = (component, tabbingConfig) => {
  5015. const tabstops = descendants(component.element, tabbingConfig.selector);
  5016. const visibles = filter$2(tabstops, (elem) => isVisible(tabbingConfig, elem));
  5017. return Optional.from(visibles[tabbingConfig.firstTabstop]);
  5018. };
  5019. const findCurrent = (component, tabbingConfig) => tabbingConfig.focusManager.get(component)
  5020. .bind((elem) => closest$3(elem, tabbingConfig.selector));
  5021. const isTabstop = (tabbingConfig, element) => isVisible(tabbingConfig, element) && tabbingConfig.useTabstopAt(element);
  5022. // Fire an alloy focus on the first visible element that matches the selector
  5023. const focusIn = (component, tabbingConfig, _tabbingState) => {
  5024. findInitial(component, tabbingConfig).each((target) => {
  5025. tabbingConfig.focusManager.set(component, target);
  5026. });
  5027. };
  5028. const goFromTabstop = (component, tabstops, stopIndex, tabbingConfig, cycle) => cycle(tabstops, stopIndex, (elem) => isTabstop(tabbingConfig, elem))
  5029. .fold(
  5030. // Even if there is only one, still capture the event if cycling
  5031. () => tabbingConfig.cyclic ? Optional.some(true) : Optional.none(), (target) => {
  5032. tabbingConfig.focusManager.set(component, target);
  5033. // Kill the event
  5034. return Optional.some(true);
  5035. });
  5036. const go = (component, _simulatedEvent, tabbingConfig, cycle) => {
  5037. // 1. Find our current tabstop
  5038. // 2. Find the index of that tabstop
  5039. // 3. Cycle the tabstop
  5040. // 4. Fire alloy focus on the resultant tabstop
  5041. const tabstops = filter$2(descendants(component.element, tabbingConfig.selector), (element) => isVisible(tabbingConfig, element));
  5042. return findCurrent(component, tabbingConfig).bind((tabstop) => {
  5043. // focused component
  5044. const optStopIndex = findIndex$1(tabstops, curry(eq, tabstop));
  5045. return optStopIndex.bind((stopIndex) => goFromTabstop(component, tabstops, stopIndex, tabbingConfig, cycle));
  5046. });
  5047. };
  5048. const goBackwards = (component, simulatedEvent, tabbingConfig) => {
  5049. const navigate = tabbingConfig.cyclic ? cyclePrev : tryPrev;
  5050. return go(component, simulatedEvent, tabbingConfig, navigate);
  5051. };
  5052. const goForwards = (component, simulatedEvent, tabbingConfig) => {
  5053. const navigate = tabbingConfig.cyclic ? cycleNext : tryNext;
  5054. return go(component, simulatedEvent, tabbingConfig, navigate);
  5055. };
  5056. const isFirstChild = (elem) => parentNode(elem).bind(firstChild).exists((child) => eq(child, elem));
  5057. const goFromPseudoTabstop = (component, simulatedEvent, tabbingConfig) => findCurrent(component, tabbingConfig).filter((elem) => !tabbingConfig.useTabstopAt(elem))
  5058. .bind((elem) => (isFirstChild(elem) ? goBackwards : goForwards)(component, simulatedEvent, tabbingConfig));
  5059. const execute = (component, simulatedEvent, tabbingConfig) => tabbingConfig.onEnter.bind((f) => f(component, simulatedEvent));
  5060. const exit = (component, simulatedEvent, tabbingConfig) => {
  5061. component.getSystem().broadcastOn([closeTooltips()], {
  5062. closedTooltip: () => {
  5063. simulatedEvent.stop();
  5064. }
  5065. });
  5066. if (!simulatedEvent.isStopped()) {
  5067. return tabbingConfig.onEscape.bind((f) => f(component, simulatedEvent));
  5068. }
  5069. else {
  5070. return Optional.none();
  5071. }
  5072. };
  5073. const getKeydownRules = constant$1([
  5074. rule(and([isShift$1, inSet(TAB)]), goBackwards),
  5075. rule(inSet(TAB), goForwards),
  5076. rule(and([isNotShift, inSet(ENTER)]), execute)
  5077. ]);
  5078. const getKeyupRules = constant$1([
  5079. rule(inSet(ESCAPE), exit),
  5080. rule(inSet(TAB), goFromPseudoTabstop),
  5081. ]);
  5082. return typical(schema, NoState.init, getKeydownRules, getKeyupRules, () => Optional.some(focusIn));
  5083. };
  5084. var AcyclicType = create$2(customField('cyclic', never));
  5085. var CyclicType = create$2(customField('cyclic', always));
  5086. const inside = (target) => ((isTag('input')(target) && get$g(target, 'type') !== 'radio') ||
  5087. isTag('textarea')(target));
  5088. const doDefaultExecute = (component, _simulatedEvent, focused) => {
  5089. // Note, we use to pass through simulatedEvent here and make target: component. This simplification
  5090. // may be a problem
  5091. dispatch(component, focused, execute$5());
  5092. return Optional.some(true);
  5093. };
  5094. const defaultExecute = (component, simulatedEvent, focused) => {
  5095. const isComplex = inside(focused) && inSet(SPACE)(simulatedEvent.event);
  5096. return isComplex ? Optional.none() : doDefaultExecute(component, simulatedEvent, focused);
  5097. };
  5098. // On Firefox, pressing space fires a click event if the element maintains focus and fires a keyup. This
  5099. // stops the keyup, which should stop the click. We might want to make this only work for buttons and Firefox etc,
  5100. // but at this stage it's cleaner to just always do it. It makes sense that Keying that handles space should handle
  5101. // keyup also. This does make the name confusing, though.
  5102. const stopEventForFirefox = (_component, _simulatedEvent) => Optional.some(true);
  5103. const schema$y = [
  5104. defaulted('execute', defaultExecute),
  5105. defaulted('useSpace', false),
  5106. defaulted('useEnter', true),
  5107. defaulted('useControlEnter', false),
  5108. defaulted('useDown', false)
  5109. ];
  5110. const execute$4 = (component, simulatedEvent, executeConfig) => executeConfig.execute(component, simulatedEvent, component.element);
  5111. const getKeydownRules$5 = (component, _simulatedEvent, executeConfig, _executeState) => {
  5112. const spaceExec = executeConfig.useSpace && !inside(component.element) ? SPACE : [];
  5113. const enterExec = executeConfig.useEnter ? ENTER : [];
  5114. const downExec = executeConfig.useDown ? DOWN : [];
  5115. const execKeys = spaceExec.concat(enterExec).concat(downExec);
  5116. return [
  5117. rule(inSet(execKeys), execute$4)
  5118. ].concat(executeConfig.useControlEnter ? [
  5119. rule(and([isControl, inSet(ENTER)]), execute$4)
  5120. ] : []);
  5121. };
  5122. const getKeyupRules$5 = (component, _simulatedEvent, executeConfig, _executeState) => executeConfig.useSpace && !inside(component.element) ?
  5123. [rule(inSet(SPACE), stopEventForFirefox)] :
  5124. [];
  5125. var ExecutionType = typical(schema$y, NoState.init, getKeydownRules$5, getKeyupRules$5, () => Optional.none());
  5126. const flatgrid$1 = () => {
  5127. const dimensions = value$2();
  5128. const setGridSize = (numRows, numColumns) => {
  5129. dimensions.set({ numRows, numColumns });
  5130. };
  5131. const getNumRows = () => dimensions.get().map((d) => d.numRows);
  5132. const getNumColumns = () => dimensions.get().map((d) => d.numColumns);
  5133. return nu$4({
  5134. readState: () => dimensions.get().map((d) => ({
  5135. numRows: String(d.numRows),
  5136. numColumns: String(d.numColumns)
  5137. })).getOr({
  5138. numRows: '?',
  5139. numColumns: '?'
  5140. }),
  5141. setGridSize,
  5142. getNumRows,
  5143. getNumColumns
  5144. });
  5145. };
  5146. const init$g = (spec) => spec.state(spec);
  5147. var KeyingState = /*#__PURE__*/Object.freeze({
  5148. __proto__: null,
  5149. flatgrid: flatgrid$1,
  5150. init: init$g
  5151. });
  5152. // Looks up direction (considering LTR and RTL), finds the focused element,
  5153. // and tries to move. If it succeeds, triggers focus and kills the event.
  5154. const useH = (movement) => (component, simulatedEvent, config, state) => {
  5155. const move = movement(component.element);
  5156. return use(move, component, simulatedEvent, config, state);
  5157. };
  5158. const west = (moveLeft, moveRight) => {
  5159. const movement = onDirection(moveLeft, moveRight);
  5160. return useH(movement);
  5161. };
  5162. const east = (moveLeft, moveRight) => {
  5163. const movement = onDirection(moveRight, moveLeft);
  5164. return useH(movement);
  5165. };
  5166. const useV = (move) => (component, simulatedEvent, config, state) => use(move, component, simulatedEvent, config, state);
  5167. const use = (move, component, simulatedEvent, config, state) => {
  5168. const outcome = config.focusManager.get(component).bind((focused) => move(component.element, focused, config, state));
  5169. return outcome.map((newFocus) => {
  5170. config.focusManager.set(component, newFocus);
  5171. return true;
  5172. });
  5173. };
  5174. const north = useV;
  5175. const south = useV;
  5176. const move$1 = useV;
  5177. const locate = (candidates, predicate) => findIndex$1(candidates, predicate).map((index) => ({
  5178. index,
  5179. candidates
  5180. }));
  5181. const locateVisible = (container, current, selector) => {
  5182. const predicate = (x) => eq(x, current);
  5183. const candidates = descendants(container, selector);
  5184. const visible = filter$2(candidates, isVisible);
  5185. return locate(visible, predicate);
  5186. };
  5187. const findIndex = (elements, target) => findIndex$1(elements, (elem) => eq(target, elem));
  5188. const withGrid = (values, index, numCols, f) => {
  5189. const oldRow = Math.floor(index / numCols);
  5190. const oldColumn = index % numCols;
  5191. return f(oldRow, oldColumn).bind((address) => {
  5192. const newIndex = address.row * numCols + address.column;
  5193. return newIndex >= 0 && newIndex < values.length ? Optional.some(values[newIndex]) : Optional.none();
  5194. });
  5195. };
  5196. const cycleHorizontal$1 = (values, index, numRows, numCols, delta) => withGrid(values, index, numCols, (oldRow, oldColumn) => {
  5197. const onLastRow = oldRow === numRows - 1;
  5198. const colsInRow = onLastRow ? values.length - (oldRow * numCols) : numCols;
  5199. const newColumn = cycleBy(oldColumn, delta, 0, colsInRow - 1);
  5200. return Optional.some({
  5201. row: oldRow,
  5202. column: newColumn
  5203. });
  5204. });
  5205. const cycleVertical$1 = (values, index, numRows, numCols, delta) => withGrid(values, index, numCols, (oldRow, oldColumn) => {
  5206. const newRow = cycleBy(oldRow, delta, 0, numRows - 1);
  5207. const onLastRow = newRow === numRows - 1;
  5208. const colsInRow = onLastRow ? values.length - (newRow * numCols) : numCols;
  5209. const newCol = clamp(oldColumn, 0, colsInRow - 1);
  5210. return Optional.some({
  5211. row: newRow,
  5212. column: newCol
  5213. });
  5214. });
  5215. const cycleRight$1 = (values, index, numRows, numCols) => cycleHorizontal$1(values, index, numRows, numCols, +1);
  5216. const cycleLeft$1 = (values, index, numRows, numCols) => cycleHorizontal$1(values, index, numRows, numCols, -1);
  5217. const cycleUp$1 = (values, index, numRows, numCols) => cycleVertical$1(values, index, numRows, numCols, -1);
  5218. const cycleDown$1 = (values, index, numRows, numCols) => cycleVertical$1(values, index, numRows, numCols, +1);
  5219. const schema$x = [
  5220. required$1('selector'),
  5221. defaulted('execute', defaultExecute),
  5222. onKeyboardHandler('onEscape'),
  5223. defaulted('captureTab', false),
  5224. initSize()
  5225. ];
  5226. const focusIn$4 = (component, gridConfig, _gridState) => {
  5227. descendant(component.element, gridConfig.selector).each((first) => {
  5228. gridConfig.focusManager.set(component, first);
  5229. });
  5230. };
  5231. const findCurrent$1 = (component, gridConfig) => gridConfig.focusManager.get(component).bind((elem) => closest$3(elem, gridConfig.selector));
  5232. const execute$3 = (component, simulatedEvent, gridConfig, _gridState) => findCurrent$1(component, gridConfig)
  5233. .bind((focused) => gridConfig.execute(component, simulatedEvent, focused));
  5234. const doMove$2 = (cycle) => (element, focused, gridConfig, gridState) => locateVisible(element, focused, gridConfig.selector)
  5235. .bind((identified) => cycle(identified.candidates, identified.index, gridState.getNumRows().getOr(gridConfig.initSize.numRows), gridState.getNumColumns().getOr(gridConfig.initSize.numColumns)));
  5236. const handleTab = (_component, _simulatedEvent, gridConfig) => gridConfig.captureTab ? Optional.some(true) : Optional.none();
  5237. const doEscape$1 = (component, simulatedEvent, gridConfig) => gridConfig.onEscape(component, simulatedEvent);
  5238. const moveLeft$3 = doMove$2(cycleLeft$1);
  5239. const moveRight$3 = doMove$2(cycleRight$1);
  5240. const moveNorth$1 = doMove$2(cycleUp$1);
  5241. const moveSouth$1 = doMove$2(cycleDown$1);
  5242. const getKeydownRules$4 = constant$1([
  5243. rule(inSet(LEFT), west(moveLeft$3, moveRight$3)),
  5244. rule(inSet(RIGHT), east(moveLeft$3, moveRight$3)),
  5245. rule(inSet(UP), north(moveNorth$1)),
  5246. rule(inSet(DOWN), south(moveSouth$1)),
  5247. rule(and([isShift$1, inSet(TAB)]), handleTab),
  5248. rule(and([isNotShift, inSet(TAB)]), handleTab),
  5249. // Probably should make whether space is used configurable
  5250. rule(inSet(SPACE.concat(ENTER)), execute$3)
  5251. ]);
  5252. const getKeyupRules$4 = constant$1([
  5253. rule(inSet(ESCAPE), doEscape$1),
  5254. rule(inSet(SPACE), stopEventForFirefox)
  5255. ]);
  5256. var FlatgridType = typical(schema$x, flatgrid$1, getKeydownRules$4, getKeyupRules$4, () => Optional.some(focusIn$4));
  5257. const f = (container, selector, current, delta, getNewIndex) => {
  5258. const isDisabledButton = (candidate) => name$3(candidate) === 'button' && get$g(candidate, 'disabled') === 'disabled';
  5259. const tryNewIndex = (initial, index, candidates) => getNewIndex(initial, index, delta, 0, candidates.length - 1, candidates[index], (newIndex) => isDisabledButton(candidates[newIndex]) ?
  5260. tryNewIndex(initial, newIndex, candidates) :
  5261. Optional.from(candidates[newIndex]));
  5262. // I wonder if this will be a problem when the focused element is invisible (shouldn't happen)
  5263. return locateVisible(container, current, selector).bind((identified) => {
  5264. const index = identified.index;
  5265. const candidates = identified.candidates;
  5266. return tryNewIndex(index, index, candidates);
  5267. });
  5268. };
  5269. const horizontalWithoutCycles = (container, selector, current, delta) => f(container, selector, current, delta, (prevIndex, v, d, min, max, oldCandidate, onNewIndex) => {
  5270. const newIndex = clamp(v + d, min, max);
  5271. return newIndex === prevIndex ? Optional.from(oldCandidate) : onNewIndex(newIndex);
  5272. });
  5273. const horizontal = (container, selector, current, delta) => f(container, selector, current, delta, (prevIndex, v, d, min, max, _oldCandidate, onNewIndex) => {
  5274. const newIndex = cycleBy(v, d, min, max);
  5275. // If we've cycled back to the original index, we've failed to find a new valid candidate
  5276. return newIndex === prevIndex ? Optional.none() : onNewIndex(newIndex);
  5277. });
  5278. const schema$w = [
  5279. required$1('selector'),
  5280. defaulted('getInitial', Optional.none),
  5281. defaulted('execute', defaultExecute),
  5282. onKeyboardHandler('onEscape'),
  5283. defaulted('executeOnMove', false),
  5284. defaulted('allowVertical', true),
  5285. defaulted('allowHorizontal', true),
  5286. defaulted('cycles', true)
  5287. ];
  5288. // TODO: Remove dupe.
  5289. // TODO: Probably use this for not just execution.
  5290. const findCurrent = (component, flowConfig) => flowConfig.focusManager.get(component).bind((elem) => closest$3(elem, flowConfig.selector));
  5291. const execute$2 = (component, simulatedEvent, flowConfig) => findCurrent(component, flowConfig).bind((focused) => flowConfig.execute(component, simulatedEvent, focused));
  5292. const focusIn$3 = (component, flowConfig, _state) => {
  5293. flowConfig.getInitial(component).orThunk(() => descendant(component.element, flowConfig.selector)).each((first) => {
  5294. flowConfig.focusManager.set(component, first);
  5295. });
  5296. };
  5297. const moveLeft$2 = (element, focused, info) => (info.cycles ? horizontal : horizontalWithoutCycles)(element, info.selector, focused, -1);
  5298. const moveRight$2 = (element, focused, info) => (info.cycles ? horizontal : horizontalWithoutCycles)(element, info.selector, focused, +1);
  5299. const doMove$1 = (movement) => (component, simulatedEvent, flowConfig, flowState) => movement(component, simulatedEvent, flowConfig, flowState).bind(() => flowConfig.executeOnMove ?
  5300. execute$2(component, simulatedEvent, flowConfig) :
  5301. Optional.some(true));
  5302. const doEscape = (component, simulatedEvent, flowConfig) => flowConfig.onEscape(component, simulatedEvent);
  5303. const getKeydownRules$3 = (_component, _se, flowConfig, _flowState) => {
  5304. const westMovers = [...flowConfig.allowHorizontal ? LEFT : []].concat(flowConfig.allowVertical ? UP : []);
  5305. const eastMovers = [...flowConfig.allowHorizontal ? RIGHT : []].concat(flowConfig.allowVertical ? DOWN : []);
  5306. return [
  5307. rule(inSet(westMovers), doMove$1(west(moveLeft$2, moveRight$2))),
  5308. rule(inSet(eastMovers), doMove$1(east(moveLeft$2, moveRight$2))),
  5309. rule(inSet(ENTER), execute$2),
  5310. rule(inSet(SPACE), execute$2)
  5311. ];
  5312. };
  5313. const getKeyupRules$3 = constant$1([
  5314. rule(inSet(SPACE), stopEventForFirefox),
  5315. rule(inSet(ESCAPE), doEscape)
  5316. ]);
  5317. var FlowType = typical(schema$w, NoState.init, getKeydownRules$3, getKeyupRules$3, () => Optional.some(focusIn$3));
  5318. const toCell = (matrix, rowIndex, columnIndex) => Optional.from(matrix[rowIndex]).bind((row) => Optional.from(row[columnIndex]).map((cell) => ({
  5319. rowIndex,
  5320. columnIndex,
  5321. cell
  5322. })));
  5323. const cycleHorizontal = (matrix, rowIndex, startCol, deltaCol) => {
  5324. const row = matrix[rowIndex];
  5325. const colsInRow = row.length;
  5326. const newColIndex = cycleBy(startCol, deltaCol, 0, colsInRow - 1);
  5327. return toCell(matrix, rowIndex, newColIndex);
  5328. };
  5329. const cycleVertical = (matrix, colIndex, startRow, deltaRow) => {
  5330. const nextRowIndex = cycleBy(startRow, deltaRow, 0, matrix.length - 1);
  5331. const colsInNextRow = matrix[nextRowIndex].length;
  5332. const nextColIndex = clamp(colIndex, 0, colsInNextRow - 1);
  5333. return toCell(matrix, nextRowIndex, nextColIndex);
  5334. };
  5335. const moveHorizontal = (matrix, rowIndex, startCol, deltaCol) => {
  5336. const row = matrix[rowIndex];
  5337. const colsInRow = row.length;
  5338. const newColIndex = clamp(startCol + deltaCol, 0, colsInRow - 1);
  5339. return toCell(matrix, rowIndex, newColIndex);
  5340. };
  5341. const moveVertical = (matrix, colIndex, startRow, deltaRow) => {
  5342. const nextRowIndex = clamp(startRow + deltaRow, 0, matrix.length - 1);
  5343. const colsInNextRow = matrix[nextRowIndex].length;
  5344. const nextColIndex = clamp(colIndex, 0, colsInNextRow - 1);
  5345. return toCell(matrix, nextRowIndex, nextColIndex);
  5346. };
  5347. // return address(Math.floor(index / columns), index % columns);
  5348. const cycleRight = (matrix, startRow, startCol) => cycleHorizontal(matrix, startRow, startCol, +1);
  5349. const cycleLeft = (matrix, startRow, startCol) => cycleHorizontal(matrix, startRow, startCol, -1);
  5350. const cycleUp = (matrix, startRow, startCol) => cycleVertical(matrix, startCol, startRow, -1);
  5351. const cycleDown = (matrix, startRow, startCol) => cycleVertical(matrix, startCol, startRow, +1);
  5352. const moveLeft$1 = (matrix, startRow, startCol) => moveHorizontal(matrix, startRow, startCol, -1);
  5353. const moveRight$1 = (matrix, startRow, startCol) => moveHorizontal(matrix, startRow, startCol, +1);
  5354. const moveUp$1 = (matrix, startRow, startCol) => moveVertical(matrix, startCol, startRow, -1);
  5355. const moveDown$1 = (matrix, startRow, startCol) => moveVertical(matrix, startCol, startRow, +1);
  5356. const schema$v = [
  5357. requiredObjOf('selectors', [
  5358. required$1('row'),
  5359. required$1('cell')
  5360. ]),
  5361. // Used to determine whether pressing right/down at the end cycles back to the start/top
  5362. defaulted('cycles', true),
  5363. defaulted('previousSelector', Optional.none),
  5364. defaulted('execute', defaultExecute)
  5365. ];
  5366. const focusIn$2 = (component, matrixConfig, _state) => {
  5367. const focused = matrixConfig.previousSelector(component).orThunk(() => {
  5368. const selectors = matrixConfig.selectors;
  5369. return descendant(component.element, selectors.cell);
  5370. });
  5371. focused.each((cell) => {
  5372. matrixConfig.focusManager.set(component, cell);
  5373. });
  5374. };
  5375. const execute$1 = (component, simulatedEvent, matrixConfig) => search(component.element).bind((focused) => matrixConfig.execute(component, simulatedEvent, focused));
  5376. const toMatrix = (rows, matrixConfig) => map$2(rows, (row) => descendants(row, matrixConfig.selectors.cell));
  5377. const doMove = (ifCycle, ifMove) => (element, focused, matrixConfig) => {
  5378. const move = matrixConfig.cycles ? ifCycle : ifMove;
  5379. return closest$3(focused, matrixConfig.selectors.row).bind((inRow) => {
  5380. const cellsInRow = descendants(inRow, matrixConfig.selectors.cell);
  5381. return findIndex(cellsInRow, focused).bind((colIndex) => {
  5382. const allRows = descendants(element, matrixConfig.selectors.row);
  5383. return findIndex(allRows, inRow).bind((rowIndex) => {
  5384. // Now, make the matrix.
  5385. const matrix = toMatrix(allRows, matrixConfig);
  5386. return move(matrix, rowIndex, colIndex).map((next) => next.cell);
  5387. });
  5388. });
  5389. });
  5390. };
  5391. const moveLeft = doMove(cycleLeft, moveLeft$1);
  5392. const moveRight = doMove(cycleRight, moveRight$1);
  5393. const moveNorth = doMove(cycleUp, moveUp$1);
  5394. const moveSouth = doMove(cycleDown, moveDown$1);
  5395. const getKeydownRules$2 = constant$1([
  5396. rule(inSet(LEFT), west(moveLeft, moveRight)),
  5397. rule(inSet(RIGHT), east(moveLeft, moveRight)),
  5398. rule(inSet(UP), north(moveNorth)),
  5399. rule(inSet(DOWN), south(moveSouth)),
  5400. rule(inSet(SPACE.concat(ENTER)), execute$1)
  5401. ]);
  5402. const getKeyupRules$2 = constant$1([
  5403. rule(inSet(SPACE), stopEventForFirefox)
  5404. ]);
  5405. var MatrixType = typical(schema$v, NoState.init, getKeydownRules$2, getKeyupRules$2, () => Optional.some(focusIn$2));
  5406. const schema$u = [
  5407. required$1('selector'),
  5408. defaulted('execute', defaultExecute),
  5409. defaulted('moveOnTab', false)
  5410. ];
  5411. const execute = (component, simulatedEvent, menuConfig) => menuConfig.focusManager.get(component).bind((focused) => menuConfig.execute(component, simulatedEvent, focused));
  5412. const focusIn$1 = (component, menuConfig, _state) => {
  5413. // Maybe keep selection if it was there before
  5414. descendant(component.element, menuConfig.selector).each((first) => {
  5415. menuConfig.focusManager.set(component, first);
  5416. });
  5417. };
  5418. const moveUp = (element, focused, info) => horizontal(element, info.selector, focused, -1);
  5419. const moveDown = (element, focused, info) => horizontal(element, info.selector, focused, +1);
  5420. const fireShiftTab = (component, simulatedEvent, menuConfig, menuState) => menuConfig.moveOnTab ? move$1(moveUp)(component, simulatedEvent, menuConfig, menuState) : Optional.none();
  5421. const fireTab = (component, simulatedEvent, menuConfig, menuState) => menuConfig.moveOnTab ? move$1(moveDown)(component, simulatedEvent, menuConfig, menuState) : Optional.none();
  5422. const getKeydownRules$1 = constant$1([
  5423. rule(inSet(UP), move$1(moveUp)),
  5424. rule(inSet(DOWN), move$1(moveDown)),
  5425. rule(and([isShift$1, inSet(TAB)]), fireShiftTab),
  5426. rule(and([isNotShift, inSet(TAB)]), fireTab),
  5427. rule(inSet(ENTER), execute),
  5428. rule(inSet(SPACE), execute)
  5429. ]);
  5430. const getKeyupRules$1 = constant$1([
  5431. rule(inSet(SPACE), stopEventForFirefox)
  5432. ]);
  5433. var MenuType = typical(schema$u, NoState.init, getKeydownRules$1, getKeyupRules$1, () => Optional.some(focusIn$1));
  5434. const schema$t = [
  5435. onKeyboardHandler('onSpace'),
  5436. onKeyboardHandler('onEnter'),
  5437. onKeyboardHandler('onShiftEnter'),
  5438. onKeyboardHandler('onLeft'),
  5439. onKeyboardHandler('onRight'),
  5440. onKeyboardHandler('onTab'),
  5441. onKeyboardHandler('onShiftTab'),
  5442. onKeyboardHandler('onUp'),
  5443. onKeyboardHandler('onDown'),
  5444. onKeyboardHandler('onEscape'),
  5445. defaulted('stopSpaceKeyup', false),
  5446. option$3('focusIn')
  5447. ];
  5448. const getKeydownRules = (component, simulatedEvent, specialInfo) => [
  5449. rule(inSet(SPACE), specialInfo.onSpace),
  5450. rule(and([isNotShift, inSet(ENTER)]), specialInfo.onEnter),
  5451. rule(and([isShift$1, inSet(ENTER)]), specialInfo.onShiftEnter),
  5452. rule(and([isShift$1, inSet(TAB)]), specialInfo.onShiftTab),
  5453. rule(and([isNotShift, inSet(TAB)]), specialInfo.onTab),
  5454. rule(inSet(UP), specialInfo.onUp),
  5455. rule(inSet(DOWN), specialInfo.onDown),
  5456. rule(inSet(LEFT), specialInfo.onLeft),
  5457. rule(inSet(RIGHT), specialInfo.onRight),
  5458. rule(inSet(SPACE), specialInfo.onSpace)
  5459. ];
  5460. const getKeyupRules = (component, simulatedEvent, specialInfo) => [
  5461. ...(specialInfo.stopSpaceKeyup ? [rule(inSet(SPACE), stopEventForFirefox)] : []),
  5462. rule(inSet(ESCAPE), specialInfo.onEscape)
  5463. ];
  5464. var SpecialType = typical(schema$t, NoState.init, getKeydownRules, getKeyupRules, (specialInfo) => specialInfo.focusIn);
  5465. const acyclic = AcyclicType.schema();
  5466. const cyclic = CyclicType.schema();
  5467. const flow = FlowType.schema();
  5468. const flatgrid = FlatgridType.schema();
  5469. const matrix = MatrixType.schema();
  5470. const execution = ExecutionType.schema();
  5471. const menu = MenuType.schema();
  5472. const special = SpecialType.schema();
  5473. var KeyboardBranches = /*#__PURE__*/Object.freeze({
  5474. __proto__: null,
  5475. acyclic: acyclic,
  5476. cyclic: cyclic,
  5477. flow: flow,
  5478. flatgrid: flatgrid,
  5479. matrix: matrix,
  5480. execution: execution,
  5481. menu: menu,
  5482. special: special
  5483. });
  5484. const isFlatgridState = (keyState) => hasNonNullableKey(keyState, 'setGridSize');
  5485. const Keying = createModes({
  5486. branchKey: 'mode',
  5487. branches: KeyboardBranches,
  5488. name: 'keying',
  5489. active: {
  5490. events: (keyingConfig, keyingState) => {
  5491. const handler = keyingConfig.handler;
  5492. return handler.toEvents(keyingConfig, keyingState);
  5493. }
  5494. },
  5495. apis: {
  5496. focusIn: (component, keyConfig, keyState) => {
  5497. // If we have a custom sendFocusIn function, use that.
  5498. // Otherwise, we just trigger focus on the outer element.
  5499. keyConfig.sendFocusIn(keyConfig).fold(() => {
  5500. component.getSystem().triggerFocus(component.element, component.element);
  5501. }, (sendFocusIn) => {
  5502. sendFocusIn(component, keyConfig, keyState);
  5503. });
  5504. },
  5505. // These APIs are going to be interesting because they are not
  5506. // available for all keying modes
  5507. setGridSize: (component, keyConfig, keyState, numRows, numColumns) => {
  5508. if (!isFlatgridState(keyState)) {
  5509. // eslint-disable-next-line no-console
  5510. console.error('Layout does not support setGridSize');
  5511. }
  5512. else {
  5513. keyState.setGridSize(numRows, numColumns);
  5514. }
  5515. }
  5516. },
  5517. state: KeyingState
  5518. });
  5519. const premadeTag = generate$6('alloy-premade');
  5520. const premade$1 = (comp) => {
  5521. Object.defineProperty(comp.element.dom, premadeTag, {
  5522. value: comp.uid,
  5523. writable: true
  5524. });
  5525. return wrap(premadeTag, comp);
  5526. };
  5527. const isPremade = (element) => has$2(element.dom, premadeTag);
  5528. const getPremade = (spec) => get$h(spec, premadeTag);
  5529. const makeApi = (f) => markAsSketchApi((component, ...rest) => f(component.getApis(), component, ...rest), f);
  5530. const isConnected = (comp) => comp.getSystem().isConnected();
  5531. const fireDetaching = (component) => {
  5532. emit(component, detachedFromDom());
  5533. const children = component.components();
  5534. each$1(children, fireDetaching);
  5535. };
  5536. const fireAttaching = (component) => {
  5537. const children = component.components();
  5538. each$1(children, fireAttaching);
  5539. emit(component, attachedToDom());
  5540. };
  5541. // Unlike attach, a virtualAttach makes no actual DOM changes.
  5542. // This is because it should only be used in a situation
  5543. // where we are patching an existing element.
  5544. const virtualAttach = (parent, child) => {
  5545. // So we still add it to the world
  5546. parent.getSystem().addToWorld(child);
  5547. // And we fire attaching ONLY if it's already in the DOM
  5548. if (inBody(parent.element)) {
  5549. fireAttaching(child);
  5550. }
  5551. };
  5552. // Unlike detach, a virtualDetach makes no actual DOM changes.
  5553. // This is because it's used in patching circumstances.
  5554. const virtualDetach = (comp) => {
  5555. fireDetaching(comp);
  5556. comp.getSystem().removeFromWorld(comp);
  5557. };
  5558. const attach$1 = (parent, child) => {
  5559. append$2(parent.element, child.element);
  5560. };
  5561. const detachChildren$1 = (component) => {
  5562. // This will not detach the component, but will detach its children and sync at the end.
  5563. each$1(component.components(), (childComp) => remove$7(childComp.element));
  5564. // Clear the component also.
  5565. empty(component.element);
  5566. component.syncComponents();
  5567. };
  5568. const replaceChildren = (component, newSpecs, buildNewChildren) => {
  5569. // Detach all existing children
  5570. const subs = component.components();
  5571. detachChildren$1(component);
  5572. const newChildren = buildNewChildren(newSpecs);
  5573. // Determine which components have been deleted and remove them from the world
  5574. const deleted = difference(subs, newChildren);
  5575. each$1(deleted, (comp) => {
  5576. fireDetaching(comp);
  5577. component.getSystem().removeFromWorld(comp);
  5578. });
  5579. // Add all new components
  5580. each$1(newChildren, (childComp) => {
  5581. // If the component isn't connected, ie is new, then we also need to add it to the world
  5582. if (!isConnected(childComp)) {
  5583. component.getSystem().addToWorld(childComp);
  5584. attach$1(component, childComp);
  5585. if (inBody(component.element)) {
  5586. fireAttaching(childComp);
  5587. }
  5588. }
  5589. else {
  5590. attach$1(component, childComp);
  5591. }
  5592. });
  5593. component.syncComponents();
  5594. };
  5595. const virtualReplaceChildren = (component, newSpecs, buildNewChildren) => {
  5596. // When replacing we don't want to fire detachedFromDom and attachedToDom again for a premade that has just had its position in the children moved around,
  5597. // so we only detach initially if we aren't a premade. Premades will be detached later, but only if they are no longer in the child list.
  5598. const subs = component.components();
  5599. const existingComps = bind$3(newSpecs, (spec) => getPremade(spec).toArray());
  5600. each$1(subs, (childComp) => {
  5601. if (!contains$2(existingComps, childComp)) {
  5602. virtualDetach(childComp);
  5603. }
  5604. });
  5605. const newChildren = buildNewChildren(newSpecs);
  5606. // Determine which components have been deleted and remove them from the world
  5607. // It's probable the component has already been detached beforehand so only
  5608. // detach what's still attached to the world (i.e removed premades)
  5609. const deleted = difference(subs, newChildren);
  5610. each$1(deleted, (deletedComp) => {
  5611. if (isConnected(deletedComp)) {
  5612. virtualDetach(deletedComp);
  5613. }
  5614. });
  5615. // Add all new components
  5616. each$1(newChildren, (childComp) => {
  5617. // If the component isn't connected, ie is new, then we also need to add it to the world
  5618. if (!isConnected(childComp)) {
  5619. virtualAttach(component, childComp);
  5620. }
  5621. });
  5622. component.syncComponents();
  5623. };
  5624. const attach = (parent, child) => {
  5625. attachWith(parent, child, append$2);
  5626. };
  5627. const attachWith = (parent, child, insertion) => {
  5628. parent.getSystem().addToWorld(child);
  5629. insertion(parent.element, child.element);
  5630. if (inBody(parent.element)) {
  5631. fireAttaching(child);
  5632. }
  5633. parent.syncComponents();
  5634. };
  5635. const doDetach = (component) => {
  5636. fireDetaching(component);
  5637. remove$7(component.element);
  5638. component.getSystem().removeFromWorld(component);
  5639. };
  5640. const detach = (component) => {
  5641. const parent$1 = parent(component.element).bind((p) => component.getSystem().getByDom(p).toOptional());
  5642. doDetach(component);
  5643. parent$1.each((p) => {
  5644. p.syncComponents();
  5645. });
  5646. };
  5647. const detachChildren = (component) => {
  5648. // This will not detach the component, but will detach its children and sync at the end.
  5649. const subs = component.components();
  5650. each$1(subs, doDetach);
  5651. // Clear the component also.
  5652. empty(component.element);
  5653. component.syncComponents();
  5654. };
  5655. const attachSystem = (element, guiSystem) => {
  5656. attachSystemWith(element, guiSystem, append$2);
  5657. };
  5658. const attachSystemAfter = (element, guiSystem) => {
  5659. attachSystemWith(element, guiSystem, after$1);
  5660. };
  5661. const attachSystemWith = (element, guiSystem, inserter) => {
  5662. inserter(element, guiSystem.element);
  5663. const children$1 = children(guiSystem.element);
  5664. each$1(children$1, (child) => {
  5665. guiSystem.getByDom(child).each(fireAttaching);
  5666. });
  5667. };
  5668. const detachSystem = (guiSystem) => {
  5669. const children$1 = children(guiSystem.element);
  5670. each$1(children$1, (child) => {
  5671. guiSystem.getByDom(child).each(fireDetaching);
  5672. });
  5673. remove$7(guiSystem.element);
  5674. };
  5675. const determineObsoleted = (parent, index, oldObsoleted) => {
  5676. // When dealing with premades, the process of building something may have moved existing nodes around, so we see
  5677. // if the child at the index position is still the same. If it isn't, we need to introduce some complex behaviour
  5678. //
  5679. // Example:
  5680. // ```<div><premade></premade><span></span></div>```
  5681. // and then moving the premade inside a blockquote
  5682. // ```<div><blockquote><premade></premade></blockquote><span></span></div>```
  5683. //
  5684. // so when you go to replace the first thing it would think there is only 1 child which would be the span, so in
  5685. // this case we insert a marker to keep the span in the same spot.
  5686. const newObsoleted = child$2(parent, index);
  5687. return newObsoleted.map((newObs) => {
  5688. const elemChanged = oldObsoleted.exists((o) => !eq(o, newObs));
  5689. // Adding a marker prevents the case where a premade is added to something shifting it from where
  5690. // it was. That in turn un-synced all trailing children and made it so they couldn't be patched.
  5691. if (elemChanged) {
  5692. const oldTag = oldObsoleted.map(name$3).getOr('span');
  5693. const marker = SugarElement.fromTag(oldTag);
  5694. before$1(newObs, marker);
  5695. return marker;
  5696. }
  5697. else {
  5698. return newObs;
  5699. }
  5700. });
  5701. };
  5702. const ensureInDom = (parent, child, obsoleted) => {
  5703. obsoleted.fold(
  5704. // There is nothing here, so just append to the parent
  5705. () => append$2(parent, child), (obs) => {
  5706. if (!eq(obs, child)) {
  5707. // This situation occurs when the DOM element that has been patched when building it is no
  5708. // longer the one that we need to replace. This is probably caused by premades.
  5709. before$1(obs, child);
  5710. remove$7(obs);
  5711. }
  5712. });
  5713. };
  5714. const patchChildrenWith = (parent, nu, f) => {
  5715. const builtChildren = map$2(nu, f);
  5716. // Need to regather the children in case some of the previous children have moved
  5717. // to an earlier index. So this just prunes any leftover children in the dom.
  5718. const currentChildren = children(parent);
  5719. each$1(currentChildren.slice(builtChildren.length), remove$7);
  5720. return builtChildren;
  5721. };
  5722. const patchSpecChild = (parent, index, spec, build) => {
  5723. // Before building anything, this is the DOM element we are going to try to use.
  5724. const oldObsoleted = child$2(parent, index);
  5725. const childComp = build(spec, oldObsoleted);
  5726. const obsoleted = determineObsoleted(parent, index, oldObsoleted);
  5727. ensureInDom(parent, childComp.element, obsoleted);
  5728. return childComp;
  5729. };
  5730. const patchSpecChildren = (parent, specs, build) => patchChildrenWith(parent, specs, (spec, index) => patchSpecChild(parent, index, spec, build));
  5731. const patchDomChildren = (parent, nodes) => patchChildrenWith(parent, nodes, (node, index) => {
  5732. const optObsoleted = child$2(parent, index);
  5733. ensureInDom(parent, node, optObsoleted);
  5734. return node;
  5735. });
  5736. const preserve = (f, container) => {
  5737. const dos = getRootNode(container);
  5738. const refocus = active$1(dos).bind((focused) => {
  5739. const hasFocus = (elem) => eq(focused, elem);
  5740. return hasFocus(container) ? Optional.some(container) : descendant$1(container, hasFocus);
  5741. });
  5742. const result = f(container);
  5743. // If there is a focussed element, the F function may cause focus to be lost (such as by hiding elements). Restore it afterwards.
  5744. refocus.each((oldFocus) => {
  5745. active$1(dos).filter((newFocus) => eq(newFocus, oldFocus)).fold(() => {
  5746. // Only refocus if the focus has changed, otherwise we break IE
  5747. focus$4(oldFocus);
  5748. }, noop);
  5749. });
  5750. return result;
  5751. };
  5752. const withoutReuse = (parent, data) => {
  5753. preserve(() => {
  5754. replaceChildren(parent, data, () => map$2(data, parent.getSystem().build));
  5755. }, parent.element);
  5756. };
  5757. const withReuse = (parent, data) => {
  5758. // Note: We shouldn't need AriaPreserve since we're trying to keep the existing elements,
  5759. // but let's just do it for now just to be safe.
  5760. preserve(() => {
  5761. virtualReplaceChildren(parent, data, () => {
  5762. // Build the new children
  5763. return patchSpecChildren(parent.element, data, parent.getSystem().buildOrPatch);
  5764. });
  5765. }, parent.element);
  5766. };
  5767. const virtualReplace = (component, replacee, replaceeIndex, childSpec) => {
  5768. virtualDetach(replacee);
  5769. const child = patchSpecChild(component.element, replaceeIndex, childSpec, component.getSystem().buildOrPatch);
  5770. virtualAttach(component, child);
  5771. component.syncComponents();
  5772. };
  5773. const insert = (component, insertion, childSpec) => {
  5774. const child = component.getSystem().build(childSpec);
  5775. attachWith(component, child, insertion);
  5776. };
  5777. const replace = (component, replacee, replaceeIndex, childSpec) => {
  5778. detach(replacee);
  5779. insert(component, (p, c) => appendAt(p, c, replaceeIndex), childSpec);
  5780. };
  5781. const set$3 = (component, replaceConfig, replaceState, data) => {
  5782. const replacer = replaceConfig.reuseDom ? withReuse : withoutReuse;
  5783. return replacer(component, data);
  5784. };
  5785. const append = (component, replaceConfig, replaceState, appendee) => {
  5786. insert(component, append$2, appendee);
  5787. };
  5788. const prepend = (component, replaceConfig, replaceState, prependee) => {
  5789. insert(component, prepend$1, prependee);
  5790. };
  5791. // NOTE: Removee is going to be a component, not a spec.
  5792. const remove = (component, replaceConfig, replaceState, removee) => {
  5793. const children = contents(component);
  5794. const foundChild = find$5(children, (child) => eq(removee.element, child.element));
  5795. foundChild.each(detach);
  5796. };
  5797. // TODO: Rename
  5798. const contents = (component, _replaceConfig) => component.components();
  5799. const replaceAt = (component, replaceConfig, replaceState, replaceeIndex, replacer) => {
  5800. const children = contents(component);
  5801. return Optional.from(children[replaceeIndex]).map((replacee) => {
  5802. replacer.fold(() => detach(replacee), (r) => {
  5803. const replacer = replaceConfig.reuseDom ? virtualReplace : replace;
  5804. replacer(component, replacee, replaceeIndex, r);
  5805. });
  5806. return replacee;
  5807. });
  5808. };
  5809. const replaceBy = (component, replaceConfig, replaceState, replaceePred, replacer) => {
  5810. const children = contents(component);
  5811. return findIndex$1(children, replaceePred).bind((replaceeIndex) => replaceAt(component, replaceConfig, replaceState, replaceeIndex, replacer));
  5812. };
  5813. var ReplaceApis = /*#__PURE__*/Object.freeze({
  5814. __proto__: null,
  5815. append: append,
  5816. prepend: prepend,
  5817. remove: remove,
  5818. replaceAt: replaceAt,
  5819. replaceBy: replaceBy,
  5820. set: set$3,
  5821. contents: contents
  5822. });
  5823. const Replacing = create$3({
  5824. fields: [
  5825. defaultedBoolean('reuseDom', true)
  5826. ],
  5827. name: 'replacing',
  5828. apis: ReplaceApis
  5829. });
  5830. // The purpose of this check is to ensure that a simulated focus call is not going
  5831. // to recurse infinitely. Essentially, if the originator of the focus call is the same
  5832. // as the element receiving it, and it wasn't its own target, then stop the focus call
  5833. // and log a warning.
  5834. const isRecursive = (component, originator, target) => eq(originator, component.element) && !eq(originator, target);
  5835. const events$g = derive$2([
  5836. can(focus$3(), (component, simulatedEvent) => {
  5837. // originator may not always be there. Will need to check this.
  5838. const event = simulatedEvent.event;
  5839. const originator = event.originator;
  5840. const target = event.target;
  5841. if (isRecursive(component, originator, target)) {
  5842. // eslint-disable-next-line no-console
  5843. console.warn(focus$3() + ' did not get interpreted by the desired target. ' +
  5844. '\nOriginator: ' + element(originator) +
  5845. '\nTarget: ' + element(target) +
  5846. '\nCheck the ' + focus$3() + ' event handlers');
  5847. return false;
  5848. }
  5849. else {
  5850. return true;
  5851. }
  5852. })
  5853. ]);
  5854. var DefaultEvents = /*#__PURE__*/Object.freeze({
  5855. __proto__: null,
  5856. events: events$g
  5857. });
  5858. const prefix$1 = constant$1('alloy-id-');
  5859. const idAttr$1 = constant$1('data-alloy-id');
  5860. const prefix = prefix$1();
  5861. const idAttr = idAttr$1();
  5862. const write = (label, elem) => {
  5863. const id = generate$6(prefix + label);
  5864. writeOnly(elem, id);
  5865. return id;
  5866. };
  5867. const writeOnly = (elem, uid) => {
  5868. Object.defineProperty(elem.dom, idAttr, {
  5869. value: uid,
  5870. writable: true
  5871. });
  5872. };
  5873. const read = (elem) => {
  5874. const id = isElement$1(elem) ? elem.dom[idAttr] : null;
  5875. return Optional.from(id);
  5876. };
  5877. const generate$4 = (prefix) => generate$6(prefix);
  5878. const make$7 = identity;
  5879. const NoContextApi = (getComp) => {
  5880. const getMessage = (event) => `The component must be in a context to execute: ${event}` +
  5881. (getComp ? '\n' + element(getComp().element) + ' is not in context.' : '');
  5882. const fail = (event) => () => {
  5883. throw new Error(getMessage(event));
  5884. };
  5885. const warn = (event) => () => {
  5886. // eslint-disable-next-line no-console
  5887. console.warn(getMessage(event));
  5888. };
  5889. return {
  5890. debugInfo: constant$1('fake'),
  5891. triggerEvent: warn('triggerEvent'),
  5892. triggerFocus: warn('triggerFocus'),
  5893. triggerEscape: warn('triggerEscape'),
  5894. broadcast: warn('broadcast'),
  5895. broadcastOn: warn('broadcastOn'),
  5896. broadcastEvent: warn('broadcastEvent'),
  5897. build: fail('build'),
  5898. buildOrPatch: fail('buildOrPatch'),
  5899. addToWorld: fail('addToWorld'),
  5900. removeFromWorld: fail('removeFromWorld'),
  5901. addToGui: fail('addToGui'),
  5902. removeFromGui: fail('removeFromGui'),
  5903. getByUid: fail('getByUid'),
  5904. getByDom: fail('getByDom'),
  5905. isConnected: never
  5906. };
  5907. };
  5908. const singleton = NoContextApi();
  5909. const generateFrom$1 = (spec, all) => {
  5910. /*
  5911. * This takes a basic record of configured behaviours, defaults their state
  5912. * and ensures that all the behaviours were valid. Will need to document
  5913. * this entire process. Let's see where this is used.
  5914. */
  5915. const schema = map$2(all, (a) =>
  5916. // Optional here probably just due to ForeignGui listing everything it supports. Can most likely
  5917. // change it to strict once I fix the other errors.
  5918. optionObjOf(a.name(), [
  5919. required$1('config'),
  5920. defaulted('state', NoState)
  5921. ]));
  5922. const validated = asRaw('component.behaviours', objOf(schema), spec.behaviours).fold((errInfo) => {
  5923. throw new Error(formatError(errInfo) + '\nComplete spec:\n' +
  5924. JSON.stringify(spec, null, 2));
  5925. }, identity);
  5926. return {
  5927. list: all,
  5928. data: map$1(validated, (optBlobThunk) => {
  5929. const output = optBlobThunk.map((blob) => ({
  5930. config: blob.config,
  5931. state: blob.state.init(blob.config)
  5932. }));
  5933. return constant$1(output);
  5934. })
  5935. };
  5936. };
  5937. const getBehaviours$3 = (bData) => bData.list;
  5938. const getData$2 = (bData) => bData.data;
  5939. const byInnerKey = (data, tuple) => {
  5940. const r = {};
  5941. each(data, (detail, key) => {
  5942. each(detail, (value, indexKey) => {
  5943. const chain = get$h(r, indexKey).getOr([]);
  5944. r[indexKey] = chain.concat([
  5945. tuple(key, value)
  5946. ]);
  5947. });
  5948. });
  5949. return r;
  5950. };
  5951. // Based on all the behaviour exhibits, and the original dom modification, identify
  5952. // the overall combined dom modification that needs to occur
  5953. const combine$1 = (info, baseMod, behaviours, base) => {
  5954. // Clone the object so we can change it.
  5955. const modsByBehaviour = { ...baseMod };
  5956. each$1(behaviours, (behaviour) => {
  5957. modsByBehaviour[behaviour.name()] = behaviour.exhibit(info, base);
  5958. });
  5959. // byAspect format: { classes: [ { name: Toggling, modification: [ 'selected' ] } ] }
  5960. const byAspect = byInnerKey(modsByBehaviour, (name, modification) => ({ name, modification }));
  5961. const combineObjects = (objects) => foldr(objects, (b, a) => ({ ...a.modification, ...b }), {});
  5962. const combinedClasses = foldr(byAspect.classes, (b, a) => a.modification.concat(b), []);
  5963. const combinedAttributes = combineObjects(byAspect.attributes);
  5964. const combinedStyles = combineObjects(byAspect.styles);
  5965. return nu$2({
  5966. classes: combinedClasses,
  5967. attributes: combinedAttributes,
  5968. styles: combinedStyles
  5969. });
  5970. };
  5971. const sortKeys = (label, keyName, array, order) => {
  5972. try {
  5973. const sorted = sort(array, (a, b) => {
  5974. const aKey = a[keyName];
  5975. const bKey = b[keyName];
  5976. const aIndex = order.indexOf(aKey);
  5977. const bIndex = order.indexOf(bKey);
  5978. if (aIndex === -1) {
  5979. throw new Error('The ordering for ' + label + ' does not have an entry for ' + aKey +
  5980. '.\nOrder specified: ' + JSON.stringify(order, null, 2));
  5981. }
  5982. if (bIndex === -1) {
  5983. throw new Error('The ordering for ' + label + ' does not have an entry for ' + bKey +
  5984. '.\nOrder specified: ' + JSON.stringify(order, null, 2));
  5985. }
  5986. if (aIndex < bIndex) {
  5987. return -1;
  5988. }
  5989. else if (bIndex < aIndex) {
  5990. return 1;
  5991. }
  5992. else {
  5993. return 0;
  5994. }
  5995. });
  5996. return Result.value(sorted);
  5997. }
  5998. catch (err) {
  5999. return Result.error([err]);
  6000. }
  6001. };
  6002. const uncurried = (handler, purpose) => ({
  6003. handler,
  6004. purpose
  6005. });
  6006. const curried = (handler, purpose) => ({
  6007. cHandler: handler,
  6008. purpose
  6009. });
  6010. const curryArgs = (descHandler, extraArgs) => curried(curry.apply(undefined, [descHandler.handler].concat(extraArgs)), descHandler.purpose);
  6011. const getCurried = (descHandler) => descHandler.cHandler;
  6012. const behaviourTuple = (name, handler) => ({
  6013. name,
  6014. handler
  6015. });
  6016. const nameToHandlers = (behaviours, info) => {
  6017. const r = {};
  6018. each$1(behaviours, (behaviour) => {
  6019. r[behaviour.name()] = behaviour.handlers(info);
  6020. });
  6021. return r;
  6022. };
  6023. const groupByEvents = (info, behaviours, base) => {
  6024. const behaviourEvents = {
  6025. ...base,
  6026. ...nameToHandlers(behaviours, info)
  6027. };
  6028. // Now, with all of these events, we need to index by event name
  6029. return byInnerKey(behaviourEvents, behaviourTuple);
  6030. };
  6031. const combine = (info, eventOrder, behaviours, base) => {
  6032. const byEventName = groupByEvents(info, behaviours, base);
  6033. return combineGroups(byEventName, eventOrder);
  6034. };
  6035. const assemble = (rawHandler) => {
  6036. const handler = read$1(rawHandler);
  6037. return (component, simulatedEvent, ...rest) => {
  6038. const args = [component, simulatedEvent].concat(rest);
  6039. if (handler.abort.apply(undefined, args)) {
  6040. simulatedEvent.stop();
  6041. }
  6042. else if (handler.can.apply(undefined, args)) {
  6043. handler.run.apply(undefined, args);
  6044. }
  6045. };
  6046. };
  6047. const missingOrderError = (eventName, tuples) => Result.error([
  6048. 'The event (' + eventName + ') has more than one behaviour that listens to it.\nWhen this occurs, you must ' +
  6049. 'specify an event ordering for the behaviours in your spec (e.g. [ "listing", "toggling" ]).\nThe behaviours that ' +
  6050. 'can trigger it are: ' + JSON.stringify(map$2(tuples, (c) => c.name), null, 2)
  6051. ]);
  6052. const fuse = (tuples, eventOrder, eventName) => {
  6053. // ASSUMPTION: tuples.length will never be 0, because it wouldn't have an entry if it was 0
  6054. const order = eventOrder[eventName];
  6055. if (!order) {
  6056. return missingOrderError(eventName, tuples);
  6057. }
  6058. else {
  6059. return sortKeys('Event: ' + eventName, 'name', tuples, order).map((sortedTuples) => {
  6060. const handlers = map$2(sortedTuples, (tuple) => tuple.handler);
  6061. return fuse$1(handlers);
  6062. });
  6063. }
  6064. };
  6065. const combineGroups = (byEventName, eventOrder) => {
  6066. const r = mapToArray(byEventName, (tuples, eventName) => {
  6067. const combined = tuples.length === 1 ? Result.value(tuples[0].handler) : fuse(tuples, eventOrder, eventName);
  6068. return combined.map((handler) => {
  6069. const assembled = assemble(handler);
  6070. const purpose = tuples.length > 1 ? filter$2(eventOrder[eventName], (o) => exists(tuples, (t) => t.name === o)).join(' > ') : tuples[0].name;
  6071. return wrap(eventName, uncurried(assembled, purpose));
  6072. });
  6073. });
  6074. return consolidate(r, {});
  6075. };
  6076. const baseBehaviour = 'alloy.base.behaviour';
  6077. const schema$s = objOf([
  6078. field$1('dom', 'dom', required$2(), objOf([
  6079. // Note, no children.
  6080. required$1('tag'),
  6081. defaulted('styles', {}),
  6082. defaulted('classes', []),
  6083. defaulted('attributes', {}),
  6084. option$3('value'),
  6085. option$3('innerHtml')
  6086. ])),
  6087. required$1('components'),
  6088. required$1('uid'),
  6089. defaulted('events', {}),
  6090. defaulted('apis', {}),
  6091. // Use mergeWith in the future when pre-built behaviours conflict
  6092. field$1('eventOrder', 'eventOrder', mergeWith({
  6093. // Note, not using constant behaviour names to avoid code size of unused behaviours
  6094. [execute$5()]: ['disabling', baseBehaviour, 'toggling', 'typeaheadevents'],
  6095. [focus$3()]: [baseBehaviour, 'focusing', 'keying'],
  6096. [systemInit()]: [baseBehaviour, 'disabling', 'toggling', 'representing', 'tooltipping'],
  6097. [input()]: [baseBehaviour, 'representing', 'streaming', 'invalidating'],
  6098. [detachedFromDom()]: [baseBehaviour, 'representing', 'item-events', 'toolbar-button-events', 'tooltipping'],
  6099. [mousedown()]: ['focusing', baseBehaviour, 'item-type-events'],
  6100. [touchstart()]: ['focusing', baseBehaviour, 'item-type-events'],
  6101. [mouseover()]: ['item-type-events', 'tooltipping'],
  6102. [receive()]: ['receiving', 'reflecting', 'tooltipping']
  6103. }), anyValue()),
  6104. option$3('domModification')
  6105. ]);
  6106. const toInfo = (spec) => asRaw('custom.definition', schema$s, spec);
  6107. const toDefinition = (detail) =>
  6108. // EFFICIENCY: Consider not merging here.
  6109. ({
  6110. ...detail.dom,
  6111. uid: detail.uid,
  6112. domChildren: map$2(detail.components, (comp) => comp.element)
  6113. });
  6114. const toModification = (detail) => detail.domModification.fold(() => nu$2({}), nu$2);
  6115. const toEvents = (info) => info.events;
  6116. const diffKeyValueSet = (newObj, oldObj) => {
  6117. const newKeys = keys(newObj);
  6118. const oldKeys = keys(oldObj);
  6119. const toRemove = difference(oldKeys, newKeys);
  6120. const toSet = bifilter(newObj, (v, k) => {
  6121. return !has$2(oldObj, k) || v !== oldObj[k];
  6122. }).t;
  6123. return { toRemove, toSet };
  6124. };
  6125. const reconcileToDom = (definition, obsoleted) => {
  6126. const { class: clazz, style, ...existingAttributes } = clone$2(obsoleted);
  6127. const { toSet: attrsToSet, toRemove: attrsToRemove } = diffKeyValueSet(definition.attributes, existingAttributes);
  6128. const updateAttrs = () => {
  6129. each$1(attrsToRemove, (a) => remove$8(obsoleted, a));
  6130. setAll$1(obsoleted, attrsToSet);
  6131. };
  6132. const existingStyles = getAllRaw(obsoleted);
  6133. const { toSet: stylesToSet, toRemove: stylesToRemove } = diffKeyValueSet(definition.styles, existingStyles);
  6134. const updateStyles = () => {
  6135. each$1(stylesToRemove, (s) => remove$6(obsoleted, s));
  6136. setAll(obsoleted, stylesToSet);
  6137. };
  6138. const existingClasses = get$7(obsoleted);
  6139. const classesToRemove = difference(existingClasses, definition.classes);
  6140. const classesToAdd = difference(definition.classes, existingClasses);
  6141. const updateClasses = () => {
  6142. add$1(obsoleted, classesToAdd);
  6143. remove$2(obsoleted, classesToRemove);
  6144. };
  6145. const updateHtml = (html) => {
  6146. set$8(obsoleted, html);
  6147. };
  6148. const updateChildren = () => {
  6149. const children = definition.domChildren;
  6150. patchDomChildren(obsoleted, children);
  6151. };
  6152. const updateValue = () => {
  6153. const valueElement = obsoleted;
  6154. const value = definition.value.getOrUndefined();
  6155. if (value !== get$5(valueElement)) {
  6156. // TINY-8736: Value.set throws an error in case the value is undefined
  6157. set$4(valueElement, value !== null && value !== void 0 ? value : '');
  6158. }
  6159. };
  6160. updateAttrs();
  6161. updateClasses();
  6162. updateStyles();
  6163. // Patching can only support one form of children, so we only update the html or the children, but never both
  6164. definition.innerHtml.fold(updateChildren, updateHtml);
  6165. updateValue();
  6166. return obsoleted;
  6167. };
  6168. const introduceToDom = (definition) => {
  6169. const subject = SugarElement.fromTag(definition.tag);
  6170. setAll$1(subject, definition.attributes);
  6171. add$1(subject, definition.classes);
  6172. setAll(subject, definition.styles);
  6173. // Remember: Order of innerHtml vs children is important.
  6174. definition.innerHtml.each((html) => set$8(subject, html));
  6175. // Children are already elements.
  6176. const children = definition.domChildren;
  6177. append$1(subject, children);
  6178. definition.value.each((value) => {
  6179. set$4(subject, value);
  6180. });
  6181. return subject;
  6182. };
  6183. const attemptPatch = (definition, obsoleted) => {
  6184. try {
  6185. const e = reconcileToDom(definition, obsoleted);
  6186. return Optional.some(e);
  6187. }
  6188. catch (_a) {
  6189. return Optional.none();
  6190. }
  6191. };
  6192. // If a component has both innerHtml and children then we can't patch it
  6193. const hasMixedChildren = (definition) => definition.innerHtml.isSome() && definition.domChildren.length > 0;
  6194. const renderToDom = (definition, optObsoleted) => {
  6195. // If the current tag doesn't match, let's not try to add anything further down the tree.
  6196. // If it does match though and we don't have mixed children then attempt to patch attributes etc...
  6197. const canBePatched = (candidate) => name$3(candidate) === definition.tag && !hasMixedChildren(definition) && !isPremade(candidate);
  6198. const elem = optObsoleted
  6199. .filter(canBePatched)
  6200. .bind((obsoleted) => attemptPatch(definition, obsoleted))
  6201. .getOrThunk(() => introduceToDom(definition));
  6202. writeOnly(elem, definition.uid);
  6203. return elem;
  6204. };
  6205. // This goes through the list of behaviours defined for a particular spec (removing anything
  6206. // that has been revoked), and returns the BehaviourType (e.g. Sliding)
  6207. const getBehaviours$2 = (spec) => {
  6208. const behaviours = get$h(spec, 'behaviours').getOr({});
  6209. return bind$3(keys(behaviours), (name) => {
  6210. const behaviour = behaviours[name];
  6211. return isNonNullable(behaviour) ? [behaviour.me] : [];
  6212. });
  6213. };
  6214. const generateFrom = (spec, all) => generateFrom$1(spec, all);
  6215. const generate$3 = (spec) => {
  6216. const all = getBehaviours$2(spec);
  6217. return generateFrom(spec, all);
  6218. };
  6219. // This is probably far too complicated. I think DomModification is probably
  6220. // questionable as a concept. Maybe it should be deprecated.
  6221. const getDomDefinition = (info, bList, bData) => {
  6222. // Get the current DOM definition from the spec
  6223. const definition = toDefinition(info);
  6224. // Get the current DOM modification definition from the spec
  6225. const infoModification = toModification(info);
  6226. // Treat the DOM modification like it came from a behaviour
  6227. const baseModification = {
  6228. 'alloy.base.modification': infoModification
  6229. };
  6230. // Combine the modifications from any defined behaviours
  6231. const modification = bList.length > 0 ? combine$1(bData, baseModification, bList, definition) : infoModification;
  6232. // Transform the DOM definition with the combined dom modifications to make a new DOM definition
  6233. return merge(definition, modification);
  6234. };
  6235. const getEvents = (info, bList, bData) => {
  6236. const baseEvents = {
  6237. 'alloy.base.behaviour': toEvents(info)
  6238. };
  6239. return combine(bData, info.eventOrder, bList, baseEvents).getOrDie();
  6240. };
  6241. const build$2 = (spec, obsoleted) => {
  6242. const getMe = () => me;
  6243. const systemApi = Cell(singleton);
  6244. const info = getOrDie(toInfo(spec));
  6245. const bBlob = generate$3(spec);
  6246. const bList = getBehaviours$3(bBlob);
  6247. const bData = getData$2(bBlob);
  6248. const modDefinition = getDomDefinition(info, bList, bData);
  6249. const item = renderToDom(modDefinition, obsoleted);
  6250. const events = getEvents(info, bList, bData);
  6251. const subcomponents = Cell(info.components);
  6252. const connect = (newApi) => {
  6253. systemApi.set(newApi);
  6254. };
  6255. const disconnect = () => {
  6256. systemApi.set(NoContextApi(getMe));
  6257. };
  6258. const syncComponents = () => {
  6259. // Update the component list with the current children
  6260. const children$1 = children(item);
  6261. // INVESTIGATE: Not sure about how to handle text nodes here.
  6262. const subs = bind$3(children$1, (child) => systemApi.get().getByDom(child).fold(() => [], pure$2));
  6263. subcomponents.set(subs);
  6264. };
  6265. // TYPIFY (any here is for the info.apis() pathway)
  6266. const config = (behaviour) => {
  6267. const b = bData;
  6268. const f = isFunction(b[behaviour.name()]) ? b[behaviour.name()] : () => {
  6269. throw new Error('Could not find ' + behaviour.name() + ' in ' + JSON.stringify(spec, null, 2));
  6270. };
  6271. return f();
  6272. };
  6273. const hasConfigured = (behaviour) => isFunction(bData[behaviour.name()]);
  6274. const getApis = () => info.apis;
  6275. const readState = (behaviourName) => bData[behaviourName]().map((b) => b.state.readState()).getOr('not enabled');
  6276. const me = {
  6277. uid: spec.uid,
  6278. getSystem: systemApi.get,
  6279. config,
  6280. hasConfigured,
  6281. spec,
  6282. readState,
  6283. getApis,
  6284. connect,
  6285. disconnect,
  6286. element: item,
  6287. syncComponents,
  6288. components: subcomponents.get,
  6289. events
  6290. };
  6291. return me;
  6292. };
  6293. const buildSubcomponents = (spec, obsoleted) => {
  6294. const components = get$h(spec, 'components').getOr([]);
  6295. return obsoleted.fold(() => map$2(components, build$1), (obs) => map$2(components, (c, i) => {
  6296. return buildOrPatch(c, child$2(obs, i));
  6297. }));
  6298. };
  6299. const buildFromSpec = (userSpec, obsoleted) => {
  6300. const { events: specEvents, ...spec } = make$7(userSpec);
  6301. // Build the subcomponents. A spec hierarchy is built from the bottom up.
  6302. // obsoleted is used to define which element we are attempting to replace
  6303. // so that it might be used to patch the DOM instead of recreate it.
  6304. const components = buildSubcomponents(spec, obsoleted);
  6305. const completeSpec = {
  6306. ...spec,
  6307. events: { ...DefaultEvents, ...specEvents },
  6308. components
  6309. };
  6310. return Result.value(
  6311. // Note, this isn't a spec any more, because it has built children
  6312. build$2(completeSpec, obsoleted));
  6313. };
  6314. const text$2 = (textContent) => {
  6315. const element = SugarElement.fromText(textContent);
  6316. return external({
  6317. element
  6318. });
  6319. };
  6320. const external = (spec) => {
  6321. const extSpec = asRawOrDie$1('external.component', objOfOnly([
  6322. required$1('element'),
  6323. option$3('uid')
  6324. ]), spec);
  6325. const systemApi = Cell(NoContextApi());
  6326. const connect = (newApi) => {
  6327. systemApi.set(newApi);
  6328. };
  6329. const disconnect = () => {
  6330. systemApi.set(NoContextApi(() => me));
  6331. };
  6332. const uid = extSpec.uid.getOrThunk(() => generate$4('external'));
  6333. writeOnly(extSpec.element, uid);
  6334. const me = {
  6335. uid,
  6336. getSystem: systemApi.get,
  6337. config: Optional.none,
  6338. hasConfigured: never,
  6339. connect,
  6340. disconnect,
  6341. getApis: () => ({}),
  6342. element: extSpec.element,
  6343. spec,
  6344. readState: constant$1('No state'),
  6345. syncComponents: noop,
  6346. components: constant$1([]),
  6347. events: {}
  6348. };
  6349. return premade$1(me);
  6350. };
  6351. // We experimented with just having a counter for efficiency, but that fails for situations
  6352. // where an external JS file is using alloy, and is contained within another
  6353. // alloy root container. The ids can conflict, because the counters do not
  6354. // know about each other (being parts of separate scripts).
  6355. //
  6356. // There are other solutions than this ... not sure if they are going to have better performance, though
  6357. const uids = generate$4;
  6358. const isSketchSpec$1 = (spec) => has$2(spec, 'uid');
  6359. // INVESTIGATE: A better way to provide 'meta-specs'
  6360. const buildOrPatch = (spec, obsoleted) => getPremade(spec).getOrThunk(() => {
  6361. // EFFICIENCY: Consider not merging here, and passing uid through separately
  6362. const userSpecWithUid = isSketchSpec$1(spec) ? spec : {
  6363. uid: uids(''),
  6364. ...spec
  6365. };
  6366. return buildFromSpec(userSpecWithUid, obsoleted).getOrDie();
  6367. });
  6368. const build$1 = (spec) => buildOrPatch(spec, Optional.none());
  6369. const premade = premade$1;
  6370. // Mark this component as busy, or blocked.
  6371. const block = (component, config, state,
  6372. // This works in conjunction with the 'getRoot' function in the config. To
  6373. // attach a blocker component to the dom, ensure that 'getRoot' returns a
  6374. // component, and this function returns the specification of the component to
  6375. // attach.
  6376. getBusySpec) => {
  6377. set$9(component.element, 'aria-busy', true);
  6378. const root = config.getRoot(component).getOr(component);
  6379. const blockerBehaviours = derive$1([
  6380. // Trap the "Tab" key and don't let it escape.
  6381. Keying.config({
  6382. mode: 'special',
  6383. onTab: () => Optional.some(true),
  6384. onShiftTab: () => Optional.some(true)
  6385. }),
  6386. Focusing.config({})
  6387. ]);
  6388. const blockSpec = getBusySpec(root, blockerBehaviours);
  6389. const blocker = root.getSystem().build(blockSpec);
  6390. Replacing.append(root, premade(blocker));
  6391. if (blocker.hasConfigured(Keying) && config.focus) {
  6392. Keying.focusIn(blocker);
  6393. }
  6394. if (!state.isBlocked()) {
  6395. config.onBlock(component);
  6396. }
  6397. state.blockWith(() => Replacing.remove(root, blocker));
  6398. };
  6399. // Mark this component as unblocked, or not busy. This is a noop on a component
  6400. // that isn't blocked.
  6401. const unblock = (component, config, state) => {
  6402. remove$8(component.element, 'aria-busy');
  6403. if (state.isBlocked()) {
  6404. config.onUnblock(component);
  6405. }
  6406. state.clear();
  6407. };
  6408. const isBlocked = (component, blockingConfig, blockingState) => blockingState.isBlocked();
  6409. var BlockingApis = /*#__PURE__*/Object.freeze({
  6410. __proto__: null,
  6411. block: block,
  6412. unblock: unblock,
  6413. isBlocked: isBlocked
  6414. });
  6415. var BlockingSchema = [
  6416. // The blocking behaviour places a blocking element over the DOM while the
  6417. // component is in the blocked state. If a function is provided here that
  6418. // returns Some, then the blocking element will be added as a child of the
  6419. // element returned. Otherwise, it will be added as a child of the main
  6420. // component.
  6421. defaultedFunction('getRoot', Optional.none),
  6422. // This boolean, if provided, will specify whether the blocking element is
  6423. // focused when the component is first blocked
  6424. defaultedBoolean('focus', true),
  6425. // This function, if provided, will be called any time the component is
  6426. // blocked (unless it was already blocked).
  6427. onHandler('onBlock'),
  6428. // This function, if provided, will be called any time the component is
  6429. // unblocked (unless it was already unblocked).
  6430. onHandler('onUnblock')
  6431. ];
  6432. const init$f = () => {
  6433. const blocker = destroyable();
  6434. const blockWith = (destroy) => {
  6435. blocker.set({ destroy });
  6436. };
  6437. return nu$4({
  6438. readState: blocker.isSet,
  6439. blockWith,
  6440. clear: blocker.clear,
  6441. isBlocked: blocker.isSet
  6442. });
  6443. };
  6444. var BlockingState = /*#__PURE__*/Object.freeze({
  6445. __proto__: null,
  6446. init: init$f
  6447. });
  6448. // Mark a component as able to be "Blocked" or able to enter a busy state. See
  6449. // BlockingSchema and BlockingApis for more details on how to configure this.
  6450. const Blocking = create$3({
  6451. fields: BlockingSchema,
  6452. name: 'blocking',
  6453. apis: BlockingApis,
  6454. state: BlockingState
  6455. });
  6456. const getCurrent = (component, composeConfig, _composeState) => composeConfig.find(component);
  6457. var ComposeApis = /*#__PURE__*/Object.freeze({
  6458. __proto__: null,
  6459. getCurrent: getCurrent
  6460. });
  6461. const ComposeSchema = [
  6462. required$1('find')
  6463. ];
  6464. const Composing = create$3({
  6465. fields: ComposeSchema,
  6466. name: 'composing',
  6467. apis: ComposeApis
  6468. });
  6469. const getCoupled = (component, coupleConfig, coupleState, name) => coupleState.getOrCreate(component, coupleConfig, name);
  6470. const getExistingCoupled = (component, coupleConfig, coupleState, name) => coupleState.getExisting(component, coupleConfig, name);
  6471. var CouplingApis = /*#__PURE__*/Object.freeze({
  6472. __proto__: null,
  6473. getCoupled: getCoupled,
  6474. getExistingCoupled: getExistingCoupled
  6475. });
  6476. var CouplingSchema = [
  6477. requiredOf('others', setOf(Result.value, anyValue()))
  6478. ];
  6479. // Unfortunately, the Coupling APIs currently throw errors when the coupled name
  6480. // is not recognised. This is because if the wrong name is used, it is a
  6481. // non-recoverable error, and the developer should be notified. However, there are
  6482. // better ways to do this: (removing this API and only returning Optionals/Results)
  6483. const init$e = () => {
  6484. const coupled = {};
  6485. const lookupCoupled = (coupleConfig, coupledName) => {
  6486. const available = keys(coupleConfig.others);
  6487. if (available.length === 0) {
  6488. throw new Error('Cannot find any known coupled components');
  6489. }
  6490. else {
  6491. return get$h(coupled, coupledName);
  6492. }
  6493. };
  6494. const getOrCreate = (component, coupleConfig, name) => {
  6495. return lookupCoupled(coupleConfig, name).getOrThunk(() => {
  6496. // TODO: TINY-9014 Likely type error. coupleConfig.others[key] is
  6497. // `() => ((comp: AlloyComponent) => AlloySpec)`,
  6498. // but builder is being treated as a `(comp: AlloyComponent) => AlloySpec`
  6499. const builder = get$h(coupleConfig.others, name).getOrDie('No information found for coupled component: ' + name);
  6500. const spec = builder(component);
  6501. const built = component.getSystem().build(spec);
  6502. coupled[name] = built;
  6503. return built;
  6504. });
  6505. };
  6506. const getExisting = (component, coupleConfig, name) => {
  6507. return lookupCoupled(coupleConfig, name).orThunk(() => {
  6508. // Validate we recognise this coupled component's name.
  6509. get$h(coupleConfig.others, name).getOrDie('No information found for coupled component: ' + name);
  6510. // It's a valid name, so return None, because it hasn't been built yet.
  6511. return Optional.none();
  6512. });
  6513. };
  6514. const readState = constant$1({});
  6515. return nu$4({
  6516. readState,
  6517. getExisting,
  6518. getOrCreate
  6519. });
  6520. };
  6521. var CouplingState = /*#__PURE__*/Object.freeze({
  6522. __proto__: null,
  6523. init: init$e
  6524. });
  6525. const Coupling = create$3({
  6526. fields: CouplingSchema,
  6527. name: 'coupling',
  6528. apis: CouplingApis,
  6529. state: CouplingState
  6530. });
  6531. // Just use "disabled" attribute for these, not "aria-disabled"
  6532. const nativeDisabled = [
  6533. 'input',
  6534. 'button',
  6535. 'textarea',
  6536. 'select'
  6537. ];
  6538. const onLoad$5 = (component, disableConfig, disableState) => {
  6539. const f = disableConfig.disabled() ? disable : enable;
  6540. f(component, disableConfig);
  6541. };
  6542. const hasNative = (component, config) => config.useNative === true && contains$2(nativeDisabled, name$3(component.element));
  6543. const nativeIsDisabled = (component) => has$1(component.element, 'disabled');
  6544. const nativeDisable = (component) => {
  6545. set$9(component.element, 'disabled', 'disabled');
  6546. };
  6547. const nativeEnable = (component) => {
  6548. remove$8(component.element, 'disabled');
  6549. };
  6550. const ariaIsDisabled = (component) => get$g(component.element, 'aria-disabled') === 'true';
  6551. const ariaDisable = (component) => {
  6552. set$9(component.element, 'aria-disabled', 'true');
  6553. };
  6554. const ariaEnable = (component) => {
  6555. set$9(component.element, 'aria-disabled', 'false');
  6556. };
  6557. const disable = (component, disableConfig, _disableState) => {
  6558. disableConfig.disableClass.each((disableClass) => {
  6559. add$2(component.element, disableClass);
  6560. });
  6561. const f = hasNative(component, disableConfig) ? nativeDisable : ariaDisable;
  6562. f(component);
  6563. disableConfig.onDisabled(component);
  6564. };
  6565. const enable = (component, disableConfig, _disableState) => {
  6566. disableConfig.disableClass.each((disableClass) => {
  6567. remove$3(component.element, disableClass);
  6568. });
  6569. const f = hasNative(component, disableConfig) ? nativeEnable : ariaEnable;
  6570. f(component);
  6571. disableConfig.onEnabled(component);
  6572. };
  6573. const isDisabled$1 = (component, disableConfig) => hasNative(component, disableConfig) ? nativeIsDisabled(component) : ariaIsDisabled(component);
  6574. const set$2 = (component, disableConfig, disableState, disabled) => {
  6575. const f = disabled ? disable : enable;
  6576. f(component, disableConfig);
  6577. };
  6578. var DisableApis = /*#__PURE__*/Object.freeze({
  6579. __proto__: null,
  6580. enable: enable,
  6581. disable: disable,
  6582. isDisabled: isDisabled$1,
  6583. onLoad: onLoad$5,
  6584. set: set$2
  6585. });
  6586. const exhibit$5 = (base, disableConfig) => nu$2({
  6587. // Do not add the attribute yet, because it will depend on the node name
  6588. // if we use "aria-disabled" or just "disabled"
  6589. classes: disableConfig.disabled() ? disableConfig.disableClass.toArray() : []
  6590. });
  6591. const events$f = (disableConfig, disableState) => derive$2([
  6592. abort(execute$5(), (component, _simulatedEvent) => isDisabled$1(component, disableConfig)),
  6593. loadEvent(disableConfig, disableState, onLoad$5)
  6594. ]);
  6595. var ActiveDisable = /*#__PURE__*/Object.freeze({
  6596. __proto__: null,
  6597. exhibit: exhibit$5,
  6598. events: events$f
  6599. });
  6600. var DisableSchema = [
  6601. defaultedFunction('disabled', never),
  6602. defaulted('useNative', true),
  6603. option$3('disableClass'),
  6604. onHandler('onDisabled'),
  6605. onHandler('onEnabled')
  6606. ];
  6607. const Disabling = create$3({
  6608. fields: DisableSchema,
  6609. name: 'disabling',
  6610. active: ActiveDisable,
  6611. apis: DisableApis
  6612. });
  6613. const NuPositionCss = (position, left, top, right, bottom) => {
  6614. const toPx = (num) => num + 'px';
  6615. return {
  6616. position,
  6617. left: left.map(toPx),
  6618. top: top.map(toPx),
  6619. right: right.map(toPx),
  6620. bottom: bottom.map(toPx)
  6621. };
  6622. };
  6623. const toOptions = (position) => ({
  6624. ...position,
  6625. position: Optional.some(position.position)
  6626. });
  6627. const applyPositionCss = (element, position) => {
  6628. setOptions(element, toOptions(position));
  6629. };
  6630. const appear = (component, contextualInfo) => {
  6631. const elem = component.element;
  6632. add$2(elem, contextualInfo.transitionClass);
  6633. remove$3(elem, contextualInfo.fadeOutClass);
  6634. add$2(elem, contextualInfo.fadeInClass);
  6635. contextualInfo.onShow(component);
  6636. };
  6637. const disappear = (component, contextualInfo) => {
  6638. const elem = component.element;
  6639. add$2(elem, contextualInfo.transitionClass);
  6640. remove$3(elem, contextualInfo.fadeInClass);
  6641. add$2(elem, contextualInfo.fadeOutClass);
  6642. contextualInfo.onHide(component);
  6643. };
  6644. const isPartiallyVisible = (box, bounds) => box.y < bounds.bottom && box.bottom > bounds.y;
  6645. const isTopCompletelyVisible = (box, bounds) => box.y >= bounds.y;
  6646. const isBottomCompletelyVisible = (box, bounds) => box.bottom <= bounds.bottom;
  6647. const forceTopPosition = (winBox, leftX, viewport) => ({
  6648. location: 'top',
  6649. leftX,
  6650. topY: viewport.bounds.y - winBox.y
  6651. });
  6652. const forceBottomPosition = (winBox, leftX, viewport) => ({
  6653. location: 'bottom',
  6654. leftX,
  6655. bottomY: winBox.bottom - viewport.bounds.bottom
  6656. });
  6657. const getDockedLeftPosition = (bounds) => {
  6658. // Essentially, we are just getting the bounding client rect left here,
  6659. // because winBox.x will be the scroll value.
  6660. return bounds.box.x - bounds.win.x;
  6661. };
  6662. const tryDockingPosition = (modes, bounds, viewport) => {
  6663. const winBox = bounds.win;
  6664. const box = bounds.box;
  6665. const leftX = getDockedLeftPosition(bounds);
  6666. return findMap(modes, (mode) => {
  6667. switch (mode) {
  6668. case 'bottom':
  6669. return !isBottomCompletelyVisible(box, viewport.bounds) ? Optional.some(forceBottomPosition(winBox, leftX, viewport)) : Optional.none();
  6670. case 'top':
  6671. return !isTopCompletelyVisible(box, viewport.bounds) ? Optional.some(forceTopPosition(winBox, leftX, viewport)) : Optional.none();
  6672. default:
  6673. return Optional.none();
  6674. }
  6675. }).getOr({
  6676. location: 'no-dock'
  6677. });
  6678. };
  6679. const isVisibleForModes = (modes, box, viewport) => forall(modes, (mode) => {
  6680. switch (mode) {
  6681. case 'bottom':
  6682. return isBottomCompletelyVisible(box, viewport.bounds);
  6683. case 'top':
  6684. return isTopCompletelyVisible(box, viewport.bounds);
  6685. }
  6686. });
  6687. const getXYForRestoring = (pos, viewport) => {
  6688. const priorY = viewport.optScrollEnv.fold(constant$1(pos.bounds.y), (scrollEnv) => scrollEnv.scrollElmTop + (pos.bounds.y - scrollEnv.currentScrollTop));
  6689. return SugarPosition(pos.bounds.x, priorY);
  6690. };
  6691. const getXYForSaving = (box, viewport) => {
  6692. const priorY = viewport.optScrollEnv.fold(constant$1(box.y), (scrollEnv) => box.y + scrollEnv.currentScrollTop - scrollEnv.scrollElmTop);
  6693. return SugarPosition(box.x, priorY);
  6694. };
  6695. const getPrior = (elem, viewport, state) => state.getInitialPos().map((pos) => {
  6696. const xy = getXYForRestoring(pos, viewport);
  6697. return {
  6698. box: bounds(xy.left, xy.top, get$c(elem), get$d(elem)),
  6699. location: pos.location
  6700. };
  6701. });
  6702. const storePrior = (elem, box, viewport, state, decision) => {
  6703. const xy = getXYForSaving(box, viewport);
  6704. const bounds$1 = bounds(xy.left, xy.top, box.width, box.height);
  6705. state.setInitialPos({
  6706. style: getAllRaw(elem),
  6707. position: get$e(elem, 'position') || 'static',
  6708. bounds: bounds$1,
  6709. location: decision.location
  6710. });
  6711. };
  6712. // When we are using APIs like forceDockToTop, then we only want to store the previous position
  6713. // if we weren't already docked. Otherwise, we still want to move the component, but keep its old
  6714. // restore values
  6715. const storePriorIfNone = (elem, box, viewport, state, decision) => {
  6716. state.getInitialPos().fold(() => storePrior(elem, box, viewport, state, decision), () => noop);
  6717. };
  6718. const revertToOriginal = (elem, box, state) => state.getInitialPos().bind((position) => {
  6719. var _a;
  6720. state.clearInitialPos();
  6721. switch (position.position) {
  6722. case 'static':
  6723. return Optional.some({
  6724. morph: 'static'
  6725. });
  6726. case 'absolute':
  6727. const offsetParent = getOffsetParent(elem).getOr(body());
  6728. const offsetBox = box$1(offsetParent);
  6729. // Adding the scrollDelta here may not be the right solution. The basic problem is that the
  6730. // rest of the code isn't considering whether its absolute or not, and where the offset parent
  6731. // is. In the situation where the offset parent is *inside* the scrolling environment, then
  6732. // we don't need to consider the scroll, and that's what getXYForRestoring does ... it removes
  6733. // the scroll. We don't need to consider the scroll because the sink is already affected by the
  6734. // scroll. However, when the sink IS the scroller, its position is not moved by scrolling. But the
  6735. // positions of everything inside it needs to consider the scroll. So we add the scroll value.
  6736. //
  6737. // This might also be a bit naive. It's possible that we need to check that the offsetParent
  6738. // is THE scroller, not just that it has a scroll value. For example, if the offset parent
  6739. // was the body, and the body had a scroll, this might give unexpected results. That's somewhat
  6740. // countered by the fact that if the offset parent is outside the scroller, then you don't really
  6741. // have a scrolling environment any more, because the offset parent isn't going to be impacted
  6742. // at all by the scroller
  6743. const scrollDelta = (_a = offsetParent.dom.scrollTop) !== null && _a !== void 0 ? _a : 0;
  6744. return Optional.some({
  6745. morph: 'absolute',
  6746. positionCss: NuPositionCss('absolute', get$h(position.style, 'left').map((_left) => box.x - offsetBox.x), get$h(position.style, 'top').map((_top) => box.y - offsetBox.y + scrollDelta), get$h(position.style, 'right').map((_right) => offsetBox.right - box.right), get$h(position.style, 'bottom').map((_bottom) => offsetBox.bottom - box.bottom))
  6747. });
  6748. default:
  6749. return Optional.none();
  6750. }
  6751. });
  6752. const tryMorphToOriginal = (elem, viewport, state) => getPrior(elem, viewport, state)
  6753. .filter(({ box }) => isVisibleForModes(state.getModes(), box, viewport))
  6754. .bind(({ box }) => revertToOriginal(elem, box, state));
  6755. const tryDecisionToFixedMorph = (decision) => {
  6756. switch (decision.location) {
  6757. case 'top': {
  6758. // We store our current position so we can revert to it once it's
  6759. // visible again.
  6760. return Optional.some({
  6761. morph: 'fixed',
  6762. positionCss: NuPositionCss('fixed', Optional.some(decision.leftX), Optional.some(decision.topY), Optional.none(), Optional.none())
  6763. });
  6764. }
  6765. case 'bottom': {
  6766. // We store our current position so we can revert to it once it's
  6767. // visible again.
  6768. return Optional.some({
  6769. morph: 'fixed',
  6770. positionCss: NuPositionCss('fixed', Optional.some(decision.leftX), Optional.none(), Optional.none(), Optional.some(decision.bottomY))
  6771. });
  6772. }
  6773. default:
  6774. return Optional.none();
  6775. }
  6776. };
  6777. const tryMorphToFixed = (elem, viewport, state) => {
  6778. const box = box$1(elem);
  6779. const winBox = win();
  6780. const decision = tryDockingPosition(state.getModes(), {
  6781. win: winBox,
  6782. box
  6783. }, viewport);
  6784. if (decision.location === 'top' || decision.location === 'bottom') {
  6785. // We are moving from undocked to docked, so store the previous location
  6786. // so that we can restore it when we switch out of docking (back to undocked)
  6787. storePrior(elem, box, viewport, state, decision);
  6788. return tryDecisionToFixedMorph(decision);
  6789. }
  6790. else {
  6791. return Optional.none();
  6792. }
  6793. };
  6794. const tryMorphToOriginalOrUpdateFixed = (elem, viewport, state) => {
  6795. // When a "docked" element is docked to the top of a scroll container (due to optScrollEnv in
  6796. // viewport), we need to reposition its fixed if the scroll container has itself moved its top position.
  6797. // This isn't required when the docking is to the top of the window, because the entire window cannot
  6798. // be scrolled up and down the page - it is the page.
  6799. //
  6800. // Imagine a situation where the toolbar has docked to the top of the scroll container, which is at
  6801. // y = 200. Now, when the user scrolls the page another 50px down the page, the top of the scroll
  6802. // container will now be 150px, but the "fixed" toolbar will still be at "200px". So this is a morph
  6803. // from "fixed" to "fixed", but with new coordinates. So if we can't morph to original from "fixed",
  6804. // we try to update our "fixed" position (if we have a scrolling environment in the viewport)
  6805. return tryMorphToOriginal(elem, viewport, state)
  6806. .orThunk(() => {
  6807. // Importantly, we don't update our stored position for the element before "docking", because
  6808. // this is a transition between "docked" and "docked", not "undocked" and "docked". We want to
  6809. // keep our undocked position in our store, not a docked position.
  6810. // So we don't change our stored position. We just improve our fixed.
  6811. return viewport.optScrollEnv
  6812. .bind((_) => getPrior(elem, viewport, state))
  6813. .bind(({ box, location }) => {
  6814. const winBox = win();
  6815. const leftX = getDockedLeftPosition({ win: winBox, box });
  6816. // Keep the same docking location
  6817. const decision = location === 'top'
  6818. ? forceTopPosition(winBox, leftX, viewport)
  6819. : forceBottomPosition(winBox, leftX, viewport);
  6820. return tryDecisionToFixedMorph(decision);
  6821. });
  6822. });
  6823. };
  6824. const tryMorph = (component, viewport, state) => {
  6825. const elem = component.element;
  6826. const isDocked = is$1(getRaw(elem, 'position'), 'fixed');
  6827. return isDocked
  6828. ? tryMorphToOriginalOrUpdateFixed(elem, viewport, state)
  6829. : tryMorphToFixed(elem, viewport, state);
  6830. };
  6831. // The difference between the "calculate" functions and the "try" functions is that the "try" functions
  6832. // will first consider whether there is a need to morph, whereas the "calculate" functions will just
  6833. // give you the morph details, bypassing the check to see if it's needed
  6834. const calculateMorphToOriginal = (component, viewport, state) => {
  6835. const elem = component.element;
  6836. return getPrior(elem, viewport, state)
  6837. .bind(({ box }) => revertToOriginal(elem, box, state));
  6838. };
  6839. const forceDockWith = (elem, viewport, state, getDecision) => {
  6840. const box = box$1(elem);
  6841. const winBox = win();
  6842. const leftX = getDockedLeftPosition({ win: winBox, box });
  6843. const decision = getDecision(winBox, leftX, viewport);
  6844. if (decision.location === 'bottom' || decision.location === 'top') {
  6845. // We only want to store the values if we aren't already docking. If we are already docking, then
  6846. // we just want to move the element, without updating where it started originally
  6847. storePriorIfNone(elem, box, viewport, state, decision);
  6848. return tryDecisionToFixedMorph(decision);
  6849. }
  6850. else {
  6851. return Optional.none();
  6852. }
  6853. };
  6854. const morphToStatic = (component, config, state) => {
  6855. state.setDocked(false);
  6856. each$1(['left', 'right', 'top', 'bottom', 'position'], (prop) => remove$6(component.element, prop));
  6857. config.onUndocked(component);
  6858. };
  6859. const morphToCoord = (component, config, state, position) => {
  6860. const isDocked = position.position === 'fixed';
  6861. state.setDocked(isDocked);
  6862. applyPositionCss(component.element, position);
  6863. const method = isDocked ? config.onDocked : config.onUndocked;
  6864. method(component);
  6865. };
  6866. const updateVisibility = (component, config, state, viewport, morphToDocked = false) => {
  6867. config.contextual.each((contextInfo) => {
  6868. // Make the dockable component disappear if the context is outside the viewport
  6869. contextInfo.lazyContext(component).each((box) => {
  6870. const isVisible = isPartiallyVisible(box, viewport.bounds);
  6871. if (isVisible !== state.isVisible()) {
  6872. state.setVisible(isVisible);
  6873. // If morphing to docked and the context isn't visible then immediately set
  6874. // the fadeout class and don't worry about transitioning, as the context
  6875. // would never have been in view while docked
  6876. if (morphToDocked && !isVisible) {
  6877. add$1(component.element, [contextInfo.fadeOutClass]);
  6878. contextInfo.onHide(component);
  6879. }
  6880. else {
  6881. const method = isVisible ? appear : disappear;
  6882. method(component, contextInfo);
  6883. }
  6884. }
  6885. });
  6886. });
  6887. };
  6888. const applyFixedMorph = (component, config, state, viewport, morph) => {
  6889. // This "updateVisibility" call is potentially duplicated with the
  6890. // call in refreshInternal for isDocked. We might want to consolidate them.
  6891. // The difference between them is the "morphToDocked" flag.
  6892. updateVisibility(component, config, state, viewport, true);
  6893. morphToCoord(component, config, state, morph.positionCss);
  6894. };
  6895. const applyMorph = (component, config, state, viewport, morph) => {
  6896. // Apply the morph result depending on its type
  6897. switch (morph.morph) {
  6898. case 'static': {
  6899. return morphToStatic(component, config, state);
  6900. }
  6901. case 'absolute': {
  6902. return morphToCoord(component, config, state, morph.positionCss);
  6903. }
  6904. case 'fixed': {
  6905. return applyFixedMorph(component, config, state, viewport, morph);
  6906. }
  6907. }
  6908. };
  6909. const refreshInternal = (component, config, state) => {
  6910. // Absolute coordinates (considers scroll)
  6911. const viewport = config.lazyViewport(component);
  6912. updateVisibility(component, config, state, viewport);
  6913. tryMorph(component, viewport, state).each((morph) => {
  6914. applyMorph(component, config, state, viewport, morph);
  6915. });
  6916. };
  6917. const resetInternal = (component, config, state) => {
  6918. // Morph back to the original position
  6919. const elem = component.element;
  6920. state.setDocked(false);
  6921. const viewport = config.lazyViewport(component);
  6922. calculateMorphToOriginal(component, viewport, state).each((staticOrAbsoluteMorph) => {
  6923. // This code is very similar to the "applyMorph" function above. The main difference
  6924. // is that it doesn't consider fixed position, because something that is docking
  6925. // can't currently start with fixed position
  6926. switch (staticOrAbsoluteMorph.morph) {
  6927. case 'static': {
  6928. morphToStatic(component, config, state);
  6929. break;
  6930. }
  6931. case 'absolute': {
  6932. morphToCoord(component, config, state, staticOrAbsoluteMorph.positionCss);
  6933. break;
  6934. }
  6935. }
  6936. });
  6937. // Remove contextual visibility classes
  6938. state.setVisible(true);
  6939. config.contextual.each((contextInfo) => {
  6940. remove$2(elem, [contextInfo.fadeInClass, contextInfo.fadeOutClass, contextInfo.transitionClass]);
  6941. contextInfo.onShow(component);
  6942. });
  6943. // Apply docking again to reset the position
  6944. refresh$4(component, config, state);
  6945. };
  6946. const refresh$4 = (component, config, state) => {
  6947. // Ensure the component is attached to the document/world, if not then do nothing as we can't
  6948. // check if the component should be docked or not when in a detached state
  6949. if (component.getSystem().isConnected()) {
  6950. refreshInternal(component, config, state);
  6951. }
  6952. };
  6953. const reset$1 = (component, config, state) => {
  6954. // If the component is not docked then there's no need to reset the state,
  6955. // so only reset when docked
  6956. if (state.isDocked()) {
  6957. resetInternal(component, config, state);
  6958. }
  6959. };
  6960. const forceDockWithDecision = (getDecision) => (component, config, state) => {
  6961. const viewport = config.lazyViewport(component);
  6962. const optMorph = forceDockWith(component.element, viewport, state, getDecision);
  6963. optMorph.each((morph) => {
  6964. // ASSUMPTION: This "applyFixedMorph" sets state.setDocked to true.
  6965. applyFixedMorph(component, config, state, viewport, morph);
  6966. });
  6967. };
  6968. const forceDockToTop = forceDockWithDecision(forceTopPosition);
  6969. const forceDockToBottom = forceDockWithDecision(forceBottomPosition);
  6970. const isDocked$2 = (component, config, state) => state.isDocked();
  6971. const setModes = (component, config, state, modes) => state.setModes(modes);
  6972. const getModes = (component, config, state) => state.getModes();
  6973. var DockingApis = /*#__PURE__*/Object.freeze({
  6974. __proto__: null,
  6975. refresh: refresh$4,
  6976. reset: reset$1,
  6977. isDocked: isDocked$2,
  6978. getModes: getModes,
  6979. setModes: setModes,
  6980. forceDockToTop: forceDockToTop,
  6981. forceDockToBottom: forceDockToBottom
  6982. });
  6983. const events$e = (dockInfo, dockState) => derive$2([
  6984. runOnSource(transitionend(), (component, simulatedEvent) => {
  6985. dockInfo.contextual.each((contextInfo) => {
  6986. if (has(component.element, contextInfo.transitionClass)) {
  6987. remove$2(component.element, [contextInfo.transitionClass, contextInfo.fadeInClass]);
  6988. const notify = dockState.isVisible() ? contextInfo.onShown : contextInfo.onHidden;
  6989. notify(component);
  6990. }
  6991. simulatedEvent.stop();
  6992. });
  6993. }),
  6994. run$1(windowScroll(), (component, _) => {
  6995. refresh$4(component, dockInfo, dockState);
  6996. }),
  6997. run$1(externalElementScroll(), (component, _) => {
  6998. refresh$4(component, dockInfo, dockState);
  6999. }),
  7000. run$1(windowResize(), (component, _) => {
  7001. reset$1(component, dockInfo, dockState);
  7002. })
  7003. ]);
  7004. var ActiveDocking = /*#__PURE__*/Object.freeze({
  7005. __proto__: null,
  7006. events: events$e
  7007. });
  7008. var DockingSchema = [
  7009. optionObjOf('contextual', [
  7010. requiredString('fadeInClass'),
  7011. requiredString('fadeOutClass'),
  7012. requiredString('transitionClass'),
  7013. requiredFunction('lazyContext'),
  7014. onHandler('onShow'),
  7015. onHandler('onShown'),
  7016. onHandler('onHide'),
  7017. onHandler('onHidden')
  7018. ]),
  7019. defaultedFunction('lazyViewport', () => ({
  7020. bounds: win(),
  7021. optScrollEnv: Optional.none()
  7022. })),
  7023. defaultedArrayOf('modes', ['top', 'bottom'], string),
  7024. onHandler('onDocked'),
  7025. onHandler('onUndocked')
  7026. ];
  7027. const init$d = (spec) => {
  7028. const docked = Cell(false);
  7029. const visible = Cell(true);
  7030. const initialBounds = value$2();
  7031. const modes = Cell(spec.modes);
  7032. const readState = () => `docked: ${docked.get()}, visible: ${visible.get()}, modes: ${modes.get().join(',')}`;
  7033. return nu$4({
  7034. isDocked: docked.get,
  7035. setDocked: docked.set,
  7036. getInitialPos: initialBounds.get,
  7037. setInitialPos: initialBounds.set,
  7038. clearInitialPos: initialBounds.clear,
  7039. isVisible: visible.get,
  7040. setVisible: visible.set,
  7041. getModes: modes.get,
  7042. setModes: modes.set,
  7043. readState
  7044. });
  7045. };
  7046. var DockingState = /*#__PURE__*/Object.freeze({
  7047. __proto__: null,
  7048. init: init$d
  7049. });
  7050. const Docking = create$3({
  7051. fields: DockingSchema,
  7052. name: 'docking',
  7053. active: ActiveDocking,
  7054. apis: DockingApis,
  7055. state: DockingState
  7056. });
  7057. /*
  7058. * origin: the position (without scroll) of the offset parent
  7059. * scroll: the scrolling position of the window
  7060. *
  7061. * fixed: the fixed coordinates to show for css
  7062. * offset: the absolute coordinates to show for css when inside an offset parent
  7063. * absolute: the absolute coordinates to show before considering the offset parent
  7064. */
  7065. const adt$4 = Adt.generate([
  7066. { offset: ['x', 'y'] },
  7067. { absolute: ['x', 'y'] },
  7068. { fixed: ['x', 'y'] }
  7069. ]);
  7070. const subtract = (change) => (point) => point.translate(-change.left, -change.top);
  7071. const add = (change) => (point) => point.translate(change.left, change.top);
  7072. const transform = (changes) => (x, y) => foldl(changes, (rest, f) => f(rest), SugarPosition(x, y));
  7073. const asFixed = (coord, scroll, origin) => coord.fold(
  7074. // offset to fixed
  7075. transform([add(origin), subtract(scroll)]),
  7076. // absolute to fixed
  7077. transform([subtract(scroll)]),
  7078. // fixed to fixed
  7079. transform([]));
  7080. const asAbsolute = (coord, scroll, origin) => coord.fold(
  7081. // offset to absolute
  7082. transform([add(origin)]),
  7083. // absolute to absolute
  7084. transform([]),
  7085. // fixed to absolute
  7086. transform([add(scroll)]));
  7087. const asOffset = (coord, scroll, origin) => coord.fold(
  7088. // offset to offset
  7089. transform([]),
  7090. // absolute to offset
  7091. transform([subtract(origin)]),
  7092. // fixed to offset
  7093. transform([add(scroll), subtract(origin)]));
  7094. const withinRange = (coord1, coord2, xRange, yRange, scroll, origin) => {
  7095. const a1 = asAbsolute(coord1, scroll, origin);
  7096. const a2 = asAbsolute(coord2, scroll, origin);
  7097. // console.log(`a1.left: ${a1.left}, a2.left: ${a2.left}, leftDelta: ${a1.left - a2.left}, xRange: ${xRange}, lD <= xRange: ${Math.abs(a1.left - a2.left) <= xRange}`);
  7098. // console.log(`a1.top: ${a1.top}, a2.top: ${a2.top}, topDelta: ${a1.top - a2.top}, yRange: ${yRange}, lD <= xRange: ${Math.abs(a1.top - a2.top) <= yRange}`);
  7099. return Math.abs(a1.left - a2.left) <= xRange &&
  7100. Math.abs(a1.top - a2.top) <= yRange;
  7101. };
  7102. const getDeltas = (coord1, coord2, xRange, yRange, scroll, origin) => {
  7103. const a1 = asAbsolute(coord1, scroll, origin);
  7104. const a2 = asAbsolute(coord2, scroll, origin);
  7105. const left = Math.abs(a1.left - a2.left);
  7106. const top = Math.abs(a1.top - a2.top);
  7107. return SugarPosition(left, top);
  7108. };
  7109. const toStyles = (coord, scroll, origin) => {
  7110. const stylesOpt = coord.fold((x, y) => ({ position: Optional.some('absolute'), left: Optional.some(x + 'px'), top: Optional.some(y + 'px') }), // offset
  7111. (x, y) => ({ position: Optional.some('absolute'), left: Optional.some((x - origin.left) + 'px'), top: Optional.some((y - origin.top) + 'px') }), // absolute
  7112. (x, y) => ({ position: Optional.some('fixed'), left: Optional.some(x + 'px'), top: Optional.some(y + 'px') }) // fixed
  7113. );
  7114. return { right: Optional.none(), bottom: Optional.none(), ...stylesOpt };
  7115. };
  7116. const translate$2 = (coord, deltaX, deltaY) => coord.fold((x, y) => offset(x + deltaX, y + deltaY), (x, y) => absolute$1(x + deltaX, y + deltaY), (x, y) => fixed$1(x + deltaX, y + deltaY));
  7117. const absorb = (partialCoord, originalCoord, scroll, origin) => {
  7118. const absorbOne = (stencil, nu) => (optX, optY) => {
  7119. const original = stencil(originalCoord, scroll, origin);
  7120. return nu(optX.getOr(original.left), optY.getOr(original.top));
  7121. };
  7122. return partialCoord.fold(absorbOne(asOffset, offset), absorbOne(asAbsolute, absolute$1), absorbOne(asFixed, fixed$1));
  7123. };
  7124. const offset = adt$4.offset;
  7125. const absolute$1 = adt$4.absolute;
  7126. const fixed$1 = adt$4.fixed;
  7127. const parseAttrToInt = (element, name) => {
  7128. const value = get$g(element, name);
  7129. return isUndefined(value) ? NaN : parseInt(value, 10);
  7130. };
  7131. // NOTE: Moved from ego with some parameterisation
  7132. const get$3 = (component, snapsInfo) => {
  7133. const element = component.element;
  7134. const x = parseAttrToInt(element, snapsInfo.leftAttr);
  7135. const y = parseAttrToInt(element, snapsInfo.topAttr);
  7136. return isNaN(x) || isNaN(y) ? Optional.none() : Optional.some(SugarPosition(x, y));
  7137. };
  7138. const set$1 = (component, snapsInfo, pt) => {
  7139. const element = component.element;
  7140. set$9(element, snapsInfo.leftAttr, pt.left + 'px');
  7141. set$9(element, snapsInfo.topAttr, pt.top + 'px');
  7142. };
  7143. const clear = (component, snapsInfo) => {
  7144. const element = component.element;
  7145. remove$8(element, snapsInfo.leftAttr);
  7146. remove$8(element, snapsInfo.topAttr);
  7147. };
  7148. // Types of coordinates
  7149. // SugarLocation: This is the position on the screen including scroll.
  7150. // Absolute: This is the css setting that would be applied. Therefore, it subtracts
  7151. // the origin of the relative offsetParent.
  7152. // Fixed: This is the fixed position.
  7153. /*
  7154. So in attempt to make this more understandable, let's use offset, absolute, and fixed.
  7155. and try and model individual combinators
  7156. */
  7157. /*
  7158. Relationships:
  7159. - location -> absolute: should just need to subtract the position of the offset parent (origin)
  7160. - location -> fixed: subtract the scrolling
  7161. - absolute -> fixed: add the origin, and subtract the scrolling
  7162. - absolute -> location: add the origin
  7163. - fixed -> absolute: add the scrolling, remove the origin
  7164. - fixed -> location: add the scrolling
  7165. /*
  7166. * When the user is dragging around the element, and it snaps into place, it is important
  7167. * for the next movement to be from its pre-snapped location, rather than the snapped location.
  7168. * This is because if it is from the snapped location the next delta movement may not actually
  7169. * be high enough to get it out of the snap area, and hence, it will just snap again (and again).
  7170. */
  7171. // This identifies the position of the draggable element as either its current position, or the position
  7172. // that we put on it before we snapped it into place (before dropping). Once it's dropped, the presnap
  7173. // position will go away. It is used to avoid the situation where you can't escape the snap unless you
  7174. // move the mouse really quickly :)
  7175. const getCoords = (component, snapInfo, coord, delta) => get$3(component, snapInfo).fold(() => coord, (fixed) =>
  7176. // We have a pre-snap position, so we have to apply the delta ourselves
  7177. fixed$1(fixed.left + delta.left, fixed.top + delta.top));
  7178. const moveOrSnap = (component, snapInfo, coord, delta, scroll, origin) => {
  7179. const newCoord = getCoords(component, snapInfo, coord, delta);
  7180. const snap = snapInfo.mustSnap ? findClosestSnap(component, snapInfo, newCoord, scroll, origin) :
  7181. findSnap(component, snapInfo, newCoord, scroll, origin);
  7182. const fixedCoord = asFixed(newCoord, scroll, origin);
  7183. set$1(component, snapInfo, fixedCoord);
  7184. return snap.fold(() => ({
  7185. coord: fixed$1(fixedCoord.left, fixedCoord.top),
  7186. extra: Optional.none()
  7187. })
  7188. // No snap.
  7189. // var newfixed = graph.boundToFixed(theatre, element, loc.left, loc.top, fixed.left, fixed.top, height);
  7190. // presnaps.set(element, 'fixed', newfixed.left, newfixed.top);
  7191. // return { position: 'fixed', left: newfixed.left + 'px', top: newfixed.top + 'px' };
  7192. , (spanned) => ({
  7193. coord: spanned.output,
  7194. extra: spanned.extra
  7195. }));
  7196. };
  7197. const stopDrag = (component, snapInfo) => {
  7198. clear(component, snapInfo);
  7199. };
  7200. const findMatchingSnap = (snaps, newCoord, scroll, origin) => findMap(snaps, (snap) => {
  7201. const sensor = snap.sensor;
  7202. const inRange = withinRange(newCoord, sensor, snap.range.left, snap.range.top, scroll, origin);
  7203. return inRange ? Optional.some({
  7204. output: absorb(snap.output, newCoord, scroll, origin),
  7205. extra: snap.extra
  7206. }) : Optional.none();
  7207. });
  7208. const findClosestSnap = (component, snapInfo, newCoord, scroll, origin) => {
  7209. // You need to pass in the absX and absY so that they can be used for things which only care about snapping one axis and keeping the other one.
  7210. const snaps = snapInfo.getSnapPoints(component);
  7211. const matchSnap = findMatchingSnap(snaps, newCoord, scroll, origin);
  7212. return matchSnap.orThunk(() => {
  7213. const bestSnap = foldl(snaps, (acc, snap) => {
  7214. const sensor = snap.sensor;
  7215. const deltas = getDeltas(newCoord, sensor, snap.range.left, snap.range.top, scroll, origin);
  7216. return acc.deltas.fold(() => ({
  7217. deltas: Optional.some(deltas),
  7218. snap: Optional.some(snap)
  7219. }), (bestDeltas) => {
  7220. const currAvg = (deltas.left + deltas.top) / 2;
  7221. const bestAvg = (bestDeltas.left + bestDeltas.top) / 2;
  7222. if (currAvg <= bestAvg) {
  7223. return {
  7224. deltas: Optional.some(deltas),
  7225. snap: Optional.some(snap)
  7226. };
  7227. }
  7228. else {
  7229. return acc;
  7230. }
  7231. });
  7232. }, {
  7233. deltas: Optional.none(),
  7234. snap: Optional.none()
  7235. });
  7236. return bestSnap.snap.map((snap) => ({
  7237. output: absorb(snap.output, newCoord, scroll, origin),
  7238. extra: snap.extra
  7239. }));
  7240. });
  7241. };
  7242. // x: the absolute position.left of the draggable element
  7243. // y: the absolute position.top of the draggable element
  7244. // deltaX: the amount the mouse has moved horizontally
  7245. // deltaY: the amount the mouse has moved vertically
  7246. const findSnap = (component, snapInfo, newCoord, scroll, origin) => {
  7247. // You need to pass in the absX and absY so that they can be used for things which only care about snapping one axis and keeping the other one.
  7248. const snaps = snapInfo.getSnapPoints(component);
  7249. // HERE
  7250. return findMatchingSnap(snaps, newCoord, scroll, origin);
  7251. };
  7252. const snapTo$1 = (snap, scroll, origin) => ({
  7253. // TODO: This looks to be incorrect and needs fixing as DragCoord definitely needs a number
  7254. // based drag coord for the second argument here, so this is probably a bug.
  7255. coord: absorb(snap.output, snap.output, scroll, origin),
  7256. extra: snap.extra
  7257. });
  7258. const snapTo = (component, dragConfig, _state, snap) => {
  7259. const target = dragConfig.getTarget(component.element);
  7260. if (dragConfig.repositionTarget) {
  7261. const doc = owner$4(component.element);
  7262. const scroll = get$b(doc);
  7263. const origin = getOrigin(target);
  7264. const snapPin = snapTo$1(snap, scroll, origin);
  7265. const styles = toStyles(snapPin.coord, scroll, origin);
  7266. setOptions(target, styles);
  7267. }
  7268. };
  7269. var DraggingApis = /*#__PURE__*/Object.freeze({
  7270. __proto__: null,
  7271. snapTo: snapTo
  7272. });
  7273. const field = (name, forbidden) => defaultedObjOf(name, {}, map$2(forbidden, (f) => forbid(f.name(), 'Cannot configure ' + f.name() + ' for ' + name)).concat([
  7274. customField('dump', identity)
  7275. ]));
  7276. const get$2 = (data) => data.dump;
  7277. const augment = (data, original) => ({
  7278. ...derive$1(original),
  7279. ...data.dump
  7280. });
  7281. // Is this used?
  7282. const SketchBehaviours = {
  7283. field,
  7284. augment,
  7285. get: get$2
  7286. };
  7287. const base = (partSchemas, partUidsSchemas) => {
  7288. const ps = partSchemas.length > 0 ? [
  7289. requiredObjOf('parts', partSchemas)
  7290. ] : [];
  7291. return ps.concat([
  7292. required$1('uid'),
  7293. defaulted('dom', {}), // Maybe get rid of.
  7294. defaulted('components', []),
  7295. snapshot('originalSpec'),
  7296. defaulted('debug.sketcher', {})
  7297. ]).concat(partUidsSchemas);
  7298. };
  7299. const asRawOrDie = (label, schema, spec, partSchemas, partUidsSchemas) => {
  7300. const baseS = base(partSchemas, partUidsSchemas);
  7301. return asRawOrDie$1(label + ' [SpecSchema]', objOfOnly(baseS.concat(schema)), spec);
  7302. };
  7303. const single$1 = (owner, schema, factory, spec) => {
  7304. const specWithUid = supplyUid(spec);
  7305. const detail = asRawOrDie(owner, schema, specWithUid, [], []);
  7306. return factory(detail, specWithUid);
  7307. };
  7308. const composite$1 = (owner, schema, partTypes, factory, spec) => {
  7309. const specWithUid = supplyUid(spec);
  7310. // Identify any information required for external parts
  7311. const partSchemas = schemas(partTypes);
  7312. // Generate partUids for all parts (external and otherwise)
  7313. const partUidsSchema = defaultUidsSchema(partTypes);
  7314. const detail = asRawOrDie(owner, schema, specWithUid, partSchemas, [partUidsSchema]);
  7315. // Create (internals, externals) substitutions
  7316. const subs = substitutes(owner, detail, partTypes);
  7317. // Work out the components by substituting internals
  7318. const components = components$1(owner, detail, subs.internals());
  7319. return factory(detail, components, specWithUid, subs.externals());
  7320. };
  7321. const hasUid = (spec) => has$2(spec, 'uid');
  7322. const supplyUid = (spec) => {
  7323. return hasUid(spec) ? spec : {
  7324. ...spec,
  7325. uid: generate$4('uid')
  7326. };
  7327. };
  7328. const isSketchSpec = (spec) => {
  7329. return spec.uid !== undefined;
  7330. };
  7331. const singleSchema = objOfOnly([
  7332. required$1('name'),
  7333. required$1('factory'),
  7334. required$1('configFields'),
  7335. defaulted('apis', {}),
  7336. defaulted('extraApis', {})
  7337. ]);
  7338. const compositeSchema = objOfOnly([
  7339. required$1('name'),
  7340. required$1('factory'),
  7341. required$1('configFields'),
  7342. required$1('partFields'),
  7343. defaulted('apis', {}),
  7344. defaulted('extraApis', {})
  7345. ]);
  7346. const single = (rawConfig) => {
  7347. const config = asRawOrDie$1('Sketcher for ' + rawConfig.name, singleSchema, rawConfig);
  7348. const sketch = (spec) => single$1(config.name, config.configFields, config.factory, spec);
  7349. const apis = map$1(config.apis, makeApi);
  7350. const extraApis = map$1(config.extraApis, (f, k) => markAsExtraApi(f, k));
  7351. return {
  7352. name: config.name,
  7353. configFields: config.configFields,
  7354. sketch,
  7355. ...apis,
  7356. ...extraApis
  7357. };
  7358. };
  7359. const composite = (rawConfig) => {
  7360. const config = asRawOrDie$1('Sketcher for ' + rawConfig.name, compositeSchema, rawConfig);
  7361. const sketch = (spec) => composite$1(config.name, config.configFields, config.partFields, config.factory, spec);
  7362. // These are constructors that will store their configuration.
  7363. const parts = generate$5(config.name, config.partFields);
  7364. const apis = map$1(config.apis, makeApi);
  7365. const extraApis = map$1(config.extraApis, (f, k) => markAsExtraApi(f, k));
  7366. return {
  7367. name: config.name,
  7368. partFields: config.partFields,
  7369. configFields: config.configFields,
  7370. sketch,
  7371. parts,
  7372. ...apis,
  7373. ...extraApis
  7374. };
  7375. };
  7376. const factory$n = (detail) => {
  7377. const { attributes, ...domWithoutAttributes } = detail.dom;
  7378. return {
  7379. uid: detail.uid,
  7380. dom: {
  7381. tag: 'div',
  7382. attributes: {
  7383. role: 'presentation',
  7384. ...attributes
  7385. },
  7386. ...domWithoutAttributes
  7387. },
  7388. components: detail.components,
  7389. behaviours: get$2(detail.containerBehaviours),
  7390. events: detail.events,
  7391. domModification: detail.domModification,
  7392. eventOrder: detail.eventOrder
  7393. };
  7394. };
  7395. const Container = single({
  7396. name: 'Container',
  7397. factory: factory$n,
  7398. configFields: [
  7399. defaulted('components', []),
  7400. field('containerBehaviours', []),
  7401. // TODO: Deprecate
  7402. defaulted('events', {}),
  7403. defaulted('domModification', {}),
  7404. defaulted('eventOrder', {})
  7405. ]
  7406. });
  7407. const initialAttribute = 'data-initial-z-index';
  7408. // We have to alter the z index of the alloy root of the blocker so that
  7409. // it can have a z-index high enough to act as the "blocker". Just before
  7410. // discarding it, we need to reset those z-indices back to what they
  7411. // were. ASSUMPTION: the blocker has been added as a direct child of the root
  7412. const resetZIndex = (blocker) => {
  7413. parent(blocker.element).filter(isElement$1).each((root) => {
  7414. getOpt(root, initialAttribute).fold(() => remove$6(root, 'z-index'), (zIndex) => set$7(root, 'z-index', zIndex));
  7415. remove$8(root, initialAttribute);
  7416. });
  7417. };
  7418. const changeZIndex = (blocker) => {
  7419. parent(blocker.element).filter(isElement$1).each((root) => {
  7420. getRaw(root, 'z-index').each((zindex) => {
  7421. set$9(root, initialAttribute, zindex);
  7422. });
  7423. // Used to be a really high number, but it probably just has
  7424. // to match the blocker
  7425. set$7(root, 'z-index', get$e(blocker.element, 'z-index'));
  7426. });
  7427. };
  7428. const instigate = (anyComponent, blocker) => {
  7429. anyComponent.getSystem().addToGui(blocker);
  7430. changeZIndex(blocker);
  7431. };
  7432. const discard = (blocker) => {
  7433. resetZIndex(blocker);
  7434. blocker.getSystem().removeFromGui(blocker);
  7435. };
  7436. const createComponent = (component, blockerClass, blockerEvents) => component.getSystem().build(Container.sketch({
  7437. dom: {
  7438. // Probably consider doing with classes?
  7439. styles: {
  7440. 'left': '0px',
  7441. 'top': '0px',
  7442. 'width': '100%',
  7443. 'height': '100%',
  7444. 'position': 'fixed',
  7445. 'z-index': '1000000000000000'
  7446. },
  7447. classes: [blockerClass]
  7448. },
  7449. events: blockerEvents
  7450. }));
  7451. var SnapSchema = optionObjOf('snaps', [
  7452. required$1('getSnapPoints'),
  7453. onHandler('onSensor'),
  7454. required$1('leftAttr'),
  7455. required$1('topAttr'),
  7456. defaulted('lazyViewport', win),
  7457. defaulted('mustSnap', false)
  7458. ]);
  7459. const schema$r = [
  7460. // Is this used?
  7461. defaulted('useFixed', never),
  7462. required$1('blockerClass'),
  7463. defaulted('getTarget', identity),
  7464. defaulted('onDrag', noop),
  7465. defaulted('repositionTarget', true),
  7466. defaulted('onDrop', noop),
  7467. defaultedFunction('getBounds', win),
  7468. SnapSchema
  7469. ];
  7470. const getCurrentCoord = (target) => lift3(getRaw(target, 'left'), getRaw(target, 'top'), getRaw(target, 'position'), (left, top, position) => {
  7471. const nu = position === 'fixed' ? fixed$1 : offset;
  7472. return nu(parseInt(left, 10), parseInt(top, 10));
  7473. }).getOrThunk(() => {
  7474. const location = absolute$3(target);
  7475. return absolute$1(location.left, location.top);
  7476. });
  7477. const clampCoords = (component, coords, scroll, origin, startData) => {
  7478. const bounds = startData.bounds;
  7479. const absoluteCoord = asAbsolute(coords, scroll, origin);
  7480. const newX = clamp(absoluteCoord.left, bounds.x, bounds.x + bounds.width - startData.width);
  7481. const newY = clamp(absoluteCoord.top, bounds.y, bounds.y + bounds.height - startData.height);
  7482. const newCoords = absolute$1(newX, newY);
  7483. // Translate the absolute coord back into the previous type
  7484. return coords.fold(
  7485. // offset
  7486. () => {
  7487. const offset$1 = asOffset(newCoords, scroll, origin);
  7488. return offset(offset$1.left, offset$1.top);
  7489. },
  7490. // absolute
  7491. constant$1(newCoords),
  7492. // fixed
  7493. () => {
  7494. const fixed = asFixed(newCoords, scroll, origin);
  7495. return fixed$1(fixed.left, fixed.top);
  7496. });
  7497. };
  7498. const calcNewCoord = (component, optSnaps, currentCoord, scroll, origin, delta, startData) => {
  7499. const newCoord = optSnaps.fold(() => {
  7500. // When not docking, use fixed coordinates.
  7501. const translated = translate$2(currentCoord, delta.left, delta.top);
  7502. const fixedCoord = asFixed(translated, scroll, origin);
  7503. return fixed$1(fixedCoord.left, fixedCoord.top);
  7504. }, (snapInfo) => {
  7505. const snapping = moveOrSnap(component, snapInfo, currentCoord, delta, scroll, origin);
  7506. snapping.extra.each((extra) => {
  7507. snapInfo.onSensor(component, extra);
  7508. });
  7509. return snapping.coord;
  7510. });
  7511. // Clamp the coords so that they are within the bounds
  7512. return clampCoords(component, newCoord, scroll, origin, startData);
  7513. };
  7514. const dragBy = (component, dragConfig, startData, delta) => {
  7515. const target = dragConfig.getTarget(component.element);
  7516. if (dragConfig.repositionTarget) {
  7517. const doc = owner$4(component.element);
  7518. const scroll = get$b(doc);
  7519. const origin = getOrigin(target);
  7520. const currentCoord = getCurrentCoord(target);
  7521. const newCoord = calcNewCoord(component, dragConfig.snaps, currentCoord, scroll, origin, delta, startData);
  7522. const styles = toStyles(newCoord, scroll, origin);
  7523. setOptions(target, styles);
  7524. }
  7525. // NOTE: On drag just goes with the original delta. It does not know about snapping.
  7526. dragConfig.onDrag(component, target, delta);
  7527. };
  7528. const calcStartData = (dragConfig, comp) => ({
  7529. bounds: dragConfig.getBounds(),
  7530. height: getOuter$1(comp.element),
  7531. width: getOuter(comp.element)
  7532. });
  7533. const move = (component, dragConfig, dragState, dragMode, event) => {
  7534. const delta = dragState.update(dragMode, event);
  7535. const dragStartData = dragState.getStartData().getOrThunk(() => calcStartData(dragConfig, component));
  7536. delta.each((dlt) => {
  7537. dragBy(component, dragConfig, dragStartData, dlt);
  7538. });
  7539. };
  7540. const stop = (component, blocker, dragConfig, dragState) => {
  7541. blocker.each(discard);
  7542. dragConfig.snaps.each((snapInfo) => {
  7543. stopDrag(component, snapInfo);
  7544. });
  7545. const target = dragConfig.getTarget(component.element);
  7546. dragState.reset();
  7547. dragConfig.onDrop(component, target);
  7548. };
  7549. const handlers = (events) => (dragConfig, dragState) => {
  7550. const updateStartState = (comp) => {
  7551. dragState.setStartData(calcStartData(dragConfig, comp));
  7552. };
  7553. return derive$2([
  7554. run$1(windowScroll(), (comp) => {
  7555. // Only update if we have some start data
  7556. dragState.getStartData().each(() => updateStartState(comp));
  7557. }),
  7558. ...events(dragConfig, dragState, updateStartState)
  7559. ]);
  7560. };
  7561. const init$c = (dragApi) => derive$2([
  7562. // When the user clicks on the blocker, something has probably gone slightly
  7563. // wrong, so we'll just drop for safety. The blocker should really only
  7564. // be there when the mouse is already down and not released, so a 'click'
  7565. run$1(mousedown(), dragApi.forceDrop),
  7566. // When the user releases the mouse on the blocker, that is a drop
  7567. run$1(mouseup(), dragApi.drop),
  7568. // As the user moves the mouse around (while pressed down), we move the
  7569. // component around
  7570. run$1(mousemove(), (comp, simulatedEvent) => {
  7571. dragApi.move(simulatedEvent.event);
  7572. }),
  7573. // When the use moves outside the range, schedule a block to occur but
  7574. // give it a chance to be cancelled.
  7575. run$1(mouseout(), dragApi.delayDrop)
  7576. ]);
  7577. const getData$1 = (event) => Optional.from(SugarPosition(event.x, event.y));
  7578. // When dragging with the mouse, the delta is simply the difference
  7579. // between the two position (previous/old and next/nu)
  7580. const getDelta$1 = (old, nu) => SugarPosition(nu.left - old.left, nu.top - old.top);
  7581. var MouseData = /*#__PURE__*/Object.freeze({
  7582. __proto__: null,
  7583. getData: getData$1,
  7584. getDelta: getDelta$1
  7585. });
  7586. const events$d = (dragConfig, dragState, updateStartState) => [
  7587. run$1(mousedown(), (component, simulatedEvent) => {
  7588. const raw = simulatedEvent.event.raw;
  7589. if (raw.button !== 0) {
  7590. return;
  7591. }
  7592. simulatedEvent.stop();
  7593. const stop$1 = () => stop(component, Optional.some(blocker), dragConfig, dragState);
  7594. // If the user has moved something outside the area, and has not come back within
  7595. // 200 ms, then drop
  7596. const delayDrop = DelayedFunction(stop$1, 200);
  7597. const dragApi = {
  7598. drop: stop$1,
  7599. delayDrop: delayDrop.schedule,
  7600. forceDrop: stop$1,
  7601. move: (event) => {
  7602. // Stop any pending drops caused by mouseout
  7603. delayDrop.cancel();
  7604. move(component, dragConfig, dragState, MouseData, event);
  7605. }
  7606. };
  7607. const blocker = createComponent(component, dragConfig.blockerClass, init$c(dragApi));
  7608. const start = () => {
  7609. updateStartState(component);
  7610. instigate(component, blocker);
  7611. };
  7612. start();
  7613. })
  7614. ];
  7615. const schema$q = [
  7616. ...schema$r,
  7617. output$1('dragger', {
  7618. handlers: handlers(events$d)
  7619. })
  7620. ];
  7621. const init$b = (dragApi) => derive$2([
  7622. // When the user taps on the blocker, something has probably gone slightly
  7623. // wrong, so we'll just drop for safety. The blocker should really only
  7624. // be there when their finger is already down and not released, so a 'tap'
  7625. run$1(touchstart(), dragApi.forceDrop),
  7626. // When the user releases their finger on the blocker, that is a drop
  7627. run$1(touchend(), dragApi.drop),
  7628. run$1(touchcancel(), dragApi.drop),
  7629. // As the user moves their finger around (while pressed down), we move the
  7630. // component around
  7631. run$1(touchmove(), (comp, simulatedEvent) => {
  7632. dragApi.move(simulatedEvent.event);
  7633. })
  7634. ]);
  7635. const getDataFrom = (touches) => {
  7636. const touch = touches[0];
  7637. return Optional.some(SugarPosition(touch.clientX, touch.clientY));
  7638. };
  7639. const getData = (event) => {
  7640. const raw = event.raw;
  7641. const touches = raw.touches;
  7642. return touches.length === 1 ? getDataFrom(touches) : Optional.none();
  7643. };
  7644. // When dragging the touch, the delta is simply the difference
  7645. // between the two touch positions (previous/old and next/nu)
  7646. const getDelta = (old, nu) => SugarPosition(nu.left - old.left, nu.top - old.top);
  7647. var TouchData = /*#__PURE__*/Object.freeze({
  7648. __proto__: null,
  7649. getData: getData,
  7650. getDelta: getDelta
  7651. });
  7652. const events$c = (dragConfig, dragState, updateStartState) => {
  7653. const blockerSingleton = value$2();
  7654. const stopBlocking = (component) => {
  7655. stop(component, blockerSingleton.get(), dragConfig, dragState);
  7656. blockerSingleton.clear();
  7657. };
  7658. // Android fires events on the component at all times, while iOS initially fires on the component
  7659. // but once moved off the component then fires on the element behind. As such we need to use
  7660. // a blocker and then listen to both touchmove/touchend on both the component and blocker.
  7661. return [
  7662. run$1(touchstart(), (component, simulatedEvent) => {
  7663. simulatedEvent.stop();
  7664. const stop = () => stopBlocking(component);
  7665. const dragApi = {
  7666. drop: stop,
  7667. // delayDrop is not used by touch
  7668. delayDrop: noop,
  7669. forceDrop: stop,
  7670. move: (event) => {
  7671. move(component, dragConfig, dragState, TouchData, event);
  7672. }
  7673. };
  7674. const blocker = createComponent(component, dragConfig.blockerClass, init$b(dragApi));
  7675. blockerSingleton.set(blocker);
  7676. const start = () => {
  7677. updateStartState(component);
  7678. instigate(component, blocker);
  7679. };
  7680. start();
  7681. }),
  7682. run$1(touchmove(), (component, simulatedEvent) => {
  7683. simulatedEvent.stop();
  7684. move(component, dragConfig, dragState, TouchData, simulatedEvent.event);
  7685. }),
  7686. run$1(touchend(), (component, simulatedEvent) => {
  7687. simulatedEvent.stop();
  7688. stopBlocking(component);
  7689. }),
  7690. run$1(touchcancel(), stopBlocking)
  7691. ];
  7692. };
  7693. const schema$p = [
  7694. ...schema$r,
  7695. output$1('dragger', {
  7696. handlers: handlers(events$c)
  7697. })
  7698. ];
  7699. const events$b = (dragConfig, dragState, updateStartState) => [
  7700. ...events$d(dragConfig, dragState, updateStartState),
  7701. ...events$c(dragConfig, dragState, updateStartState)
  7702. ];
  7703. const schema$o = [
  7704. ...schema$r,
  7705. output$1('dragger', {
  7706. handlers: handlers(events$b)
  7707. })
  7708. ];
  7709. const mouse = schema$q;
  7710. const touch = schema$p;
  7711. const mouseOrTouch = schema$o;
  7712. var DraggingBranches = /*#__PURE__*/Object.freeze({
  7713. __proto__: null,
  7714. mouse: mouse,
  7715. touch: touch,
  7716. mouseOrTouch: mouseOrTouch
  7717. });
  7718. // NOTE: mode refers to the way that information is retrieved from
  7719. // the user interaction. It can be things like MouseData, TouchData etc.
  7720. const init$a = () => {
  7721. // Dragging operates on the difference between the previous user
  7722. // interaction and the next user interaction. Therefore, we store
  7723. // the previous interaction so that we can compare it.
  7724. let previous = Optional.none();
  7725. // Dragging requires calculating the bounds, so we store that data initially
  7726. // to reduce the amount of computation each mouse movement
  7727. let startData = Optional.none();
  7728. const reset = () => {
  7729. previous = Optional.none();
  7730. startData = Optional.none();
  7731. };
  7732. // Return position delta between previous position and nu position,
  7733. // or None if this is the first. Set the previous position to nu.
  7734. const calculateDelta = (mode, nu) => {
  7735. const result = previous.map((old) => mode.getDelta(old, nu));
  7736. previous = Optional.some(nu);
  7737. return result;
  7738. };
  7739. // NOTE: This dragEvent is the DOM touch event or mouse event
  7740. const update = (mode, dragEvent) => mode.getData(dragEvent).bind((nuData) => calculateDelta(mode, nuData));
  7741. const setStartData = (data) => {
  7742. startData = Optional.some(data);
  7743. };
  7744. const getStartData = () => startData;
  7745. const readState = constant$1({});
  7746. return nu$4({
  7747. readState,
  7748. reset,
  7749. update,
  7750. getStartData,
  7751. setStartData
  7752. });
  7753. };
  7754. var DragState = /*#__PURE__*/Object.freeze({
  7755. __proto__: null,
  7756. init: init$a
  7757. });
  7758. const Dragging = createModes({
  7759. branchKey: 'mode',
  7760. branches: DraggingBranches,
  7761. name: 'dragging',
  7762. active: {
  7763. events: (dragConfig, dragState) => {
  7764. const dragger = dragConfig.dragger;
  7765. return dragger.handlers(dragConfig, dragState);
  7766. }
  7767. },
  7768. extra: {
  7769. // Extra. Does not need component as input.
  7770. snap: (sConfig) => ({
  7771. sensor: sConfig.sensor,
  7772. range: sConfig.range,
  7773. output: sConfig.output,
  7774. extra: Optional.from(sConfig.extra)
  7775. })
  7776. },
  7777. state: DragState,
  7778. apis: DraggingApis
  7779. });
  7780. const ariaElements = [
  7781. 'input',
  7782. 'textarea'
  7783. ];
  7784. const isAriaElement = (elem) => {
  7785. const name = name$3(elem);
  7786. return contains$2(ariaElements, name);
  7787. };
  7788. const markValid = (component, invalidConfig) => {
  7789. const elem = invalidConfig.getRoot(component).getOr(component.element);
  7790. remove$3(elem, invalidConfig.invalidClass);
  7791. invalidConfig.notify.each((notifyInfo) => {
  7792. if (isAriaElement(component.element)) {
  7793. set$9(component.element, 'aria-invalid', false);
  7794. }
  7795. notifyInfo.getContainer(component).each((container) => {
  7796. set$8(container, notifyInfo.validHtml);
  7797. });
  7798. notifyInfo.onValid(component);
  7799. });
  7800. };
  7801. const markInvalid = (component, invalidConfig, invalidState, text) => {
  7802. const elem = invalidConfig.getRoot(component).getOr(component.element);
  7803. add$2(elem, invalidConfig.invalidClass);
  7804. invalidConfig.notify.each((notifyInfo) => {
  7805. if (isAriaElement(component.element)) {
  7806. set$9(component.element, 'aria-invalid', true);
  7807. }
  7808. notifyInfo.getContainer(component).each((container) => {
  7809. // TODO: Should we just use Text here, not HTML?
  7810. set$8(container, text);
  7811. });
  7812. notifyInfo.onInvalid(component, text);
  7813. });
  7814. };
  7815. const query = (component, invalidConfig, _invalidState) => invalidConfig.validator.fold(() => Future.pure(Result.value(true)), (validatorInfo) => validatorInfo.validate(component));
  7816. const run = (component, invalidConfig, invalidState) => {
  7817. invalidConfig.notify.each((notifyInfo) => {
  7818. notifyInfo.onValidate(component);
  7819. });
  7820. return query(component, invalidConfig).map((valid) => {
  7821. if (component.getSystem().isConnected()) {
  7822. return valid.fold((err) => {
  7823. markInvalid(component, invalidConfig, invalidState, err);
  7824. return Result.error(err);
  7825. }, (v) => {
  7826. markValid(component, invalidConfig);
  7827. return Result.value(v);
  7828. });
  7829. }
  7830. else {
  7831. return Result.error('No longer in system');
  7832. }
  7833. });
  7834. };
  7835. const isInvalid = (component, invalidConfig) => {
  7836. const elem = invalidConfig.getRoot(component).getOr(component.element);
  7837. return has(elem, invalidConfig.invalidClass);
  7838. };
  7839. var InvalidateApis = /*#__PURE__*/Object.freeze({
  7840. __proto__: null,
  7841. markValid: markValid,
  7842. markInvalid: markInvalid,
  7843. query: query,
  7844. run: run,
  7845. isInvalid: isInvalid
  7846. });
  7847. const events$a = (invalidConfig, invalidState) => invalidConfig.validator.map((validatorInfo) => derive$2([
  7848. run$1(validatorInfo.onEvent, (component) => {
  7849. run(component, invalidConfig, invalidState).get(identity);
  7850. })
  7851. ].concat(validatorInfo.validateOnLoad ? [
  7852. runOnAttached((component) => {
  7853. run(component, invalidConfig, invalidState).get(noop);
  7854. })
  7855. ] : []))).getOr({});
  7856. var ActiveInvalidate = /*#__PURE__*/Object.freeze({
  7857. __proto__: null,
  7858. events: events$a
  7859. });
  7860. var InvalidateSchema = [
  7861. required$1('invalidClass'),
  7862. defaulted('getRoot', Optional.none),
  7863. // TODO: Completely rework the notify API
  7864. optionObjOf('notify', [
  7865. defaulted('aria', 'alert'),
  7866. // Maybe we should use something else.
  7867. defaulted('getContainer', Optional.none),
  7868. defaulted('validHtml', ''),
  7869. onHandler('onValid'),
  7870. onHandler('onInvalid'),
  7871. onHandler('onValidate')
  7872. ]),
  7873. optionObjOf('validator', [
  7874. required$1('validate'),
  7875. defaulted('onEvent', 'input'),
  7876. defaulted('validateOnLoad', true)
  7877. ])
  7878. ];
  7879. const onLoad$4 = (component, repConfig, repState) => {
  7880. repConfig.store.manager.onLoad(component, repConfig, repState);
  7881. };
  7882. const onUnload$2 = (component, repConfig, repState) => {
  7883. repConfig.store.manager.onUnload(component, repConfig, repState);
  7884. };
  7885. const setValue$3 = (component, repConfig, repState, data) => {
  7886. repConfig.store.manager.setValue(component, repConfig, repState, data);
  7887. };
  7888. const getValue$3 = (component, repConfig, repState) => repConfig.store.manager.getValue(component, repConfig, repState);
  7889. const getState$2 = (component, repConfig, repState) => repState;
  7890. var RepresentApis = /*#__PURE__*/Object.freeze({
  7891. __proto__: null,
  7892. onLoad: onLoad$4,
  7893. onUnload: onUnload$2,
  7894. setValue: setValue$3,
  7895. getValue: getValue$3,
  7896. getState: getState$2
  7897. });
  7898. const events$9 = (repConfig, repState) => {
  7899. const es = repConfig.resetOnDom ? [
  7900. runOnAttached((comp, _se) => {
  7901. onLoad$4(comp, repConfig, repState);
  7902. }),
  7903. runOnDetached((comp, _se) => {
  7904. onUnload$2(comp, repConfig, repState);
  7905. })
  7906. ] : [
  7907. loadEvent(repConfig, repState, onLoad$4)
  7908. ];
  7909. return derive$2(es);
  7910. };
  7911. var ActiveRepresenting = /*#__PURE__*/Object.freeze({
  7912. __proto__: null,
  7913. events: events$9
  7914. });
  7915. const memory$1 = () => {
  7916. const data = Cell(null);
  7917. const readState = () => ({
  7918. mode: 'memory',
  7919. value: data.get()
  7920. });
  7921. const isNotSet = () => data.get() === null;
  7922. const clear = () => {
  7923. data.set(null);
  7924. };
  7925. return nu$4({
  7926. set: data.set,
  7927. get: data.get,
  7928. isNotSet,
  7929. clear,
  7930. readState
  7931. });
  7932. };
  7933. const manual = () => {
  7934. const readState = noop;
  7935. return nu$4({
  7936. readState
  7937. });
  7938. };
  7939. const dataset = () => {
  7940. const dataByValue = Cell({});
  7941. const dataByText = Cell({});
  7942. const readState = () => ({
  7943. mode: 'dataset',
  7944. dataByValue: dataByValue.get(),
  7945. dataByText: dataByText.get()
  7946. });
  7947. const clear = () => {
  7948. dataByValue.set({});
  7949. dataByText.set({});
  7950. };
  7951. // itemString can be matching value or text.
  7952. // TODO: type problem - impossible to correctly return value when type parameter only exists in return type
  7953. const lookup = (itemString) => get$h(dataByValue.get(), itemString).orThunk(() => get$h(dataByText.get(), itemString));
  7954. const update = (items) => {
  7955. const currentDataByValue = dataByValue.get();
  7956. const currentDataByText = dataByText.get();
  7957. const newDataByValue = {};
  7958. const newDataByText = {};
  7959. each$1(items, (item) => {
  7960. newDataByValue[item.value] = item;
  7961. get$h(item, 'meta').each((meta) => {
  7962. get$h(meta, 'text').each((text) => {
  7963. newDataByText[text] = item;
  7964. });
  7965. });
  7966. });
  7967. dataByValue.set({
  7968. ...currentDataByValue,
  7969. ...newDataByValue
  7970. });
  7971. dataByText.set({
  7972. ...currentDataByText,
  7973. ...newDataByText
  7974. });
  7975. };
  7976. return nu$4({
  7977. readState,
  7978. lookup,
  7979. update,
  7980. clear
  7981. });
  7982. };
  7983. const init$9 = (spec) => spec.store.manager.state(spec);
  7984. var RepresentState = /*#__PURE__*/Object.freeze({
  7985. __proto__: null,
  7986. memory: memory$1,
  7987. dataset: dataset,
  7988. manual: manual,
  7989. init: init$9
  7990. });
  7991. const setValue$2 = (component, repConfig, repState, data) => {
  7992. const store = repConfig.store;
  7993. repState.update([data]);
  7994. store.setValue(component, data);
  7995. repConfig.onSetValue(component, data);
  7996. };
  7997. const getValue$2 = (component, repConfig, repState) => {
  7998. const store = repConfig.store;
  7999. const key = store.getDataKey(component);
  8000. return repState.lookup(key).getOrThunk(() => store.getFallbackEntry(key));
  8001. };
  8002. const onLoad$3 = (component, repConfig, repState) => {
  8003. const store = repConfig.store;
  8004. store.initialValue.each((data) => {
  8005. setValue$2(component, repConfig, repState, data);
  8006. });
  8007. };
  8008. const onUnload$1 = (component, repConfig, repState) => {
  8009. repState.clear();
  8010. };
  8011. var DatasetStore = [
  8012. option$3('initialValue'),
  8013. required$1('getFallbackEntry'),
  8014. required$1('getDataKey'),
  8015. required$1('setValue'),
  8016. output$1('manager', {
  8017. setValue: setValue$2,
  8018. getValue: getValue$2,
  8019. onLoad: onLoad$3,
  8020. onUnload: onUnload$1,
  8021. state: dataset
  8022. })
  8023. ];
  8024. const getValue$1 = (component, repConfig, _repState) => repConfig.store.getValue(component);
  8025. const setValue$1 = (component, repConfig, _repState, data) => {
  8026. repConfig.store.setValue(component, data);
  8027. repConfig.onSetValue(component, data);
  8028. };
  8029. const onLoad$2 = (component, repConfig, _repState) => {
  8030. repConfig.store.initialValue.each((data) => {
  8031. repConfig.store.setValue(component, data);
  8032. });
  8033. };
  8034. var ManualStore = [
  8035. required$1('getValue'),
  8036. defaulted('setValue', noop),
  8037. option$3('initialValue'),
  8038. output$1('manager', {
  8039. setValue: setValue$1,
  8040. getValue: getValue$1,
  8041. onLoad: onLoad$2,
  8042. onUnload: noop,
  8043. state: NoState.init
  8044. })
  8045. ];
  8046. const setValue = (component, repConfig, repState, data) => {
  8047. repState.set(data);
  8048. repConfig.onSetValue(component, data);
  8049. };
  8050. const getValue = (component, repConfig, repState) => repState.get();
  8051. const onLoad$1 = (component, repConfig, repState) => {
  8052. repConfig.store.initialValue.each((initVal) => {
  8053. if (repState.isNotSet()) {
  8054. repState.set(initVal);
  8055. }
  8056. });
  8057. };
  8058. const onUnload = (component, repConfig, repState) => {
  8059. repState.clear();
  8060. };
  8061. var MemoryStore = [
  8062. option$3('initialValue'),
  8063. output$1('manager', {
  8064. setValue,
  8065. getValue,
  8066. onLoad: onLoad$1,
  8067. onUnload,
  8068. state: memory$1
  8069. })
  8070. ];
  8071. var RepresentSchema = [
  8072. defaultedOf('store', { mode: 'memory' }, choose$1('mode', {
  8073. memory: MemoryStore,
  8074. manual: ManualStore,
  8075. dataset: DatasetStore
  8076. })),
  8077. onHandler('onSetValue'),
  8078. defaulted('resetOnDom', false)
  8079. ];
  8080. // The self-reference is clumsy.
  8081. const Representing = create$3({
  8082. fields: RepresentSchema,
  8083. name: 'representing',
  8084. active: ActiveRepresenting,
  8085. apis: RepresentApis,
  8086. extra: {
  8087. setValueFrom: (component, source) => {
  8088. const value = Representing.getValue(source);
  8089. Representing.setValue(component, value);
  8090. }
  8091. },
  8092. state: RepresentState
  8093. });
  8094. const Invalidating = create$3({
  8095. fields: InvalidateSchema,
  8096. name: 'invalidating',
  8097. active: ActiveInvalidate,
  8098. apis: InvalidateApis,
  8099. extra: {
  8100. // Note, this requires representing to be on the validatee
  8101. validation: (validator) => {
  8102. return (component) => {
  8103. const v = Representing.getValue(component);
  8104. return Future.pure(validator(v));
  8105. };
  8106. }
  8107. }
  8108. });
  8109. const exhibit$4 = (base, posConfig) => nu$2({
  8110. classes: [],
  8111. styles: posConfig.useFixed() ? {} : { position: 'relative' }
  8112. });
  8113. var ActivePosition = /*#__PURE__*/Object.freeze({
  8114. __proto__: null,
  8115. exhibit: exhibit$4
  8116. });
  8117. const adt$3 = Adt.generate([
  8118. { none: [] },
  8119. { relative: ['x', 'y', 'width', 'height'] },
  8120. { fixed: ['x', 'y', 'width', 'height'] }
  8121. ]);
  8122. const positionWithDirection = (posName, decision, x, y, width, height) => {
  8123. const decisionRect = decision.rect;
  8124. const decisionX = decisionRect.x - x;
  8125. const decisionY = decisionRect.y - y;
  8126. const decisionWidth = decisionRect.width;
  8127. const decisionHeight = decisionRect.height;
  8128. const decisionRight = width - (decisionX + decisionWidth);
  8129. const decisionBottom = height - (decisionY + decisionHeight);
  8130. const left = Optional.some(decisionX);
  8131. const top = Optional.some(decisionY);
  8132. const right = Optional.some(decisionRight);
  8133. const bottom = Optional.some(decisionBottom);
  8134. const none = Optional.none();
  8135. return cata$1(decision.direction, () => NuPositionCss(posName, left, top, none, none), // southeast
  8136. () => NuPositionCss(posName, none, top, right, none), // southwest
  8137. () => NuPositionCss(posName, left, none, none, bottom), // northeast
  8138. () => NuPositionCss(posName, none, none, right, bottom), // northwest
  8139. () => NuPositionCss(posName, left, top, none, none), // south
  8140. () => NuPositionCss(posName, left, none, none, bottom), // north
  8141. () => NuPositionCss(posName, left, top, none, none), // east
  8142. () => NuPositionCss(posName, none, top, right, none) // west
  8143. );
  8144. };
  8145. const reposition = (origin, decision) => origin.fold(() => {
  8146. const decisionRect = decision.rect;
  8147. return NuPositionCss('absolute', Optional.some(decisionRect.x), Optional.some(decisionRect.y), Optional.none(), Optional.none());
  8148. }, (x, y, width, height) => {
  8149. return positionWithDirection('absolute', decision, x, y, width, height);
  8150. }, (x, y, width, height) => {
  8151. return positionWithDirection('fixed', decision, x, y, width, height);
  8152. });
  8153. const toBox = (origin, element) => {
  8154. const rel = curry(find$2, element);
  8155. const position = origin.fold(rel, rel, () => {
  8156. const scroll = get$b();
  8157. // TODO: Make adding the scroll in OuterPosition.find optional.
  8158. return find$2(element).translate(-scroll.left, -scroll.top);
  8159. });
  8160. const width = getOuter(element);
  8161. const height = getOuter$1(element);
  8162. return bounds(position.left, position.top, width, height);
  8163. };
  8164. const viewport = (origin, optBounds) => optBounds.fold(
  8165. /* There are no bounds supplied */
  8166. () => origin.fold(win, win, bounds), (bounds$1) =>
  8167. /* Use any bounds supplied or remove the scroll position of the bounds for fixed. */
  8168. origin.fold(constant$1(bounds$1), constant$1(bounds$1), () => {
  8169. const pos = translate$1(origin, bounds$1.x, bounds$1.y);
  8170. return bounds(pos.left, pos.top, bounds$1.width, bounds$1.height);
  8171. }));
  8172. const translate$1 = (origin, x, y) => {
  8173. const pos = SugarPosition(x, y);
  8174. const removeScroll = () => {
  8175. const outerScroll = get$b();
  8176. return pos.translate(-outerScroll.left, -outerScroll.top);
  8177. };
  8178. // This could use cata if it wasn't a circular reference
  8179. return origin.fold(constant$1(pos), constant$1(pos), removeScroll);
  8180. };
  8181. const cata = (subject, onNone, onRelative, onFixed) => subject.fold(onNone, onRelative, onFixed);
  8182. adt$3.none;
  8183. const relative = adt$3.relative;
  8184. const fixed = adt$3.fixed;
  8185. const anchor = (anchorBox, origin) => ({
  8186. anchorBox,
  8187. origin
  8188. });
  8189. const box = (anchorBox, origin) => anchor(anchorBox, origin);
  8190. const adt$2 = Adt.generate([
  8191. { fit: ['reposition'] },
  8192. { nofit: ['reposition', 'visibleW', 'visibleH', 'isVisible'] }
  8193. ]);
  8194. /**
  8195. * This will attempt to determine if the box will fit within the specified bounds or if it needs to be repositioned.
  8196. * It will return the following details:
  8197. * - if the original rect was in bounds (originInBounds & sizeInBounds). This is used to determine if we fitted
  8198. * without having to make adjustments.
  8199. * - the height and width that would be visible in the original location. (ie the overlap between the rect and
  8200. * the bounds or the distance between the boxes if there is no overlap)
  8201. */
  8202. const determinePosition = (box, bounds) => {
  8203. const { x: boundsX, y: boundsY, right: boundsRight, bottom: boundsBottom } = bounds;
  8204. const { x, y, right, bottom, width, height } = box;
  8205. // simple checks for "is the top left inside the view"
  8206. const xInBounds = x >= boundsX && x <= boundsRight;
  8207. const yInBounds = y >= boundsY && y <= boundsBottom;
  8208. const originInBounds = xInBounds && yInBounds;
  8209. // simple checks for "is the bottom right inside the view"
  8210. const rightInBounds = right <= boundsRight && right >= boundsX;
  8211. const bottomInBounds = bottom <= boundsBottom && bottom >= boundsY;
  8212. const sizeInBounds = rightInBounds && bottomInBounds;
  8213. // measure how much of the width and height are visible. This should never be larger than the actual width or height
  8214. // however it can be a negative value when offscreen. These values are generally are only needed for the "nofit" case
  8215. const visibleW = Math.min(width, x >= boundsX ? boundsRight - x : right - boundsX);
  8216. const visibleH = Math.min(height, y >= boundsY ? boundsBottom - y : bottom - boundsY);
  8217. return {
  8218. originInBounds,
  8219. sizeInBounds,
  8220. visibleW,
  8221. visibleH
  8222. };
  8223. };
  8224. /**
  8225. * This will attempt to calculate and adjust the position of the box so that is stays within the specified bounds.
  8226. * The end result will be a new restricted box of where it can safely be placed within the bounds.
  8227. */
  8228. const calcReposition = (box, bounds$1) => {
  8229. const { x: boundsX, y: boundsY, right: boundsRight, bottom: boundsBottom } = bounds$1;
  8230. const { x, y, width, height } = box;
  8231. // measure the maximum x and y taking into account the height and width of the box
  8232. const maxX = Math.max(boundsX, boundsRight - width);
  8233. const maxY = Math.max(boundsY, boundsBottom - height);
  8234. // Futz with the X value to ensure that we're not off the left or right of the screen
  8235. const restrictedX = clamp(x, boundsX, maxX);
  8236. // Futz with the Y value to ensure that we're not off the top or bottom of the screen
  8237. const restrictedY = clamp(y, boundsY, maxY);
  8238. // Determine the new height and width based on the restricted X/Y to keep the element in bounds
  8239. const restrictedWidth = Math.min(restrictedX + width, boundsRight) - restrictedX;
  8240. const restrictedHeight = Math.min(restrictedY + height, boundsBottom) - restrictedY;
  8241. return bounds(restrictedX, restrictedY, restrictedWidth, restrictedHeight);
  8242. };
  8243. /**
  8244. * Determine the maximum height and width available for where the box is positioned in the bounds, making sure
  8245. * to account for which direction it's rendering in.
  8246. */
  8247. const calcMaxSizes = (direction, box, bounds) => {
  8248. // Futz with the "height" of the popup to ensure if it doesn't fit it's capped at the available height.
  8249. const upAvailable = constant$1(box.bottom - bounds.y);
  8250. const downAvailable = constant$1(bounds.bottom - box.y);
  8251. const maxHeight = cataVertical(direction, downAvailable, /* middle */ downAvailable, upAvailable);
  8252. // Futz with the "width" of the popup to ensure if it doesn't fit it's capped at the available width.
  8253. const westAvailable = constant$1(box.right - bounds.x);
  8254. const eastAvailable = constant$1(bounds.right - box.x);
  8255. const maxWidth = cataHorizontal(direction, eastAvailable, /* middle */ eastAvailable, westAvailable);
  8256. return {
  8257. maxWidth,
  8258. maxHeight
  8259. };
  8260. };
  8261. const attempt = (candidate, width, height, bounds$1) => {
  8262. const bubble = candidate.bubble;
  8263. const bubbleOffset = bubble.offset;
  8264. // adjust the bounds to account for the layout and bubble restrictions
  8265. const adjustedBounds = adjustBounds(bounds$1, candidate.restriction, bubbleOffset);
  8266. // candidate position is excluding the bubble, so add those values as well
  8267. const newX = candidate.x + bubbleOffset.left;
  8268. const newY = candidate.y + bubbleOffset.top;
  8269. const box = bounds(newX, newY, width, height);
  8270. // determine the position of the box in relation to the bounds
  8271. const { originInBounds, sizeInBounds, visibleW, visibleH } = determinePosition(box, adjustedBounds);
  8272. // restrict the box if it won't fit in the bounds
  8273. const fits = originInBounds && sizeInBounds;
  8274. const fittedBox = fits ? box : calcReposition(box, adjustedBounds);
  8275. // Determine if the box is at least partly visible in the bounds after applying the restrictions
  8276. const isPartlyVisible = fittedBox.width > 0 && fittedBox.height > 0;
  8277. // Determine the maximum height and width available in the bounds
  8278. const { maxWidth, maxHeight } = calcMaxSizes(candidate.direction, fittedBox, bounds$1);
  8279. const reposition = {
  8280. rect: fittedBox,
  8281. maxHeight,
  8282. maxWidth,
  8283. direction: candidate.direction,
  8284. placement: candidate.placement,
  8285. classes: {
  8286. on: bubble.classesOn,
  8287. off: bubble.classesOff
  8288. },
  8289. layout: candidate.label,
  8290. testY: newY
  8291. };
  8292. // useful debugging that I don't want to lose
  8293. // console.log(candidate.label);
  8294. // console.table([{
  8295. // newY,
  8296. // limitY: fittedBox.y,
  8297. // boundsY: bounds.y,
  8298. // boundsBottom: bounds.bottom,
  8299. // newX,
  8300. // limitX: fittedBox.x,
  8301. // boundsX: bounds.x,
  8302. // boundsRight: bounds.right,
  8303. // candidateX: candidate.x,
  8304. // candidateY: candidate.y,
  8305. // width,
  8306. // height,
  8307. // isPartlyVisible
  8308. // }]);
  8309. // console.log(`maxWidth: ${maxWidth}, visibleW: ${visibleW}`);
  8310. // console.log(`maxHeight: ${maxHeight}, visibleH: ${visibleH}`);
  8311. // console.log('originInBounds:', originInBounds);
  8312. // console.log('sizeInBounds:', sizeInBounds);
  8313. // console.log(originInBounds && sizeInBounds ? 'fit' : 'nofit');
  8314. // Take special note that we don't use the futz values in the nofit case; whether this position is a good fit is separate
  8315. // to ensuring that if we choose it the popup is actually on screen properly.
  8316. return fits || candidate.alwaysFit ? adt$2.fit(reposition) : adt$2.nofit(reposition, visibleW, visibleH, isPartlyVisible);
  8317. };
  8318. /**
  8319. * Attempts to fit a box (generally a menu).
  8320. *
  8321. * candidates: an array of layout generators, generally obtained via api.Layout or api.LinkedLayout
  8322. * anchorBox: the box on screen that triggered the menu, we must touch one of the edges as defined by the candidate layouts
  8323. * elementBox: the popup (only width and height matter)
  8324. * bubbles: the bubbles for the popup (see api.Bubble)
  8325. * bounds: the screen
  8326. */
  8327. const attempts = (element, candidates, anchorBox, elementBox, bubbles, bounds) => {
  8328. const panelWidth = elementBox.width;
  8329. const panelHeight = elementBox.height;
  8330. const attemptBestFit = (layout, reposition, visibleW, visibleH, isVisible) => {
  8331. const next = layout(anchorBox, elementBox, bubbles, element, bounds);
  8332. const attemptLayout = attempt(next, panelWidth, panelHeight, bounds);
  8333. return attemptLayout.fold(constant$1(attemptLayout), (newReposition, newVisibleW, newVisibleH, newIsVisible) => {
  8334. // console.log(`label: ${next.label}, newVisibleW: ${newVisibleW}, visibleW: ${visibleW}, newVisibleH: ${newVisibleH}, visibleH: ${visibleH}, newIsVisible: ${newIsVisible}, isVisible: ${isVisible}`);
  8335. const improved = isVisible === newIsVisible ? (newVisibleH > visibleH || newVisibleW > visibleW) : (!isVisible && newIsVisible);
  8336. // console.log('improved? ', improved);
  8337. return improved ? attemptLayout : adt$2.nofit(reposition, visibleW, visibleH, isVisible);
  8338. });
  8339. };
  8340. const abc = foldl(candidates, (b, a) => {
  8341. const bestNext = curry(attemptBestFit, a);
  8342. return b.fold(constant$1(b), bestNext);
  8343. },
  8344. // fold base case: No candidates, it's never going to be correct, so do whatever
  8345. adt$2.nofit({
  8346. rect: anchorBox,
  8347. maxHeight: elementBox.height,
  8348. maxWidth: elementBox.width,
  8349. direction: southeast$3(),
  8350. placement: "southeast" /* Placement.Southeast */,
  8351. classes: {
  8352. on: [],
  8353. off: []
  8354. },
  8355. layout: 'none',
  8356. testY: anchorBox.y
  8357. }, -1, -1, false));
  8358. // unwrapping 'reposition' from the adt, for both fit & nofit the first arg is the one we need,
  8359. // so we can cheat and use Fun.identity
  8360. return abc.fold(identity, identity);
  8361. };
  8362. const properties = ['top', 'bottom', 'right', 'left'];
  8363. const timerAttr = 'data-alloy-transition-timer';
  8364. const isTransitioning$1 = (element, transition) => hasAll(element, transition.classes);
  8365. const shouldApplyTransitionCss = (transition, decision, lastPlacement) => {
  8366. // Don't apply transitions if there was no previous placement as it's transitioning from offscreen
  8367. return lastPlacement.exists((placer) => {
  8368. const mode = transition.mode;
  8369. return mode === 'all' ? true : placer[mode] !== decision[mode];
  8370. });
  8371. };
  8372. const hasChanges = (position, intermediate) => {
  8373. // Round to 3 decimal points
  8374. const round = (value) => parseFloat(value).toFixed(3);
  8375. return find$4(intermediate, (value, key) => {
  8376. const newValue = position[key].map(round);
  8377. const val = value.map(round);
  8378. return !equals(newValue, val);
  8379. }).isSome();
  8380. };
  8381. const getTransitionDuration = (element) => {
  8382. const get = (name) => {
  8383. const style = get$e(element, name);
  8384. const times = style.split(/\s*,\s*/);
  8385. return filter$2(times, isNotEmpty);
  8386. };
  8387. const parse = (value) => {
  8388. if (isString(value) && /^[\d.]+/.test(value)) {
  8389. const num = parseFloat(value);
  8390. return endsWith(value, 'ms') ? num : num * 1000;
  8391. }
  8392. else {
  8393. return 0;
  8394. }
  8395. };
  8396. const delay = get('transition-delay');
  8397. const duration = get('transition-duration');
  8398. return foldl(duration, (acc, dur, i) => {
  8399. const time = parse(delay[i]) + parse(dur);
  8400. return Math.max(acc, time);
  8401. }, 0);
  8402. };
  8403. const setupTransitionListeners = (element, transition) => {
  8404. const transitionEnd = unbindable();
  8405. const transitionCancel = unbindable();
  8406. let timer;
  8407. const isSourceTransition = (e) => {
  8408. var _a;
  8409. // Ensure the transition event isn't from a pseudo element
  8410. const pseudoElement = (_a = e.raw.pseudoElement) !== null && _a !== void 0 ? _a : '';
  8411. return eq(e.target, element) && isEmpty(pseudoElement) && contains$2(properties, e.raw.propertyName);
  8412. };
  8413. const transitionDone = (e) => {
  8414. if (isNullable(e) || isSourceTransition(e)) {
  8415. transitionEnd.clear();
  8416. transitionCancel.clear();
  8417. // Only cleanup the class/timer on transitionend not on a cancel. This is done as cancel
  8418. // means the element has been repositioned and would need to keep transitioning
  8419. const type = e === null || e === void 0 ? void 0 : e.raw.type;
  8420. if (isNullable(type) || type === transitionend()) {
  8421. clearTimeout(timer);
  8422. remove$8(element, timerAttr);
  8423. remove$2(element, transition.classes);
  8424. }
  8425. }
  8426. };
  8427. const transitionStart = bind$1(element, transitionstart(), (e) => {
  8428. if (isSourceTransition(e)) {
  8429. transitionStart.unbind();
  8430. transitionEnd.set(bind$1(element, transitionend(), transitionDone));
  8431. transitionCancel.set(bind$1(element, transitioncancel(), transitionDone));
  8432. }
  8433. });
  8434. // Request the next animation frame so we can roughly determine when the transition starts and then ensure
  8435. // the transition is cleaned up. In addition add ~17ms to the delay as that's about about 1 frame at 60fps
  8436. const duration = getTransitionDuration(element);
  8437. window.requestAnimationFrame(() => {
  8438. timer = setTimeout(transitionDone, duration + 17);
  8439. set$9(element, timerAttr, timer);
  8440. });
  8441. };
  8442. const startTransitioning = (element, transition) => {
  8443. add$1(element, transition.classes);
  8444. // Clear any existing cleanup timers
  8445. getOpt(element, timerAttr).each((timerId) => {
  8446. clearTimeout(parseInt(timerId, 10));
  8447. remove$8(element, timerAttr);
  8448. });
  8449. setupTransitionListeners(element, transition);
  8450. };
  8451. const applyTransitionCss = (element, origin, position, transition, decision, lastPlacement) => {
  8452. const shouldTransition = shouldApplyTransitionCss(transition, decision, lastPlacement);
  8453. if (shouldTransition || isTransitioning$1(element, transition)) {
  8454. // Set the new position first so we can calculate the computed position
  8455. set$7(element, 'position', position.position);
  8456. // Get the computed positions for the current element based on the new position CSS being applied
  8457. const rect = toBox(origin, element);
  8458. const intermediatePosition = reposition(origin, { ...decision, rect });
  8459. const intermediateCssOptions = mapToObject(properties, (prop) => intermediatePosition[prop]);
  8460. // Apply the intermediate styles and transition classes if something has changed
  8461. if (hasChanges(position, intermediateCssOptions)) {
  8462. setOptions(element, intermediateCssOptions);
  8463. if (shouldTransition) {
  8464. startTransitioning(element, transition);
  8465. }
  8466. reflow(element);
  8467. }
  8468. }
  8469. else {
  8470. remove$2(element, transition.classes);
  8471. }
  8472. };
  8473. /*
  8474. * This is the old repartee API. It is retained in a similar structure to the original form,
  8475. * in case we decide to bring back the flexibility of working with non-standard positioning.
  8476. */
  8477. const elementSize = (p) => ({
  8478. width: Math.ceil(getOuter(p)),
  8479. height: getOuter$1(p)
  8480. });
  8481. const layout = (anchorBox, element, bubbles, options) => {
  8482. // clear the potentially limiting factors before measuring
  8483. remove$6(element, 'max-height');
  8484. remove$6(element, 'max-width');
  8485. const elementBox = elementSize(element);
  8486. return attempts(element, options.preference, anchorBox, elementBox, bubbles, options.bounds);
  8487. };
  8488. const setClasses = (element, decision) => {
  8489. const classInfo = decision.classes;
  8490. remove$2(element, classInfo.off);
  8491. add$1(element, classInfo.on);
  8492. };
  8493. /*
  8494. * maxHeightFunction is a MaxHeight instance.
  8495. * max-height is usually the distance between the edge of the popup and the screen; top of popup to bottom of screen for south, bottom of popup to top of screen for north.
  8496. *
  8497. * There are a few cases where we specifically don't want a max-height, which is why it's optional.
  8498. */
  8499. const setHeight = (element, decision, options) => {
  8500. // The old API enforced MaxHeight.anchored() for fixed position. That no longer seems necessary.
  8501. const maxHeightFunction = options.maxHeightFunction;
  8502. maxHeightFunction(element, decision.maxHeight);
  8503. };
  8504. const setWidth = (element, decision, options) => {
  8505. const maxWidthFunction = options.maxWidthFunction;
  8506. maxWidthFunction(element, decision.maxWidth);
  8507. };
  8508. const position$2 = (element, decision, options) => {
  8509. // This is a point of difference between Alloy and Repartee. Repartee appears to use Measure to calculate the available space for fixed origin
  8510. // That is not ported yet.
  8511. const positionCss = reposition(options.origin, decision);
  8512. options.transition.each((transition) => {
  8513. applyTransitionCss(element, options.origin, positionCss, transition, decision, options.lastPlacement);
  8514. });
  8515. applyPositionCss(element, positionCss);
  8516. };
  8517. const setPlacement = (element, decision) => {
  8518. setPlacement$1(element, decision.placement);
  8519. };
  8520. const defaultOr = (options, key, dephault) => options[key] === undefined ? dephault : options[key];
  8521. // This takes care of everything when you are positioning UI that can go anywhere on the screen
  8522. const simple = (anchor, element, bubble, layouts, lastPlacement, optBounds, overrideOptions, transition) => {
  8523. // the only supported override at the moment. Once relative has been deleted, maybe this can be optional in the bag
  8524. const maxHeightFunction = defaultOr(overrideOptions, 'maxHeightFunction', anchored());
  8525. const maxWidthFunction = defaultOr(overrideOptions, 'maxWidthFunction', noop);
  8526. const anchorBox = anchor.anchorBox;
  8527. const origin = anchor.origin;
  8528. const options = {
  8529. bounds: viewport(origin, optBounds),
  8530. origin,
  8531. preference: layouts,
  8532. maxHeightFunction,
  8533. maxWidthFunction,
  8534. lastPlacement,
  8535. transition
  8536. };
  8537. return go(anchorBox, element, bubble, options);
  8538. };
  8539. // This is the old public API. If we ever need full customisability again, this is how to expose it
  8540. const go = (anchorBox, element, bubble, options) => {
  8541. const decision = layout(anchorBox, element, bubble, options);
  8542. position$2(element, decision, options);
  8543. setPlacement(element, decision);
  8544. setClasses(element, decision);
  8545. setHeight(element, decision, options);
  8546. setWidth(element, decision, options);
  8547. return {
  8548. layout: decision.layout,
  8549. placement: decision.placement
  8550. };
  8551. };
  8552. const nu$1 = identity;
  8553. const schema$n = () => optionObjOf('layouts', [
  8554. required$1('onLtr'),
  8555. required$1('onRtl'),
  8556. option$3('onBottomLtr'),
  8557. option$3('onBottomRtl')
  8558. ]);
  8559. const get$1 = (elem, info, defaultLtr, defaultRtl, defaultBottomLtr, defaultBottomRtl, dirElement) => {
  8560. const isBottomToTop = dirElement.map(isBottomToTopDir).getOr(false);
  8561. const customLtr = info.layouts.map((ls) => ls.onLtr(elem));
  8562. const customRtl = info.layouts.map((ls) => ls.onRtl(elem));
  8563. const ltr = isBottomToTop ?
  8564. info.layouts.bind((ls) => ls.onBottomLtr.map((f) => f(elem)))
  8565. .or(customLtr)
  8566. .getOr(defaultBottomLtr) :
  8567. customLtr.getOr(defaultLtr);
  8568. const rtl = isBottomToTop ?
  8569. info.layouts.bind((ls) => ls.onBottomRtl.map((f) => f(elem)))
  8570. .or(customRtl)
  8571. .getOr(defaultBottomRtl) :
  8572. customRtl.getOr(defaultRtl);
  8573. const f = onDirection(ltr, rtl);
  8574. return f(elem);
  8575. };
  8576. const placement$4 = (component, anchorInfo, origin) => {
  8577. const hotspot = anchorInfo.hotspot;
  8578. const anchorBox = toBox(origin, hotspot.element);
  8579. const layouts = get$1(component.element, anchorInfo, belowOrAbove(), belowOrAboveRtl(), aboveOrBelow(), aboveOrBelowRtl(), Optional.some(anchorInfo.hotspot.element));
  8580. return Optional.some(nu$1({
  8581. anchorBox,
  8582. bubble: anchorInfo.bubble.getOr(fallback()),
  8583. overrides: anchorInfo.overrides,
  8584. layouts
  8585. }));
  8586. };
  8587. var HotspotAnchor = [
  8588. required$1('hotspot'),
  8589. option$3('bubble'),
  8590. defaulted('overrides', {}),
  8591. schema$n(),
  8592. output$1('placement', placement$4)
  8593. ];
  8594. const placement$3 = (component, anchorInfo, origin) => {
  8595. const pos = translate$1(origin, anchorInfo.x, anchorInfo.y);
  8596. const anchorBox = bounds(pos.left, pos.top, anchorInfo.width, anchorInfo.height);
  8597. const layouts = get$1(component.element, anchorInfo, all$2(), allRtl$1(),
  8598. // No default bottomToTop layouts currently needed
  8599. all$2(), allRtl$1(), Optional.none());
  8600. return Optional.some(nu$1({
  8601. anchorBox,
  8602. bubble: anchorInfo.bubble,
  8603. overrides: anchorInfo.overrides,
  8604. layouts
  8605. }));
  8606. };
  8607. var MakeshiftAnchor = [
  8608. required$1('x'),
  8609. required$1('y'),
  8610. defaulted('height', 0),
  8611. defaulted('width', 0),
  8612. defaulted('bubble', fallback()),
  8613. defaulted('overrides', {}),
  8614. schema$n(),
  8615. output$1('placement', placement$3)
  8616. ];
  8617. const adt$1 = Adt.generate([
  8618. { screen: ['point'] },
  8619. { absolute: ['point', 'scrollLeft', 'scrollTop'] }
  8620. ]);
  8621. const toFixed = (pos) =>
  8622. // TODO: Use new ADT methods
  8623. pos.fold(identity, (point, scrollLeft, scrollTop) => point.translate(-scrollLeft, -scrollTop));
  8624. const toAbsolute = (pos) => pos.fold(identity, identity);
  8625. const sum = (points) => foldl(points, (b, a) => b.translate(a.left, a.top), SugarPosition(0, 0));
  8626. const sumAsFixed = (positions) => {
  8627. const points = map$2(positions, toFixed);
  8628. return sum(points);
  8629. };
  8630. const sumAsAbsolute = (positions) => {
  8631. const points = map$2(positions, toAbsolute);
  8632. return sum(points);
  8633. };
  8634. const screen = adt$1.screen;
  8635. const absolute = adt$1.absolute;
  8636. // In one mode, the window is inside an iframe. If that iframe is in the
  8637. // same document as the positioning element (component), then identify the offset
  8638. // difference between the iframe and the component.
  8639. const getOffset = (component, origin, anchorInfo) => {
  8640. const win = defaultView(anchorInfo.root).dom;
  8641. const hasSameOwner = (frame) => {
  8642. const frameOwner = owner$4(frame);
  8643. const compOwner = owner$4(component.element);
  8644. return eq(frameOwner, compOwner);
  8645. };
  8646. return Optional.from(win.frameElement).map(SugarElement.fromDom)
  8647. .filter(hasSameOwner).map(absolute$3);
  8648. };
  8649. const getRootPoint = (component, origin, anchorInfo) => {
  8650. const doc = owner$4(component.element);
  8651. const outerScroll = get$b(doc);
  8652. const offset = getOffset(component, origin, anchorInfo).getOr(outerScroll);
  8653. return absolute(offset, outerScroll.left, outerScroll.top);
  8654. };
  8655. const getBox = (left, top, width, height) => {
  8656. const point = screen(SugarPosition(left, top));
  8657. return Optional.some(pointed(point, width, height));
  8658. };
  8659. const calcNewAnchor = (optBox, rootPoint, anchorInfo, origin, elem) => optBox.map((box) => {
  8660. const points = [rootPoint, box.point];
  8661. const topLeft = cata(origin, () => sumAsAbsolute(points), () => sumAsAbsolute(points), () => sumAsFixed(points));
  8662. const anchorBox = rect(topLeft.left, topLeft.top, box.width, box.height);
  8663. const layoutsLtr = anchorInfo.showAbove ?
  8664. aboveOrBelow() :
  8665. belowOrAbove();
  8666. const layoutsRtl = anchorInfo.showAbove ?
  8667. aboveOrBelowRtl() :
  8668. belowOrAboveRtl();
  8669. const layouts = get$1(elem, anchorInfo, layoutsLtr, layoutsRtl, layoutsLtr, layoutsRtl, Optional.none());
  8670. return nu$1({
  8671. anchorBox,
  8672. bubble: anchorInfo.bubble.getOr(fallback()),
  8673. overrides: anchorInfo.overrides,
  8674. layouts
  8675. });
  8676. });
  8677. const placement$2 = (component, anchorInfo, origin) => {
  8678. const rootPoint = getRootPoint(component, origin, anchorInfo);
  8679. return anchorInfo.node
  8680. // Ensure the node is still attached, otherwise we can't get a valid client rect for a detached node
  8681. .filter(inBody)
  8682. .bind((target) => {
  8683. const rect = target.dom.getBoundingClientRect();
  8684. const nodeBox = getBox(rect.left, rect.top, rect.width, rect.height);
  8685. const elem = anchorInfo.node.getOr(component.element);
  8686. return calcNewAnchor(nodeBox, rootPoint, anchorInfo, origin, elem);
  8687. });
  8688. };
  8689. var NodeAnchor = [
  8690. required$1('node'),
  8691. required$1('root'),
  8692. option$3('bubble'),
  8693. schema$n(),
  8694. // chiefly MaxHeight.expandable()
  8695. defaulted('overrides', {}),
  8696. defaulted('showAbove', false),
  8697. output$1('placement', placement$2)
  8698. ];
  8699. const point = (element, offset) => ({
  8700. element,
  8701. offset
  8702. });
  8703. // NOTE: This only descends once.
  8704. const descendOnce$1 = (element, offset) => {
  8705. const children$1 = children(element);
  8706. if (children$1.length === 0) {
  8707. return point(element, offset);
  8708. }
  8709. else if (offset < children$1.length) {
  8710. return point(children$1[offset], 0);
  8711. }
  8712. else {
  8713. const last = children$1[children$1.length - 1];
  8714. const len = isText(last) ? get$a(last).length : children(last).length;
  8715. return point(last, len);
  8716. }
  8717. };
  8718. // A range from (a, 1) to (body, end) was giving the wrong bounds.
  8719. const descendOnce = (element, offset) => isText(element) ? point(element, offset) : descendOnce$1(element, offset);
  8720. const isSimRange = (detail) => detail.foffset !== undefined;
  8721. const getAnchorSelection = (win, anchorInfo) => {
  8722. // FIX TEST Test both providing a getSelection and not providing a getSelection
  8723. const getSelection = anchorInfo.getSelection.getOrThunk(() => () => getExact(win));
  8724. return getSelection().map((sel) => {
  8725. if (isSimRange(sel)) {
  8726. const modStart = descendOnce(sel.start, sel.soffset);
  8727. const modFinish = descendOnce(sel.finish, sel.foffset);
  8728. return SimSelection.range(modStart.element, modStart.offset, modFinish.element, modFinish.offset);
  8729. }
  8730. else {
  8731. return sel;
  8732. }
  8733. });
  8734. };
  8735. const placement$1 = (component, anchorInfo, origin) => {
  8736. const win = defaultView(anchorInfo.root).dom;
  8737. const rootPoint = getRootPoint(component, origin, anchorInfo);
  8738. const selectionBox = getAnchorSelection(win, anchorInfo).bind((sel) => {
  8739. // This represents the *visual* rectangle of the selection.
  8740. if (isSimRange(sel)) {
  8741. const optRect = getBounds$2(win, SimSelection.exactFromRange(sel)).orThunk(() => {
  8742. const zeroWidth$1 = SugarElement.fromText(zeroWidth);
  8743. before$1(sel.start, zeroWidth$1);
  8744. // Certain things like <p><br/></p> with (p, 0) or <br>) as collapsed selection do not return a client rectangle
  8745. const rect = getFirstRect(win, SimSelection.exact(zeroWidth$1, 0, zeroWidth$1, 1));
  8746. remove$7(zeroWidth$1);
  8747. return rect;
  8748. });
  8749. return optRect.bind((rawRect) => {
  8750. return getBox(rawRect.left, rawRect.top, rawRect.width, rawRect.height);
  8751. });
  8752. }
  8753. else {
  8754. const selectionRect = map$1(sel, (cell) => cell.dom.getBoundingClientRect());
  8755. const bounds = {
  8756. left: Math.min(selectionRect.firstCell.left, selectionRect.lastCell.left),
  8757. right: Math.max(selectionRect.firstCell.right, selectionRect.lastCell.right),
  8758. top: Math.min(selectionRect.firstCell.top, selectionRect.lastCell.top),
  8759. bottom: Math.max(selectionRect.firstCell.bottom, selectionRect.lastCell.bottom)
  8760. };
  8761. return getBox(bounds.left, bounds.top, bounds.right - bounds.left, bounds.bottom - bounds.top);
  8762. }
  8763. });
  8764. const targetElement = getAnchorSelection(win, anchorInfo)
  8765. .bind((sel) => {
  8766. if (isSimRange(sel)) {
  8767. return isElement$1(sel.start) ? Optional.some(sel.start) : parentElement(sel.start);
  8768. }
  8769. else {
  8770. return Optional.some(sel.firstCell);
  8771. }
  8772. });
  8773. const elem = targetElement.getOr(component.element);
  8774. return calcNewAnchor(selectionBox, rootPoint, anchorInfo, origin, elem);
  8775. };
  8776. var SelectionAnchor = [
  8777. option$3('getSelection'),
  8778. required$1('root'),
  8779. option$3('bubble'),
  8780. schema$n(),
  8781. defaulted('overrides', {}),
  8782. defaulted('showAbove', false),
  8783. output$1('placement', placement$1)
  8784. ];
  8785. /*
  8786. Layout for submenus;
  8787. Either left or right of the anchor menu item. Never above or below.
  8788. Aligned to the top or bottom of the anchor as appropriate.
  8789. */
  8790. const labelPrefix = 'link-layout';
  8791. // display element to the right, left edge against the right of the menu
  8792. const eastX = (anchor) => anchor.x + anchor.width;
  8793. // display element to the left, right edge against the left of the menu
  8794. const westX = (anchor, element) => anchor.x - element.width;
  8795. // display element pointing up, bottom edge against the bottom of the menu (usually to one side)
  8796. const northY = (anchor, element) => anchor.y - element.height + anchor.height;
  8797. // display element pointing down, top edge against the top of the menu (usually to one side)
  8798. const southY = (anchor) => anchor.y;
  8799. const southeast = (anchor, element, bubbles) => nu$5(eastX(anchor), southY(anchor), bubbles.southeast(), southeast$3(), "southeast" /* Placement.Southeast */, boundsRestriction(anchor, { left: 0 /* AnchorBoxBounds.RightEdge */, top: 2 /* AnchorBoxBounds.TopEdge */ }), labelPrefix);
  8800. const southwest = (anchor, element, bubbles) => nu$5(westX(anchor, element), southY(anchor), bubbles.southwest(), southwest$3(), "southwest" /* Placement.Southwest */, boundsRestriction(anchor, { right: 1 /* AnchorBoxBounds.LeftEdge */, top: 2 /* AnchorBoxBounds.TopEdge */ }), labelPrefix);
  8801. const northeast = (anchor, element, bubbles) => nu$5(eastX(anchor), northY(anchor, element), bubbles.northeast(), northeast$3(), "northeast" /* Placement.Northeast */, boundsRestriction(anchor, { left: 0 /* AnchorBoxBounds.RightEdge */, bottom: 3 /* AnchorBoxBounds.BottomEdge */ }), labelPrefix);
  8802. const northwest = (anchor, element, bubbles) => nu$5(westX(anchor, element), northY(anchor, element), bubbles.northwest(), northwest$3(), "northwest" /* Placement.Northwest */, boundsRestriction(anchor, { right: 1 /* AnchorBoxBounds.LeftEdge */, bottom: 3 /* AnchorBoxBounds.BottomEdge */ }), labelPrefix);
  8803. const all = () => [southeast, southwest, northeast, northwest];
  8804. const allRtl = () => [southwest, southeast, northwest, northeast];
  8805. const placement = (component, submenuInfo, origin) => {
  8806. const anchorBox = toBox(origin, submenuInfo.item.element);
  8807. const layouts = get$1(component.element, submenuInfo, all(), allRtl(),
  8808. // No default bottomToTop layouts currently needed
  8809. all(), allRtl(), Optional.none());
  8810. return Optional.some(nu$1({
  8811. anchorBox,
  8812. bubble: fallback(),
  8813. overrides: submenuInfo.overrides,
  8814. layouts
  8815. }));
  8816. };
  8817. var SubmenuAnchor = [
  8818. required$1('item'),
  8819. schema$n(),
  8820. defaulted('overrides', {}),
  8821. output$1('placement', placement)
  8822. ];
  8823. var AnchorSchema = choose$1('type', {
  8824. selection: SelectionAnchor,
  8825. node: NodeAnchor,
  8826. hotspot: HotspotAnchor,
  8827. submenu: SubmenuAnchor,
  8828. makeshift: MakeshiftAnchor
  8829. });
  8830. const TransitionSchema = [
  8831. requiredArrayOf('classes', string),
  8832. defaultedStringEnum('mode', 'all', ['all', 'layout', 'placement'])
  8833. ];
  8834. const PositionSchema = [
  8835. defaulted('useFixed', never),
  8836. option$3('getBounds')
  8837. ];
  8838. const PlacementSchema = [
  8839. requiredOf('anchor', AnchorSchema),
  8840. optionObjOf('transition', TransitionSchema)
  8841. ];
  8842. const getFixedOrigin = () => {
  8843. // Don't use window.innerWidth/innerHeight here, as we don't want to include scrollbars
  8844. // since the right/bottom position is based on the edge of the scrollbar not the window
  8845. const html = document.documentElement;
  8846. return fixed(0, 0, html.clientWidth, html.clientHeight);
  8847. };
  8848. const getRelativeOrigin = (component) => {
  8849. const position = absolute$3(component.element);
  8850. const bounds = component.element.dom.getBoundingClientRect();
  8851. // We think that this just needs to be kept consistent with Boxes.win. If we remove the scroll values from Boxes.win, we
  8852. // should change this to just bounds.left and bounds.top from getBoundingClientRect
  8853. return relative(position.left, position.top, bounds.width, bounds.height);
  8854. };
  8855. const place = (origin, anchoring, optBounds, placee, lastPlace, transition) => {
  8856. const anchor = box(anchoring.anchorBox, origin);
  8857. return simple(anchor, placee.element, anchoring.bubble, anchoring.layouts, lastPlace, optBounds, anchoring.overrides, transition);
  8858. };
  8859. const position$1 = (component, posConfig, posState, placee, placementSpec) => {
  8860. const optWithinBounds = Optional.none();
  8861. positionWithinBounds(component, posConfig, posState, placee, placementSpec, optWithinBounds);
  8862. };
  8863. const positionWithinBounds = (component, posConfig, posState, placee, placementSpec, optWithinBounds) => {
  8864. const placeeDetail = asRawOrDie$1('placement.info', objOf(PlacementSchema), placementSpec);
  8865. const anchorage = placeeDetail.anchor;
  8866. const element = placee.element;
  8867. const placeeState = posState.get(placee.uid);
  8868. // Preserve the focus as IE 11 loses it when setting visibility to hidden
  8869. preserve(() => {
  8870. // We set it to be fixed, so that it doesn't interfere with the layout of anything
  8871. // when calculating anchors
  8872. set$7(element, 'position', 'fixed');
  8873. const oldVisibility = getRaw(element, 'visibility');
  8874. set$7(element, 'visibility', 'hidden');
  8875. // We need to calculate the origin (esp. the bounding client rect) *after* we have done
  8876. // all the preprocessing of the component and placee. Otherwise, the relative positions
  8877. // (bottom and right) will be using the wrong dimensions
  8878. const origin = posConfig.useFixed() ? getFixedOrigin() : getRelativeOrigin(component);
  8879. anchorage.placement(component, anchorage, origin).each((anchoring) => {
  8880. // If "within bounds" is specified, it overrides any Positioning config. Otherwise, we
  8881. // use the Positioning config. We don't try to combine automatically here because they are
  8882. // sometimes serving different purposes. If the Positioning config getBounds needs to be
  8883. // combined with the optWithinBounds bounds, then it is the responsibility of the calling
  8884. // code to combine them, and pass in the combined value as optWithinBounds. The optWithinBounds
  8885. // will *always* override the Positioning config.
  8886. const optBounds = optWithinBounds.orThunk(() => posConfig.getBounds.map(apply$1));
  8887. // Place the element and then update the state for the placee
  8888. const newState = place(origin, anchoring, optBounds, placee, placeeState, placeeDetail.transition);
  8889. posState.set(placee.uid, newState);
  8890. });
  8891. oldVisibility.fold(() => {
  8892. remove$6(element, 'visibility');
  8893. }, (vis) => {
  8894. set$7(element, 'visibility', vis);
  8895. });
  8896. // We need to remove position: fixed put on by above code if it is not needed.
  8897. if (getRaw(element, 'left').isNone() &&
  8898. getRaw(element, 'top').isNone() &&
  8899. getRaw(element, 'right').isNone() &&
  8900. getRaw(element, 'bottom').isNone() &&
  8901. is$1(getRaw(element, 'position'), 'fixed')) {
  8902. remove$6(element, 'position');
  8903. }
  8904. }, element);
  8905. };
  8906. const getMode = (component, pConfig, _pState) => pConfig.useFixed() ? 'fixed' : 'absolute';
  8907. const reset = (component, pConfig, posState, placee) => {
  8908. const element = placee.element;
  8909. each$1(['position', 'left', 'right', 'top', 'bottom'], (prop) => remove$6(element, prop));
  8910. reset$2(element);
  8911. posState.clear(placee.uid);
  8912. };
  8913. var PositionApis = /*#__PURE__*/Object.freeze({
  8914. __proto__: null,
  8915. position: position$1,
  8916. positionWithinBounds: positionWithinBounds,
  8917. getMode: getMode,
  8918. reset: reset
  8919. });
  8920. const init$8 = () => {
  8921. let state = {};
  8922. const set = (id, data) => {
  8923. state[id] = data;
  8924. };
  8925. const get = (id) => get$h(state, id);
  8926. const clear = (id) => {
  8927. if (isNonNullable(id)) {
  8928. delete state[id];
  8929. }
  8930. else {
  8931. state = {};
  8932. }
  8933. };
  8934. return nu$4({
  8935. readState: () => state,
  8936. clear,
  8937. set,
  8938. get
  8939. });
  8940. };
  8941. var PositioningState = /*#__PURE__*/Object.freeze({
  8942. __proto__: null,
  8943. init: init$8
  8944. });
  8945. const Positioning = create$3({
  8946. fields: PositionSchema,
  8947. name: 'positioning',
  8948. active: ActivePosition,
  8949. apis: PositionApis,
  8950. state: PositioningState
  8951. });
  8952. const chooseChannels = (channels, message) => message.universal ? channels : filter$2(channels, (ch) => contains$2(message.channels, ch));
  8953. const events$8 = (receiveConfig) => derive$2([
  8954. run$1(receive(), (component, message) => {
  8955. const channelMap = receiveConfig.channels;
  8956. const channels = keys(channelMap);
  8957. // NOTE: Receiving event ignores the whole simulated event part.
  8958. // TODO: Think about the types for this, or find a better way for this to rely on receiving.
  8959. const receivingData = message;
  8960. const targetChannels = chooseChannels(channels, receivingData);
  8961. each$1(targetChannels, (ch) => {
  8962. const channelInfo = channelMap[ch];
  8963. const channelSchema = channelInfo.schema;
  8964. const data = asRawOrDie$1('channel[' + ch + '] data\nReceiver: ' + element(component.element), channelSchema, receivingData.data);
  8965. channelInfo.onReceive(component, data);
  8966. });
  8967. })
  8968. ]);
  8969. var ActiveReceiving = /*#__PURE__*/Object.freeze({
  8970. __proto__: null,
  8971. events: events$8
  8972. });
  8973. var ReceivingSchema = [
  8974. requiredOf('channels', setOf(
  8975. // Allow any keys.
  8976. Result.value, objOfOnly([
  8977. onStrictHandler('onReceive'),
  8978. defaulted('schema', anyValue())
  8979. ])))
  8980. ];
  8981. const Receiving = create$3({
  8982. fields: ReceivingSchema,
  8983. name: 'receiving',
  8984. active: ActiveReceiving
  8985. });
  8986. const events$7 = (reflectingConfig, reflectingState) => {
  8987. const update = (component, data) => {
  8988. reflectingConfig.updateState.each((updateState) => {
  8989. const newState = updateState(component, data);
  8990. reflectingState.set(newState);
  8991. });
  8992. // FIX: Partial duplication of Replacing + Receiving
  8993. reflectingConfig.renderComponents.each((renderComponents) => {
  8994. const newComponents = renderComponents(data, reflectingState.get());
  8995. const replacer = reflectingConfig.reuseDom ? withReuse : withoutReuse;
  8996. replacer(component, newComponents);
  8997. });
  8998. };
  8999. return derive$2([
  9000. run$1(receive(), (component, message) => {
  9001. // NOTE: Receiving event ignores the whole simulated event part.
  9002. // TODO: Think about the types for this, or find a better way for this to rely on receiving.
  9003. const receivingData = message;
  9004. if (!receivingData.universal) {
  9005. const channel = reflectingConfig.channel;
  9006. if (contains$2(receivingData.channels, channel)) {
  9007. update(component, receivingData.data);
  9008. }
  9009. }
  9010. }),
  9011. runOnAttached((comp, _se) => {
  9012. reflectingConfig.initialData.each((rawData) => {
  9013. update(comp, rawData);
  9014. });
  9015. })
  9016. ]);
  9017. };
  9018. var ActiveReflecting = /*#__PURE__*/Object.freeze({
  9019. __proto__: null,
  9020. events: events$7
  9021. });
  9022. const getState$1 = (component, replaceConfig, reflectState) => reflectState;
  9023. var ReflectingApis = /*#__PURE__*/Object.freeze({
  9024. __proto__: null,
  9025. getState: getState$1
  9026. });
  9027. var ReflectingSchema = [
  9028. required$1('channel'),
  9029. option$3('renderComponents'),
  9030. option$3('updateState'),
  9031. option$3('initialData'),
  9032. defaultedBoolean('reuseDom', true)
  9033. ];
  9034. const init$7 = () => {
  9035. const cell = Cell(Optional.none());
  9036. const clear = () => cell.set(Optional.none());
  9037. const readState = () => cell.get().getOr('none');
  9038. return {
  9039. readState,
  9040. get: cell.get,
  9041. set: cell.set,
  9042. clear
  9043. };
  9044. };
  9045. var ReflectingState = /*#__PURE__*/Object.freeze({
  9046. __proto__: null,
  9047. init: init$7
  9048. });
  9049. const Reflecting = create$3({
  9050. fields: ReflectingSchema,
  9051. name: 'reflecting',
  9052. active: ActiveReflecting,
  9053. apis: ReflectingApis,
  9054. state: ReflectingState
  9055. });
  9056. // NOTE: A sandbox should not start as part of the world. It is expected to be
  9057. // added to the sink on rebuild.
  9058. const rebuild = (sandbox, sConfig, sState, data) => {
  9059. sState.get().each((_data) => {
  9060. // If currently has data, so it hasn't been removed yet. It is
  9061. // being "re-opened"
  9062. detachChildren(sandbox);
  9063. });
  9064. const point = sConfig.getAttachPoint(sandbox);
  9065. attach(point, sandbox);
  9066. // Must be after the sandbox is in the system
  9067. const built = sandbox.getSystem().build(data);
  9068. attach(sandbox, built);
  9069. sState.set(built);
  9070. return built;
  9071. };
  9072. // Open sandbox transfers focus to the opened menu
  9073. const open$1 = (sandbox, sConfig, sState, data) => {
  9074. const newState = rebuild(sandbox, sConfig, sState, data);
  9075. sConfig.onOpen(sandbox, newState);
  9076. return newState;
  9077. };
  9078. const setContent = (sandbox, sConfig, sState, data) => sState.get().map(() => rebuild(sandbox, sConfig, sState, data));
  9079. // TODO AP-191 write a test for openWhileCloaked
  9080. const openWhileCloaked = (sandbox, sConfig, sState, data, transaction) => {
  9081. cloak(sandbox, sConfig);
  9082. open$1(sandbox, sConfig, sState, data);
  9083. transaction();
  9084. decloak(sandbox, sConfig);
  9085. };
  9086. const close$1 = (sandbox, sConfig, sState) => {
  9087. sState.get().each((data) => {
  9088. detachChildren(sandbox);
  9089. detach(sandbox);
  9090. sConfig.onClose(sandbox, data);
  9091. sState.clear();
  9092. });
  9093. };
  9094. const isOpen$1 = (_sandbox, _sConfig, sState) => sState.isOpen();
  9095. const isPartOf$1 = (sandbox, sConfig, sState, queryElem) => isOpen$1(sandbox, sConfig, sState) && sState.get().exists((data) => sConfig.isPartOf(sandbox, data, queryElem));
  9096. const getState = (_sandbox, _sConfig, sState) => sState.get();
  9097. const store = (sandbox, cssKey, attr, newValue) => {
  9098. getRaw(sandbox.element, cssKey).fold(() => {
  9099. remove$8(sandbox.element, attr);
  9100. }, (v) => {
  9101. set$9(sandbox.element, attr, v);
  9102. });
  9103. set$7(sandbox.element, cssKey, newValue);
  9104. };
  9105. const restore = (sandbox, cssKey, attr) => {
  9106. getOpt(sandbox.element, attr).fold(() => remove$6(sandbox.element, cssKey), (oldValue) => set$7(sandbox.element, cssKey, oldValue));
  9107. };
  9108. const cloak = (sandbox, sConfig, _sState) => {
  9109. const sink = sConfig.getAttachPoint(sandbox);
  9110. // Use the positioning mode of the sink, so that it does not interfere with the sink's positioning
  9111. // We add it here to stop it causing layout problems.
  9112. set$7(sandbox.element, 'position', Positioning.getMode(sink));
  9113. store(sandbox, 'visibility', sConfig.cloakVisibilityAttr, 'hidden');
  9114. };
  9115. const hasPosition = (element) => exists(['top', 'left', 'right', 'bottom'], (pos) => getRaw(element, pos).isSome());
  9116. const decloak = (sandbox, sConfig, _sState) => {
  9117. if (!hasPosition(sandbox.element)) {
  9118. // If a position value was not added to the sandbox during cloaking, remove it
  9119. // otherwise certain position values (absolute, relative) will impact the child that _was_ positioned
  9120. remove$6(sandbox.element, 'position');
  9121. }
  9122. restore(sandbox, 'visibility', sConfig.cloakVisibilityAttr);
  9123. };
  9124. var SandboxApis = /*#__PURE__*/Object.freeze({
  9125. __proto__: null,
  9126. cloak: cloak,
  9127. decloak: decloak,
  9128. open: open$1,
  9129. openWhileCloaked: openWhileCloaked,
  9130. close: close$1,
  9131. isOpen: isOpen$1,
  9132. isPartOf: isPartOf$1,
  9133. getState: getState,
  9134. setContent: setContent
  9135. });
  9136. const events$6 = (sandboxConfig, sandboxState) => derive$2([
  9137. run$1(sandboxClose(), (sandbox, _simulatedEvent) => {
  9138. close$1(sandbox, sandboxConfig, sandboxState);
  9139. })
  9140. ]);
  9141. var ActiveSandbox = /*#__PURE__*/Object.freeze({
  9142. __proto__: null,
  9143. events: events$6
  9144. });
  9145. var SandboxSchema = [
  9146. onHandler('onOpen'),
  9147. onHandler('onClose'),
  9148. // Maybe this should be optional
  9149. required$1('isPartOf'),
  9150. required$1('getAttachPoint'),
  9151. defaulted('cloakVisibilityAttr', 'data-precloak-visibility')
  9152. ];
  9153. const init$6 = () => {
  9154. const contents = value$2();
  9155. const readState = constant$1('not-implemented');
  9156. return nu$4({
  9157. readState,
  9158. isOpen: contents.isSet,
  9159. clear: contents.clear,
  9160. set: contents.set,
  9161. get: contents.get
  9162. });
  9163. };
  9164. var SandboxState = /*#__PURE__*/Object.freeze({
  9165. __proto__: null,
  9166. init: init$6
  9167. });
  9168. const Sandboxing = create$3({
  9169. fields: SandboxSchema,
  9170. name: 'sandboxing',
  9171. active: ActiveSandbox,
  9172. apis: SandboxApis,
  9173. state: SandboxState
  9174. });
  9175. const getAnimationRoot = (component, slideConfig) => slideConfig.getAnimationRoot.fold(() => component.element, (get) => get(component));
  9176. const getDimensionProperty = (slideConfig) => slideConfig.dimension.property;
  9177. const getDimension = (slideConfig, elem) => slideConfig.dimension.getDimension(elem);
  9178. const disableTransitions = (component, slideConfig) => {
  9179. const root = getAnimationRoot(component, slideConfig);
  9180. remove$2(root, [slideConfig.shrinkingClass, slideConfig.growingClass]);
  9181. };
  9182. const setShrunk = (component, slideConfig) => {
  9183. remove$3(component.element, slideConfig.openClass);
  9184. add$2(component.element, slideConfig.closedClass);
  9185. set$7(component.element, getDimensionProperty(slideConfig), '0px');
  9186. reflow(component.element);
  9187. };
  9188. const setGrown = (component, slideConfig) => {
  9189. remove$3(component.element, slideConfig.closedClass);
  9190. add$2(component.element, slideConfig.openClass);
  9191. remove$6(component.element, getDimensionProperty(slideConfig));
  9192. };
  9193. const doImmediateShrink = (component, slideConfig, slideState, _calculatedSize) => {
  9194. slideState.setCollapsed();
  9195. // Force current dimension to begin transition
  9196. set$7(component.element, getDimensionProperty(slideConfig), getDimension(slideConfig, component.element));
  9197. // TINY-8710: we don't think reflow is required (as has been done elsewhere) as the animation is not needed
  9198. disableTransitions(component, slideConfig);
  9199. setShrunk(component, slideConfig);
  9200. slideConfig.onStartShrink(component);
  9201. slideConfig.onShrunk(component);
  9202. };
  9203. const doStartShrink = (component, slideConfig, slideState, calculatedSize) => {
  9204. const size = calculatedSize.getOrThunk(() => getDimension(slideConfig, component.element));
  9205. slideState.setCollapsed();
  9206. // Force current dimension to begin transition
  9207. set$7(component.element, getDimensionProperty(slideConfig), size);
  9208. reflow(component.element);
  9209. const root = getAnimationRoot(component, slideConfig);
  9210. remove$3(root, slideConfig.growingClass);
  9211. add$2(root, slideConfig.shrinkingClass); // enable transitions
  9212. setShrunk(component, slideConfig);
  9213. slideConfig.onStartShrink(component);
  9214. };
  9215. // A "smartShrink" will do an immediate shrink if no shrinking is scheduled to happen
  9216. const doStartSmartShrink = (component, slideConfig, slideState) => {
  9217. const size = getDimension(slideConfig, component.element);
  9218. const shrinker = size === '0px' ? doImmediateShrink : doStartShrink;
  9219. shrinker(component, slideConfig, slideState, Optional.some(size));
  9220. };
  9221. // Showing is complex due to the inability to transition to "auto".
  9222. // We also can't cache the dimension as the parents may have resized since it was last shown.
  9223. const doStartGrow = (component, slideConfig, slideState) => {
  9224. // Start the growing animation styles
  9225. const root = getAnimationRoot(component, slideConfig);
  9226. // Record whether this is interrupting a shrink and its current size
  9227. const wasShrinking = has(root, slideConfig.shrinkingClass);
  9228. const beforeSize = getDimension(slideConfig, component.element);
  9229. setGrown(component, slideConfig);
  9230. const fullSize = getDimension(slideConfig, component.element);
  9231. // If the grow is interrupting a shrink, use the size from before the grow as the start size
  9232. // And reflow so that the animation works.
  9233. const startPartialGrow = () => {
  9234. set$7(component.element, getDimensionProperty(slideConfig), beforeSize);
  9235. reflow(component.element);
  9236. };
  9237. // If the grow is not interrupting a shrink, start from 0 (shrunk)
  9238. const startCompleteGrow = () => {
  9239. setShrunk(component, slideConfig);
  9240. };
  9241. // Determine what the initial size for the grow operation should be.
  9242. const setStartSize = wasShrinking ? startPartialGrow : startCompleteGrow;
  9243. setStartSize();
  9244. remove$3(root, slideConfig.shrinkingClass);
  9245. add$2(root, slideConfig.growingClass);
  9246. setGrown(component, slideConfig);
  9247. set$7(component.element, getDimensionProperty(slideConfig), fullSize);
  9248. slideState.setExpanded();
  9249. slideConfig.onStartGrow(component);
  9250. };
  9251. const refresh$3 = (component, slideConfig, slideState) => {
  9252. if (slideState.isExpanded()) {
  9253. remove$6(component.element, getDimensionProperty(slideConfig));
  9254. const fullSize = getDimension(slideConfig, component.element);
  9255. set$7(component.element, getDimensionProperty(slideConfig), fullSize);
  9256. }
  9257. };
  9258. const grow = (component, slideConfig, slideState) => {
  9259. if (!slideState.isExpanded()) {
  9260. doStartGrow(component, slideConfig, slideState);
  9261. }
  9262. };
  9263. const shrink = (component, slideConfig, slideState) => {
  9264. if (slideState.isExpanded()) {
  9265. doStartSmartShrink(component, slideConfig, slideState);
  9266. }
  9267. };
  9268. const immediateShrink = (component, slideConfig, slideState) => {
  9269. if (slideState.isExpanded()) {
  9270. doImmediateShrink(component, slideConfig, slideState);
  9271. }
  9272. };
  9273. const hasGrown = (component, slideConfig, slideState) => slideState.isExpanded();
  9274. const hasShrunk = (component, slideConfig, slideState) => slideState.isCollapsed();
  9275. const isGrowing = (component, slideConfig, _slideState) => {
  9276. const root = getAnimationRoot(component, slideConfig);
  9277. return has(root, slideConfig.growingClass) === true;
  9278. };
  9279. const isShrinking = (component, slideConfig, _slideState) => {
  9280. const root = getAnimationRoot(component, slideConfig);
  9281. return has(root, slideConfig.shrinkingClass) === true;
  9282. };
  9283. const isTransitioning = (component, slideConfig, slideState) => isGrowing(component, slideConfig) || isShrinking(component, slideConfig);
  9284. const toggleGrow = (component, slideConfig, slideState) => {
  9285. const f = slideState.isExpanded() ? doStartSmartShrink : doStartGrow;
  9286. f(component, slideConfig, slideState);
  9287. };
  9288. const immediateGrow = (component, slideConfig, slideState) => {
  9289. if (!slideState.isExpanded()) {
  9290. setGrown(component, slideConfig);
  9291. set$7(component.element, getDimensionProperty(slideConfig), getDimension(slideConfig, component.element));
  9292. // TINY-8710: we don't think reflow is required (as has been done elsewhere) as the animation is not needed
  9293. // Keep disableTransition to handle the case where it's part way through transitioning
  9294. disableTransitions(component, slideConfig);
  9295. slideState.setExpanded();
  9296. slideConfig.onStartGrow(component);
  9297. slideConfig.onGrown(component);
  9298. }
  9299. };
  9300. var SlidingApis = /*#__PURE__*/Object.freeze({
  9301. __proto__: null,
  9302. refresh: refresh$3,
  9303. grow: grow,
  9304. shrink: shrink,
  9305. immediateShrink: immediateShrink,
  9306. hasGrown: hasGrown,
  9307. hasShrunk: hasShrunk,
  9308. isGrowing: isGrowing,
  9309. isShrinking: isShrinking,
  9310. isTransitioning: isTransitioning,
  9311. toggleGrow: toggleGrow,
  9312. disableTransitions: disableTransitions,
  9313. immediateGrow: immediateGrow
  9314. });
  9315. const exhibit$3 = (base, slideConfig, _slideState) => {
  9316. const expanded = slideConfig.expanded;
  9317. return expanded ? nu$2({
  9318. classes: [slideConfig.openClass],
  9319. styles: {}
  9320. }) : nu$2({
  9321. classes: [slideConfig.closedClass],
  9322. styles: wrap(slideConfig.dimension.property, '0px')
  9323. });
  9324. };
  9325. const events$5 = (slideConfig, slideState) => derive$2([
  9326. runOnSource(transitionend(), (component, simulatedEvent) => {
  9327. const raw = simulatedEvent.event.raw;
  9328. // This will fire for all transitions, we're only interested in the dimension completion on source
  9329. if (raw.propertyName === slideConfig.dimension.property) {
  9330. disableTransitions(component, slideConfig); // disable transitions immediately (Safari animates the dimension removal below)
  9331. if (slideState.isExpanded()) {
  9332. remove$6(component.element, slideConfig.dimension.property);
  9333. } // when showing, remove the dimension so it is responsive
  9334. const notify = slideState.isExpanded() ? slideConfig.onGrown : slideConfig.onShrunk;
  9335. notify(component);
  9336. }
  9337. })
  9338. ]);
  9339. var ActiveSliding = /*#__PURE__*/Object.freeze({
  9340. __proto__: null,
  9341. exhibit: exhibit$3,
  9342. events: events$5
  9343. });
  9344. var SlidingSchema = [
  9345. required$1('closedClass'),
  9346. required$1('openClass'),
  9347. required$1('shrinkingClass'),
  9348. required$1('growingClass'),
  9349. // Element which shrinking and growing animations
  9350. option$3('getAnimationRoot'),
  9351. onHandler('onShrunk'),
  9352. onHandler('onStartShrink'),
  9353. onHandler('onGrown'),
  9354. onHandler('onStartGrow'),
  9355. defaulted('expanded', false),
  9356. requiredOf('dimension', choose$1('property', {
  9357. width: [
  9358. output$1('property', 'width'),
  9359. output$1('getDimension', (elem) => get$c(elem) + 'px')
  9360. ],
  9361. height: [
  9362. output$1('property', 'height'),
  9363. output$1('getDimension', (elem) => get$d(elem) + 'px')
  9364. ]
  9365. }))
  9366. ];
  9367. const init$5 = (spec) => {
  9368. const state = Cell(spec.expanded);
  9369. const readState = () => 'expanded: ' + state.get();
  9370. return nu$4({
  9371. isExpanded: () => state.get() === true,
  9372. isCollapsed: () => state.get() === false,
  9373. setCollapsed: curry(state.set, false),
  9374. setExpanded: curry(state.set, true),
  9375. readState
  9376. });
  9377. };
  9378. var SlidingState = /*#__PURE__*/Object.freeze({
  9379. __proto__: null,
  9380. init: init$5
  9381. });
  9382. const Sliding = create$3({
  9383. fields: SlidingSchema,
  9384. name: 'sliding',
  9385. active: ActiveSliding,
  9386. apis: SlidingApis,
  9387. state: SlidingState
  9388. });
  9389. const events$4 = (streamConfig, streamState) => {
  9390. const streams = streamConfig.stream.streams;
  9391. const processor = streams.setup(streamConfig, streamState);
  9392. return derive$2([
  9393. run$1(streamConfig.event, processor),
  9394. runOnDetached(() => streamState.cancel())
  9395. ].concat(streamConfig.cancelEvent.map((e) => [
  9396. run$1(e, () => streamState.cancel())
  9397. ]).getOr([])));
  9398. };
  9399. var ActiveStreaming = /*#__PURE__*/Object.freeze({
  9400. __proto__: null,
  9401. events: events$4
  9402. });
  9403. const throttle = (_config) => {
  9404. const state = Cell(null);
  9405. const readState = () => ({
  9406. timer: state.get() !== null ? 'set' : 'unset'
  9407. });
  9408. const setTimer = (t) => {
  9409. state.set(t);
  9410. };
  9411. const cancel = () => {
  9412. const t = state.get();
  9413. if (t !== null) {
  9414. t.cancel();
  9415. }
  9416. };
  9417. return nu$4({
  9418. readState,
  9419. setTimer,
  9420. cancel
  9421. });
  9422. };
  9423. const init$4 = (spec) => spec.stream.streams.state(spec);
  9424. var StreamingState = /*#__PURE__*/Object.freeze({
  9425. __proto__: null,
  9426. throttle: throttle,
  9427. init: init$4
  9428. });
  9429. const setup$e = (streamInfo, streamState) => {
  9430. const sInfo = streamInfo.stream;
  9431. const throttler = last(streamInfo.onStream, sInfo.delay);
  9432. streamState.setTimer(throttler);
  9433. return (component, simulatedEvent) => {
  9434. throttler.throttle(component, simulatedEvent);
  9435. if (sInfo.stopEvent) {
  9436. simulatedEvent.stop();
  9437. }
  9438. };
  9439. };
  9440. var StreamingSchema = [
  9441. requiredOf('stream', choose$1('mode', {
  9442. throttle: [
  9443. required$1('delay'),
  9444. defaulted('stopEvent', true),
  9445. output$1('streams', {
  9446. setup: setup$e,
  9447. state: throttle
  9448. })
  9449. ]
  9450. })),
  9451. defaulted('event', 'input'),
  9452. option$3('cancelEvent'),
  9453. onStrictHandler('onStream')
  9454. ];
  9455. const Streaming = create$3({
  9456. fields: StreamingSchema,
  9457. name: 'streaming',
  9458. active: ActiveStreaming,
  9459. state: StreamingState
  9460. });
  9461. const exhibit$2 = (base, tabConfig) => nu$2({
  9462. attributes: wrapAll([
  9463. { key: tabConfig.tabAttr, value: 'true' }
  9464. ])
  9465. });
  9466. var ActiveTabstopping = /*#__PURE__*/Object.freeze({
  9467. __proto__: null,
  9468. exhibit: exhibit$2
  9469. });
  9470. var TabstopSchema = [
  9471. defaulted('tabAttr', 'data-alloy-tabstop')
  9472. ];
  9473. const Tabstopping = create$3({
  9474. fields: TabstopSchema,
  9475. name: 'tabstopping',
  9476. active: ActiveTabstopping
  9477. });
  9478. const updateAriaState = (component, toggleConfig, toggleState) => {
  9479. const ariaInfo = toggleConfig.aria;
  9480. ariaInfo.update(component, ariaInfo, toggleState.get());
  9481. };
  9482. const updateClass = (component, toggleConfig, toggleState) => {
  9483. toggleConfig.toggleClass.each((toggleClass) => {
  9484. if (toggleState.get()) {
  9485. add$2(component.element, toggleClass);
  9486. }
  9487. else {
  9488. remove$3(component.element, toggleClass);
  9489. }
  9490. });
  9491. };
  9492. const set = (component, toggleConfig, toggleState, state) => {
  9493. const initialState = toggleState.get();
  9494. toggleState.set(state);
  9495. updateClass(component, toggleConfig, toggleState);
  9496. updateAriaState(component, toggleConfig, toggleState);
  9497. if (initialState !== state) {
  9498. toggleConfig.onToggled(component, state);
  9499. }
  9500. };
  9501. const toggle$2 = (component, toggleConfig, toggleState) => {
  9502. set(component, toggleConfig, toggleState, !toggleState.get());
  9503. };
  9504. const on = (component, toggleConfig, toggleState) => {
  9505. set(component, toggleConfig, toggleState, true);
  9506. };
  9507. const off = (component, toggleConfig, toggleState) => {
  9508. set(component, toggleConfig, toggleState, false);
  9509. };
  9510. const isOn = (component, toggleConfig, toggleState) => toggleState.get();
  9511. const onLoad = (component, toggleConfig, toggleState) => {
  9512. // There used to be a bit of code in here that would only overwrite
  9513. // the attribute if it didn't have a current value. I can't remember
  9514. // what case that was for, so I'm removing it until it is required.
  9515. set(component, toggleConfig, toggleState, toggleConfig.selected);
  9516. };
  9517. var ToggleApis = /*#__PURE__*/Object.freeze({
  9518. __proto__: null,
  9519. onLoad: onLoad,
  9520. toggle: toggle$2,
  9521. isOn: isOn,
  9522. on: on,
  9523. off: off,
  9524. set: set
  9525. });
  9526. const exhibit$1 = () => nu$2({});
  9527. const events$3 = (toggleConfig, toggleState) => {
  9528. const execute = executeEvent(toggleConfig, toggleState, toggle$2);
  9529. const load = loadEvent(toggleConfig, toggleState, onLoad);
  9530. return derive$2(flatten([
  9531. toggleConfig.toggleOnExecute ? [execute] : [],
  9532. [load]
  9533. ]));
  9534. };
  9535. var ActiveToggle = /*#__PURE__*/Object.freeze({
  9536. __proto__: null,
  9537. exhibit: exhibit$1,
  9538. events: events$3
  9539. });
  9540. const updatePressed = (component, ariaInfo, status) => {
  9541. set$9(component.element, 'aria-pressed', status);
  9542. if (ariaInfo.syncWithExpanded) {
  9543. updateExpanded(component, ariaInfo, status);
  9544. }
  9545. };
  9546. const updateSelected = (component, ariaInfo, status) => {
  9547. set$9(component.element, 'aria-selected', status);
  9548. };
  9549. const updateChecked = (component, ariaInfo, status) => {
  9550. set$9(component.element, 'aria-checked', status);
  9551. };
  9552. const updateExpanded = (component, ariaInfo, status) => {
  9553. set$9(component.element, 'aria-expanded', status);
  9554. };
  9555. var ToggleSchema = [
  9556. defaulted('selected', false),
  9557. option$3('toggleClass'),
  9558. defaulted('toggleOnExecute', true),
  9559. onHandler('onToggled'),
  9560. defaultedOf('aria', {
  9561. mode: 'none'
  9562. }, choose$1('mode', {
  9563. pressed: [
  9564. defaulted('syncWithExpanded', false),
  9565. output$1('update', updatePressed)
  9566. ],
  9567. checked: [
  9568. output$1('update', updateChecked)
  9569. ],
  9570. expanded: [
  9571. output$1('update', updateExpanded)
  9572. ],
  9573. selected: [
  9574. output$1('update', updateSelected)
  9575. ],
  9576. none: [
  9577. output$1('update', noop)
  9578. ]
  9579. }))
  9580. ];
  9581. const Toggling = create$3({
  9582. fields: ToggleSchema,
  9583. name: 'toggling',
  9584. active: ActiveToggle,
  9585. apis: ToggleApis,
  9586. state: SetupBehaviourCellState(false)
  9587. });
  9588. const ExclusivityChannel = generate$6('tooltip.exclusive');
  9589. const ShowTooltipEvent = generate$6('tooltip.show');
  9590. const HideTooltipEvent = generate$6('tooltip.hide');
  9591. const ImmediateHideTooltipEvent = generate$6('tooltip.immediateHide');
  9592. const ImmediateShowTooltipEvent = generate$6('tooltip.immediateShow');
  9593. const hideAllExclusive = (component, _tConfig, _tState) => {
  9594. component.getSystem().broadcastOn([ExclusivityChannel], {});
  9595. };
  9596. const setComponents = (_component, _tConfig, tState, specs) => {
  9597. tState.getTooltip().each((tooltip) => {
  9598. if (tooltip.getSystem().isConnected()) {
  9599. Replacing.set(tooltip, specs);
  9600. }
  9601. });
  9602. };
  9603. const isEnabled = (_component, _tConfig, tState) => tState.isEnabled();
  9604. const setEnabled = (_component, _tConfig, tState, enabled) => tState.setEnabled(enabled);
  9605. const immediateOpenClose = (component, _tConfig, _tState, open) => emit(component, open ? ImmediateShowTooltipEvent : ImmediateHideTooltipEvent);
  9606. var TooltippingApis = /*#__PURE__*/Object.freeze({
  9607. __proto__: null,
  9608. hideAllExclusive: hideAllExclusive,
  9609. immediateOpenClose: immediateOpenClose,
  9610. isEnabled: isEnabled,
  9611. setComponents: setComponents,
  9612. setEnabled: setEnabled
  9613. });
  9614. const events$2 = (tooltipConfig, state) => {
  9615. const hide = (comp) => {
  9616. state.getTooltip().each((p) => {
  9617. if (p.getSystem().isConnected()) {
  9618. detach(p);
  9619. tooltipConfig.onHide(comp, p);
  9620. state.clearTooltip();
  9621. }
  9622. });
  9623. state.clearTimer();
  9624. };
  9625. const show = (comp) => {
  9626. if (!state.isShowing() && state.isEnabled()) {
  9627. hideAllExclusive(comp);
  9628. const sink = tooltipConfig.lazySink(comp).getOrDie();
  9629. const popup = comp.getSystem().build({
  9630. dom: tooltipConfig.tooltipDom,
  9631. components: tooltipConfig.tooltipComponents,
  9632. events: derive$2(tooltipConfig.mode === 'normal'
  9633. ? [
  9634. run$1(mouseover(), (_) => {
  9635. emit(comp, ShowTooltipEvent);
  9636. }),
  9637. run$1(mouseout(), (_) => {
  9638. emit(comp, HideTooltipEvent);
  9639. })
  9640. ]
  9641. : []),
  9642. behaviours: derive$1([
  9643. Replacing.config({})
  9644. ])
  9645. });
  9646. state.setTooltip(popup);
  9647. attach(sink, popup);
  9648. tooltipConfig.onShow(comp, popup);
  9649. Positioning.position(sink, popup, { anchor: tooltipConfig.anchor(comp) });
  9650. }
  9651. };
  9652. const reposition = (comp) => {
  9653. state.getTooltip().each((tooltip) => {
  9654. const sink = tooltipConfig.lazySink(comp).getOrDie();
  9655. Positioning.position(sink, tooltip, { anchor: tooltipConfig.anchor(comp) });
  9656. });
  9657. };
  9658. const getEvents = () => {
  9659. switch (tooltipConfig.mode) {
  9660. case 'normal':
  9661. return [
  9662. run$1(focusin(), (comp) => {
  9663. emit(comp, ImmediateShowTooltipEvent);
  9664. }),
  9665. run$1(postBlur(), (comp) => {
  9666. emit(comp, ImmediateHideTooltipEvent);
  9667. }),
  9668. run$1(mouseover(), (comp) => {
  9669. emit(comp, ShowTooltipEvent);
  9670. }),
  9671. run$1(mouseout(), (comp) => {
  9672. emit(comp, HideTooltipEvent);
  9673. })
  9674. ];
  9675. case 'follow-highlight':
  9676. return [
  9677. run$1(highlight$1(), (comp, _se) => {
  9678. emit(comp, ShowTooltipEvent);
  9679. }),
  9680. run$1(dehighlight$1(), (comp) => {
  9681. emit(comp, HideTooltipEvent);
  9682. })
  9683. ];
  9684. case 'children-normal':
  9685. return [
  9686. run$1(focusin(), (comp, se) => {
  9687. search(comp.element).each((_) => {
  9688. if (is(se.event.target, '[data-mce-tooltip]')) {
  9689. state.getTooltip().fold(() => {
  9690. emit(comp, ImmediateShowTooltipEvent);
  9691. }, (tooltip) => {
  9692. if (state.isShowing()) {
  9693. tooltipConfig.onShow(comp, tooltip);
  9694. reposition(comp);
  9695. }
  9696. });
  9697. }
  9698. });
  9699. }),
  9700. run$1(postBlur(), (comp) => {
  9701. search(comp.element).fold(() => {
  9702. emit(comp, ImmediateHideTooltipEvent);
  9703. }, noop);
  9704. }),
  9705. run$1(mouseover(), (comp) => {
  9706. descendant(comp.element, '[data-mce-tooltip]:hover').each((_) => {
  9707. state.getTooltip().fold(() => {
  9708. emit(comp, ShowTooltipEvent);
  9709. }, (tooltip) => {
  9710. if (state.isShowing()) {
  9711. tooltipConfig.onShow(comp, tooltip);
  9712. reposition(comp);
  9713. }
  9714. });
  9715. });
  9716. }),
  9717. run$1(mouseout(), (comp) => {
  9718. descendant(comp.element, '[data-mce-tooltip]:hover').fold(() => {
  9719. emit(comp, HideTooltipEvent);
  9720. }, noop);
  9721. }),
  9722. ];
  9723. default:
  9724. return [
  9725. run$1(focusin(), (comp, se) => {
  9726. search(comp.element).each((_) => {
  9727. if (is(se.event.target, '[data-mce-tooltip]')) {
  9728. state.getTooltip().fold(() => {
  9729. emit(comp, ImmediateShowTooltipEvent);
  9730. }, (tooltip) => {
  9731. if (state.isShowing()) {
  9732. tooltipConfig.onShow(comp, tooltip);
  9733. reposition(comp);
  9734. }
  9735. });
  9736. }
  9737. });
  9738. }),
  9739. run$1(postBlur(), (comp) => {
  9740. search(comp.element).fold(() => {
  9741. emit(comp, ImmediateHideTooltipEvent);
  9742. }, noop);
  9743. }),
  9744. ];
  9745. }
  9746. };
  9747. return derive$2(flatten([
  9748. [
  9749. runOnInit((component) => {
  9750. tooltipConfig.onSetup(component);
  9751. }),
  9752. run$1(ShowTooltipEvent, (comp) => {
  9753. state.resetTimer(() => {
  9754. show(comp);
  9755. }, tooltipConfig.delayForShow());
  9756. }),
  9757. run$1(HideTooltipEvent, (comp) => {
  9758. state.resetTimer(() => {
  9759. hide(comp);
  9760. }, tooltipConfig.delayForHide());
  9761. }),
  9762. run$1(ImmediateShowTooltipEvent, (comp) => {
  9763. state.resetTimer(() => {
  9764. show(comp);
  9765. }, 0);
  9766. }),
  9767. run$1(ImmediateHideTooltipEvent, (comp) => {
  9768. state.resetTimer(() => {
  9769. hide(comp);
  9770. }, 0);
  9771. }),
  9772. run$1(receive(), (comp, message) => {
  9773. // TODO: Think about the types for this, or find a better way for this
  9774. // to rely on receiving.
  9775. const receivingData = message;
  9776. if (!receivingData.universal) {
  9777. if (contains$2(receivingData.channels, ExclusivityChannel) || contains$2(receivingData.channels, closeTooltips())) {
  9778. if (receivingData.data.closedTooltip && state.isShowing()) {
  9779. receivingData.data.closedTooltip();
  9780. }
  9781. hide(comp);
  9782. }
  9783. }
  9784. }),
  9785. runOnDetached((comp) => {
  9786. hide(comp);
  9787. })
  9788. ],
  9789. (getEvents())
  9790. ]));
  9791. };
  9792. var ActiveTooltipping = /*#__PURE__*/Object.freeze({
  9793. __proto__: null,
  9794. events: events$2
  9795. });
  9796. var TooltippingSchema = [
  9797. required$1('lazySink'),
  9798. required$1('tooltipDom'),
  9799. defaulted('exclusive', true),
  9800. defaulted('tooltipComponents', []),
  9801. defaultedFunction('delayForShow', constant$1(300)),
  9802. defaultedFunction('delayForHide', constant$1(100)),
  9803. defaultedFunction('onSetup', noop),
  9804. defaultedStringEnum('mode', 'normal', ['normal', 'follow-highlight', 'children-keyboard-focus', 'children-normal']),
  9805. defaulted('anchor', (comp) => ({
  9806. type: 'hotspot',
  9807. hotspot: comp,
  9808. layouts: {
  9809. onLtr: constant$1([south$2, north$2, southeast$2, northeast$2, southwest$2, northwest$2]),
  9810. onRtl: constant$1([south$2, north$2, southeast$2, northeast$2, southwest$2, northwest$2])
  9811. },
  9812. bubble: nu$6(0, -2, {}),
  9813. })),
  9814. onHandler('onHide'),
  9815. onHandler('onShow'),
  9816. ];
  9817. const init$3 = () => {
  9818. const enabled = Cell(true);
  9819. const timer = value$2();
  9820. const popup = value$2();
  9821. const clearTimer = () => {
  9822. timer.on(clearTimeout);
  9823. };
  9824. const resetTimer = (f, delay) => {
  9825. clearTimer();
  9826. timer.set(setTimeout(f, delay));
  9827. };
  9828. const readState = constant$1('not-implemented');
  9829. return nu$4({
  9830. getTooltip: popup.get,
  9831. isShowing: popup.isSet,
  9832. setTooltip: popup.set,
  9833. clearTooltip: popup.clear,
  9834. clearTimer,
  9835. resetTimer,
  9836. readState,
  9837. isEnabled: () => enabled.get(),
  9838. setEnabled: (setToEnabled) => enabled.set(setToEnabled)
  9839. });
  9840. };
  9841. var TooltippingState = /*#__PURE__*/Object.freeze({
  9842. __proto__: null,
  9843. init: init$3
  9844. });
  9845. const Tooltipping = create$3({
  9846. fields: TooltippingSchema,
  9847. name: 'tooltipping',
  9848. active: ActiveTooltipping,
  9849. state: TooltippingState,
  9850. apis: TooltippingApis
  9851. });
  9852. const exhibit = () => nu$2({
  9853. styles: {
  9854. '-webkit-user-select': 'none',
  9855. 'user-select': 'none',
  9856. '-ms-user-select': 'none',
  9857. '-moz-user-select': '-moz-none'
  9858. },
  9859. attributes: {
  9860. unselectable: 'on'
  9861. }
  9862. });
  9863. const events$1 = () => derive$2([
  9864. abort(selectstart(), always)
  9865. ]);
  9866. var ActiveUnselecting = /*#__PURE__*/Object.freeze({
  9867. __proto__: null,
  9868. events: events$1,
  9869. exhibit: exhibit
  9870. });
  9871. const Unselecting = create$3({
  9872. fields: [],
  9873. name: 'unselecting',
  9874. active: ActiveUnselecting
  9875. });
  9876. const getAttrs = (elem) => {
  9877. const attributes = elem.dom.attributes !== undefined ? elem.dom.attributes : [];
  9878. return foldl(attributes, (b, attr) => {
  9879. // Make class go through the class path. Do not list it as an attribute.
  9880. if (attr.name === 'class') {
  9881. return b;
  9882. }
  9883. else {
  9884. return { ...b, [attr.name]: attr.value };
  9885. }
  9886. }, {});
  9887. };
  9888. const getClasses = (elem) => Array.prototype.slice.call(elem.dom.classList, 0);
  9889. const fromHtml = (html) => {
  9890. const elem = SugarElement.fromHtml(html);
  9891. const children$1 = children(elem);
  9892. const attrs = getAttrs(elem);
  9893. const classes = getClasses(elem);
  9894. const contents = children$1.length === 0 ? {} : { innerHtml: get$f(elem) };
  9895. return {
  9896. tag: name$3(elem),
  9897. classes,
  9898. attributes: attrs,
  9899. ...contents
  9900. };
  9901. };
  9902. const record = (spec) => {
  9903. const uid = isSketchSpec(spec) && hasNonNullableKey(spec, 'uid') ? spec.uid : generate$4('memento');
  9904. const get = (anyInSystem) => anyInSystem.getSystem().getByUid(uid).getOrDie();
  9905. const getOpt = (anyInSystem) => anyInSystem.getSystem().getByUid(uid).toOptional();
  9906. const asSpec = () => ({
  9907. ...spec,
  9908. uid
  9909. });
  9910. return {
  9911. get,
  9912. getOpt,
  9913. asSpec
  9914. };
  9915. };
  9916. // TODO: ^ rename the parts/ api to composites, it will break mobile alloy now if we do
  9917. const parts$g = AlloyParts;
  9918. const partType$1 = PartType;
  9919. const fromSource = (event, source) => {
  9920. const stopper = Cell(false);
  9921. const cutter = Cell(false);
  9922. const stop = () => {
  9923. stopper.set(true);
  9924. };
  9925. const cut = () => {
  9926. cutter.set(true);
  9927. };
  9928. return {
  9929. stop,
  9930. cut,
  9931. isStopped: stopper.get,
  9932. isCut: cutter.get,
  9933. event,
  9934. // Used only for tiered menu at the moment. It is an element, not a component
  9935. setSource: source.set,
  9936. getSource: source.get
  9937. };
  9938. };
  9939. // Events that come from outside of the alloy root (e.g. window scroll)
  9940. const fromExternal = (event) => {
  9941. const stopper = Cell(false);
  9942. const stop = () => {
  9943. stopper.set(true);
  9944. };
  9945. return {
  9946. stop,
  9947. cut: noop, // cutting has no meaning for a broadcasted event
  9948. isStopped: stopper.get,
  9949. isCut: never,
  9950. event,
  9951. // Nor do targets really
  9952. setSource: die('Cannot set source of a broadcasted event'),
  9953. getSource: die('Cannot get source of a broadcasted event')
  9954. };
  9955. };
  9956. const isDangerous = (event) => {
  9957. // Will trigger the Back button in the browser
  9958. const keyEv = event.raw;
  9959. return keyEv.which === BACKSPACE[0] && !contains$2(['input', 'textarea'], name$3(event.target)) && !closest$1(event.target, '[contenteditable="true"]');
  9960. };
  9961. const setup$d = (container, rawSettings) => {
  9962. const settings = {
  9963. stopBackspace: true,
  9964. ...rawSettings
  9965. };
  9966. const pointerEvents = [
  9967. 'touchstart',
  9968. 'touchmove',
  9969. 'touchend',
  9970. 'touchcancel',
  9971. 'gesturestart',
  9972. 'mousedown',
  9973. 'mouseup',
  9974. 'mouseover',
  9975. 'mousemove',
  9976. 'mouseout',
  9977. 'click'
  9978. ];
  9979. const tapEvent = monitor(settings);
  9980. // These events are just passed through ... no additional processing
  9981. const simpleEvents = map$2(pointerEvents.concat([
  9982. 'selectstart',
  9983. 'input',
  9984. 'contextmenu',
  9985. 'change',
  9986. 'transitionend',
  9987. 'transitioncancel',
  9988. // Test the drag events
  9989. 'drag',
  9990. 'dragstart',
  9991. 'dragend',
  9992. 'dragenter',
  9993. 'dragleave',
  9994. 'dragover',
  9995. 'drop',
  9996. 'keyup'
  9997. ]), (type) => bind$1(container, type, (event) => {
  9998. tapEvent.fireIfReady(event, type).each((tapStopped) => {
  9999. if (tapStopped) {
  10000. event.kill();
  10001. }
  10002. });
  10003. const stopped = settings.triggerEvent(type, event);
  10004. if (stopped) {
  10005. event.kill();
  10006. }
  10007. }));
  10008. const pasteTimeout = value$2();
  10009. const onPaste = bind$1(container, 'paste', (event) => {
  10010. tapEvent.fireIfReady(event, 'paste').each((tapStopped) => {
  10011. if (tapStopped) {
  10012. event.kill();
  10013. }
  10014. });
  10015. const stopped = settings.triggerEvent('paste', event);
  10016. if (stopped) {
  10017. event.kill();
  10018. }
  10019. pasteTimeout.set(setTimeout(() => {
  10020. settings.triggerEvent(postPaste(), event);
  10021. }, 0));
  10022. });
  10023. const onKeydown = bind$1(container, 'keydown', (event) => {
  10024. // Prevent default of backspace when not in input fields.
  10025. const stopped = settings.triggerEvent('keydown', event);
  10026. if (stopped) {
  10027. event.kill();
  10028. }
  10029. else if (settings.stopBackspace && isDangerous(event)) {
  10030. event.prevent();
  10031. }
  10032. });
  10033. const onFocusIn = bind$1(container, 'focusin', (event) => {
  10034. const stopped = settings.triggerEvent('focusin', event);
  10035. if (stopped) {
  10036. event.kill();
  10037. }
  10038. });
  10039. const focusoutTimeout = value$2();
  10040. const onFocusOut = bind$1(container, 'focusout', (event) => {
  10041. const stopped = settings.triggerEvent('focusout', event);
  10042. if (stopped) {
  10043. event.kill();
  10044. }
  10045. // INVESTIGATE: Come up with a better way of doing this. Related target can be used, but not on FF.
  10046. // It allows the active element to change before firing the blur that we will listen to
  10047. // for things like closing popups
  10048. focusoutTimeout.set(setTimeout(() => {
  10049. settings.triggerEvent(postBlur(), event);
  10050. }, 0));
  10051. });
  10052. const unbind = () => {
  10053. each$1(simpleEvents, (e) => {
  10054. e.unbind();
  10055. });
  10056. onKeydown.unbind();
  10057. onFocusIn.unbind();
  10058. onFocusOut.unbind();
  10059. onPaste.unbind();
  10060. pasteTimeout.on(clearTimeout);
  10061. focusoutTimeout.on(clearTimeout);
  10062. };
  10063. return {
  10064. unbind
  10065. };
  10066. };
  10067. const derive = (rawEvent, rawTarget) => {
  10068. const source = get$h(rawEvent, 'target').getOr(rawTarget);
  10069. return Cell(source);
  10070. };
  10071. const adt = Adt.generate([
  10072. { stopped: [] },
  10073. { resume: ['element'] },
  10074. { complete: [] }
  10075. ]);
  10076. const doTriggerHandler = (lookup, eventType, rawEvent, target, source, logger) => {
  10077. const handler = lookup(eventType, target);
  10078. const simulatedEvent = fromSource(rawEvent, source);
  10079. return handler.fold(() => {
  10080. // No handler, so complete.
  10081. logger.logEventNoHandlers(eventType, target);
  10082. return adt.complete();
  10083. }, (handlerInfo) => {
  10084. const descHandler = handlerInfo.descHandler;
  10085. const eventHandler = getCurried(descHandler);
  10086. eventHandler(simulatedEvent);
  10087. // Now, check if the event was stopped.
  10088. if (simulatedEvent.isStopped()) {
  10089. logger.logEventStopped(eventType, handlerInfo.element, descHandler.purpose);
  10090. return adt.stopped();
  10091. }
  10092. else if (simulatedEvent.isCut()) {
  10093. logger.logEventCut(eventType, handlerInfo.element, descHandler.purpose);
  10094. return adt.complete();
  10095. }
  10096. else {
  10097. return parent(handlerInfo.element).fold(() => {
  10098. logger.logNoParent(eventType, handlerInfo.element, descHandler.purpose);
  10099. // No parent, so complete.
  10100. return adt.complete();
  10101. }, (parent) => {
  10102. logger.logEventResponse(eventType, handlerInfo.element, descHandler.purpose);
  10103. // Resume at parent
  10104. return adt.resume(parent);
  10105. });
  10106. }
  10107. });
  10108. };
  10109. const doTriggerOnUntilStopped = (lookup, eventType, rawEvent, rawTarget, source, logger) => doTriggerHandler(lookup, eventType, rawEvent, rawTarget, source, logger).fold(
  10110. // stopped.
  10111. always,
  10112. // Go again.
  10113. (parent) => doTriggerOnUntilStopped(lookup, eventType, rawEvent, parent, source, logger),
  10114. // completed
  10115. never);
  10116. const triggerHandler = (lookup, eventType, rawEvent, target, logger) => {
  10117. const source = derive(rawEvent, target);
  10118. return doTriggerHandler(lookup, eventType, rawEvent, target, source, logger);
  10119. };
  10120. const broadcast = (listeners, rawEvent, _logger) => {
  10121. const simulatedEvent = fromExternal(rawEvent);
  10122. each$1(listeners, (listener) => {
  10123. const descHandler = listener.descHandler;
  10124. const handler = getCurried(descHandler);
  10125. handler(simulatedEvent);
  10126. });
  10127. return simulatedEvent.isStopped();
  10128. };
  10129. const triggerUntilStopped = (lookup, eventType, rawEvent, logger) => triggerOnUntilStopped(lookup, eventType, rawEvent, rawEvent.target, logger);
  10130. const triggerOnUntilStopped = (lookup, eventType, rawEvent, rawTarget, logger) => {
  10131. const source = derive(rawEvent, rawTarget);
  10132. return doTriggerOnUntilStopped(lookup, eventType, rawEvent, rawTarget, source, logger);
  10133. };
  10134. const eventHandler = (element, descHandler) => ({
  10135. element,
  10136. descHandler
  10137. });
  10138. const broadcastHandler = (id, handler) => ({
  10139. id,
  10140. descHandler: handler
  10141. });
  10142. const EventRegistry = () => {
  10143. const registry = {};
  10144. const registerId = (extraArgs, id, events) => {
  10145. each(events, (v, k) => {
  10146. const handlers = registry[k] !== undefined ? registry[k] : {};
  10147. handlers[id] = curryArgs(v, extraArgs);
  10148. registry[k] = handlers;
  10149. });
  10150. };
  10151. const findHandler = (handlers, elem) => read(elem)
  10152. .bind((id) => get$h(handlers, id))
  10153. .map((descHandler) => eventHandler(elem, descHandler));
  10154. // Given just the event type, find all handlers regardless of element
  10155. const filterByType = (type) => get$h(registry, type)
  10156. .map((handlers) => mapToArray(handlers, (f, id) => broadcastHandler(id, f)))
  10157. .getOr([]);
  10158. // Given event type, and element, find the handler.
  10159. const find = (isAboveRoot, type, target) => get$h(registry, type)
  10160. .bind((handlers) => closest(target, (elem) => findHandler(handlers, elem), isAboveRoot));
  10161. const unregisterId = (id) => {
  10162. // INVESTIGATE: Find a better way than mutation if we can.
  10163. each(registry, (handlersById, _eventName) => {
  10164. if (has$2(handlersById, id)) {
  10165. delete handlersById[id];
  10166. }
  10167. });
  10168. };
  10169. return {
  10170. registerId,
  10171. unregisterId,
  10172. filterByType,
  10173. find
  10174. };
  10175. };
  10176. const Registry = () => {
  10177. const events = EventRegistry();
  10178. // An index of uid -> built components
  10179. const components = {};
  10180. const readOrTag = (component) => {
  10181. const elem = component.element;
  10182. return read(elem).getOrThunk(() =>
  10183. // No existing tag, so add one.
  10184. write('uid-', component.element));
  10185. };
  10186. const failOnDuplicate = (component, tagId) => {
  10187. const conflict = components[tagId];
  10188. if (conflict === component) {
  10189. unregister(component);
  10190. }
  10191. else {
  10192. throw new Error('The tagId "' + tagId + '" is already used by: ' + element(conflict.element) + '\nCannot use it for: ' + element(component.element) + '\n' +
  10193. 'The conflicting element is' + (inBody(conflict.element) ? ' ' : ' not ') + 'already in the DOM');
  10194. }
  10195. };
  10196. const register = (component) => {
  10197. const tagId = readOrTag(component);
  10198. if (hasNonNullableKey(components, tagId)) {
  10199. failOnDuplicate(component, tagId);
  10200. }
  10201. // Component is passed through an an extra argument to all events
  10202. const extraArgs = [component];
  10203. events.registerId(extraArgs, tagId, component.events);
  10204. components[tagId] = component;
  10205. };
  10206. const unregister = (component) => {
  10207. read(component.element).each((tagId) => {
  10208. delete components[tagId];
  10209. events.unregisterId(tagId);
  10210. });
  10211. };
  10212. const filter = (type) => events.filterByType(type);
  10213. const find = (isAboveRoot, type, target) => events.find(isAboveRoot, type, target);
  10214. const getById = (id) => get$h(components, id);
  10215. return {
  10216. find,
  10217. filter,
  10218. register,
  10219. unregister,
  10220. getById
  10221. };
  10222. };
  10223. const takeover = (root) => {
  10224. const isAboveRoot = (el) => parent(root.element).fold(always, (parent) => eq(el, parent));
  10225. const registry = Registry();
  10226. const lookup = (eventName, target) => registry.find(isAboveRoot, eventName, target);
  10227. const domEvents = setup$d(root.element, {
  10228. triggerEvent: (eventName, event) => {
  10229. return monitorEvent(eventName, event.target, (logger) => triggerUntilStopped(lookup, eventName, event, logger));
  10230. }
  10231. });
  10232. const systemApi = {
  10233. // This is a real system
  10234. debugInfo: constant$1('real'),
  10235. triggerEvent: (eventName, target, data) => {
  10236. monitorEvent(eventName, target, (logger) =>
  10237. // The return value is not used because this is a fake event.
  10238. triggerOnUntilStopped(lookup, eventName, data, target, logger));
  10239. },
  10240. triggerFocus: (target, originator) => {
  10241. read(target).fold(() => {
  10242. // When the target is not within the alloy system, dispatch a normal focus event.
  10243. focus$4(target);
  10244. }, (_alloyId) => {
  10245. monitorEvent(focus$3(), target, (logger) => {
  10246. // NOTE: This will stop at first handler.
  10247. triggerHandler(lookup, focus$3(), {
  10248. // originator is used by the default events to ensure that focus doesn't
  10249. // get called infinitely
  10250. originator,
  10251. kill: noop,
  10252. prevent: noop,
  10253. target
  10254. }, target, logger);
  10255. return false;
  10256. });
  10257. });
  10258. },
  10259. triggerEscape: (comp, simulatedEvent) => {
  10260. systemApi.triggerEvent('keydown', comp.element, simulatedEvent.event);
  10261. },
  10262. getByUid: (uid) => {
  10263. return getByUid(uid);
  10264. },
  10265. getByDom: (elem) => {
  10266. return getByDom(elem);
  10267. },
  10268. build: build$1,
  10269. buildOrPatch: buildOrPatch,
  10270. addToGui: (c) => {
  10271. add(c);
  10272. },
  10273. removeFromGui: (c) => {
  10274. remove(c);
  10275. },
  10276. addToWorld: (c) => {
  10277. addToWorld(c);
  10278. },
  10279. removeFromWorld: (c) => {
  10280. removeFromWorld(c);
  10281. },
  10282. broadcast: (message) => {
  10283. broadcast$1(message);
  10284. },
  10285. broadcastOn: (channels, message) => {
  10286. broadcastOn(channels, message);
  10287. },
  10288. broadcastEvent: (eventName, event) => {
  10289. broadcastEvent(eventName, event);
  10290. },
  10291. isConnected: always
  10292. };
  10293. const addToWorld = (component) => {
  10294. component.connect(systemApi);
  10295. if (!isText(component.element)) {
  10296. registry.register(component);
  10297. each$1(component.components(), addToWorld);
  10298. systemApi.triggerEvent(systemInit(), component.element, { target: component.element });
  10299. }
  10300. };
  10301. const removeFromWorld = (component) => {
  10302. if (!isText(component.element)) {
  10303. each$1(component.components(), removeFromWorld);
  10304. registry.unregister(component);
  10305. }
  10306. component.disconnect();
  10307. };
  10308. const add = (component) => {
  10309. attach(root, component);
  10310. };
  10311. const remove = (component) => {
  10312. detach(component);
  10313. };
  10314. const destroy = () => {
  10315. // INVESTIGATE: something with registry?
  10316. domEvents.unbind();
  10317. remove$7(root.element);
  10318. };
  10319. const broadcastData = (data) => {
  10320. const receivers = registry.filter(receive());
  10321. each$1(receivers, (receiver) => {
  10322. const descHandler = receiver.descHandler;
  10323. const handler = getCurried(descHandler);
  10324. handler(data);
  10325. });
  10326. };
  10327. const broadcast$1 = (message) => {
  10328. broadcastData({
  10329. universal: true,
  10330. data: message
  10331. });
  10332. };
  10333. const broadcastOn = (channels, message) => {
  10334. broadcastData({
  10335. universal: false,
  10336. channels,
  10337. data: message
  10338. });
  10339. };
  10340. // This doesn't follow usual DOM bubbling. It will just dispatch on all
  10341. // targets that have the event. It is the general case of the more specialised
  10342. // "message". "messages" may actually just go away. This is used for things
  10343. // like window scroll.
  10344. const broadcastEvent = (eventName, event) => {
  10345. const listeners = registry.filter(eventName);
  10346. return broadcast(listeners, event);
  10347. };
  10348. const getByUid = (uid) => registry.getById(uid).fold(() => Result.error(new Error('Could not find component with uid: "' + uid + '" in system.')), Result.value);
  10349. const getByDom = (elem) => {
  10350. const uid = read(elem).getOr('not found');
  10351. return getByUid(uid);
  10352. };
  10353. addToWorld(root);
  10354. return {
  10355. root,
  10356. element: root.element,
  10357. destroy,
  10358. add,
  10359. remove,
  10360. getByUid,
  10361. getByDom,
  10362. addToWorld,
  10363. removeFromWorld,
  10364. broadcast: broadcast$1,
  10365. broadcastOn,
  10366. broadcastEvent
  10367. };
  10368. };
  10369. const pointerEvents = () => {
  10370. const onClick = (component, simulatedEvent) => {
  10371. simulatedEvent.stop();
  10372. emitExecute(component);
  10373. };
  10374. return [
  10375. // Trigger execute when clicked
  10376. run$1(click(), onClick),
  10377. run$1(tap(), onClick),
  10378. // Other mouse down listeners above this one should not get mousedown behaviour (like dragging)
  10379. cutter(touchstart()),
  10380. cutter(mousedown())
  10381. ];
  10382. };
  10383. const events = (optAction) => {
  10384. const executeHandler = (action) => runOnExecute$1((component, simulatedEvent) => {
  10385. action(component);
  10386. simulatedEvent.stop();
  10387. });
  10388. return derive$2(flatten([
  10389. // Only listen to execute if it is supplied
  10390. optAction.map(executeHandler).toArray(),
  10391. pointerEvents()
  10392. ]));
  10393. };
  10394. const factory$m = (detail) => {
  10395. const events$1 = events(detail.action);
  10396. const tag = detail.dom.tag;
  10397. const lookupAttr = (attr) => get$h(detail.dom, 'attributes').bind((attrs) => get$h(attrs, attr));
  10398. // Button tags should not have a default role of button, and only buttons should
  10399. // get a type of button.
  10400. const getModAttributes = () => {
  10401. if (tag === 'button') {
  10402. // Default to type button, unless specified otherwise
  10403. const type = lookupAttr('type').getOr('button');
  10404. // Only use a role if it is specified
  10405. const roleAttrs = lookupAttr('role').map((role) => ({ role })).getOr({});
  10406. return {
  10407. type,
  10408. ...roleAttrs
  10409. };
  10410. }
  10411. else {
  10412. // We are not a button, so type is irrelevant (unless specified)
  10413. // Default role to button
  10414. const role = detail.role.getOr(lookupAttr('role').getOr('button'));
  10415. return { role };
  10416. }
  10417. };
  10418. return {
  10419. uid: detail.uid,
  10420. dom: detail.dom,
  10421. components: detail.components,
  10422. events: events$1,
  10423. behaviours: SketchBehaviours.augment(detail.buttonBehaviours, [
  10424. Focusing.config({}),
  10425. Keying.config({
  10426. mode: 'execution',
  10427. // Note execution will capture keyup when the focus is on the button
  10428. // on Firefox, because otherwise it will fire a click event and double
  10429. // up on the action
  10430. useSpace: true,
  10431. useEnter: true
  10432. })
  10433. ]),
  10434. domModification: {
  10435. attributes: getModAttributes()
  10436. },
  10437. eventOrder: detail.eventOrder
  10438. };
  10439. };
  10440. const Button = single({
  10441. name: 'Button',
  10442. factory: factory$m,
  10443. configFields: [
  10444. defaulted('uid', undefined),
  10445. required$1('dom'),
  10446. defaulted('components', []),
  10447. SketchBehaviours.field('buttonBehaviours', [Focusing, Keying]),
  10448. option$3('action'),
  10449. option$3('role'),
  10450. defaulted('eventOrder', {})
  10451. ]
  10452. });
  10453. const schema$m = constant$1([
  10454. defaulted('shell', false),
  10455. required$1('makeItem'),
  10456. defaulted('setupItem', noop),
  10457. SketchBehaviours.field('listBehaviours', [Replacing])
  10458. ]);
  10459. const customListDetail = () => ({
  10460. behaviours: derive$1([
  10461. Replacing.config({})
  10462. ])
  10463. });
  10464. const itemsPart = optional({
  10465. name: 'items',
  10466. overrides: customListDetail
  10467. });
  10468. const parts$f = constant$1([
  10469. itemsPart
  10470. ]);
  10471. const name$1 = constant$1('CustomList');
  10472. const factory$l = (detail, components, _spec, _external) => {
  10473. const setItems = (list, items) => {
  10474. getListContainer(list).fold(() => {
  10475. // check that the group container existed. It may not have if the components
  10476. // did not list anything, and shell was false.
  10477. // eslint-disable-next-line no-console
  10478. console.error('Custom List was defined to not be a shell, but no item container was specified in components');
  10479. throw new Error('Custom List was defined to not be a shell, but no item container was specified in components');
  10480. }, (container) => {
  10481. // Get all the children of container, because they will be items.
  10482. // And then use the item setGroup api
  10483. const itemComps = Replacing.contents(container);
  10484. const numListsRequired = items.length;
  10485. const numListsToAdd = numListsRequired - itemComps.length;
  10486. const itemsToAdd = numListsToAdd > 0 ?
  10487. range$2(numListsToAdd, () => detail.makeItem()) : [];
  10488. const itemsToRemove = itemComps.slice(numListsRequired);
  10489. each$1(itemsToRemove, (item) => Replacing.remove(container, item));
  10490. each$1(itemsToAdd, (item) => Replacing.append(container, item));
  10491. const builtLists = Replacing.contents(container);
  10492. each$1(builtLists, (item, i) => {
  10493. detail.setupItem(list, item, items[i], i);
  10494. });
  10495. });
  10496. };
  10497. // In shell mode, the group overrides need to be added to the main container, and there can be no children
  10498. const extra = detail.shell ? { behaviours: [Replacing.config({})], components: [] } : { behaviours: [], components };
  10499. const getListContainer = (component) => detail.shell ? Optional.some(component) : getPart(component, detail, 'items');
  10500. return {
  10501. uid: detail.uid,
  10502. dom: detail.dom,
  10503. components: extra.components,
  10504. behaviours: augment(detail.listBehaviours, extra.behaviours),
  10505. apis: {
  10506. setItems
  10507. }
  10508. };
  10509. };
  10510. const CustomList = composite({
  10511. name: name$1(),
  10512. configFields: schema$m(),
  10513. partFields: parts$f(),
  10514. factory: factory$l,
  10515. apis: {
  10516. setItems: (apis, list, items) => {
  10517. apis.setItems(list, items);
  10518. }
  10519. }
  10520. });
  10521. const attribute = 'aria-controls';
  10522. const find$1 = (queryElem) => {
  10523. const dependent = closest$4(queryElem, (elem) => {
  10524. if (!isElement$1(elem)) {
  10525. return false;
  10526. }
  10527. const id = get$g(elem, 'id');
  10528. return id !== undefined && id.indexOf(attribute) > -1;
  10529. });
  10530. return dependent.bind((dep) => {
  10531. const id = get$g(dep, 'id');
  10532. const dos = getRootNode(dep);
  10533. return descendant(dos, `[${attribute}="${id}"]`);
  10534. });
  10535. };
  10536. const manager = () => {
  10537. const ariaId = generate$6(attribute);
  10538. const link = (elem) => {
  10539. set$9(elem, attribute, ariaId);
  10540. };
  10541. const unlink = (elem) => {
  10542. remove$8(elem, attribute);
  10543. };
  10544. return {
  10545. id: ariaId,
  10546. link,
  10547. unlink,
  10548. };
  10549. };
  10550. const isAriaPartOf = (component, queryElem) => find$1(queryElem).exists((owner) => isPartOf(component, owner));
  10551. const isPartOf = (component, queryElem) => closest$2(queryElem, (el) => eq(el, component.element), never) || isAriaPartOf(component, queryElem);
  10552. const hoverEvent = 'alloy.item-hover';
  10553. const focusEvent = 'alloy.item-focus';
  10554. const toggledEvent = 'alloy.item-toggled';
  10555. const onHover = (item) => {
  10556. // Firstly, check that the focus isn't already inside the item. This
  10557. // is to handle situations like widgets where the widget is inside the item
  10558. // and it has the focus, so as you slightly adjust the mouse, you don't
  10559. // want to lose focus on the widget. Note, that because this isn't API based
  10560. // (i.e. we are manually searching for focus), it may not be that flexible.
  10561. if (search(item.element).isNone() || Focusing.isFocused(item)) {
  10562. if (!Focusing.isFocused(item)) {
  10563. Focusing.focus(item);
  10564. }
  10565. emitWith(item, hoverEvent, { item });
  10566. }
  10567. };
  10568. const onFocus$1 = (item) => {
  10569. emitWith(item, focusEvent, { item });
  10570. };
  10571. const onToggled = (item, state) => {
  10572. emitWith(item, toggledEvent, { item, state });
  10573. };
  10574. const hover = constant$1(hoverEvent);
  10575. const focus$1 = constant$1(focusEvent);
  10576. const toggled = constant$1(toggledEvent);
  10577. // eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
  10578. const getItemRole = (detail) => detail.role.fold(() => detail.toggling
  10579. .map((toggling) => toggling.exclusive ? 'menuitemradio' : 'menuitemcheckbox')
  10580. .getOr('menuitem'), identity);
  10581. const getTogglingSpec = (tConfig, isOption) => ({
  10582. aria: {
  10583. mode: isOption ? 'selected' : 'checked'
  10584. },
  10585. // Filter out the additional properties that are not in Toggling Behaviour's configuration (e.g. exclusive)
  10586. ...filter$1(tConfig, (_value, name) => name !== 'exclusive'),
  10587. onToggled: (component, state) => {
  10588. if (isFunction(tConfig.onToggled)) {
  10589. tConfig.onToggled(component, state);
  10590. }
  10591. onToggled(component, state);
  10592. }
  10593. });
  10594. const builder$2 = (detail) => ({
  10595. dom: detail.dom,
  10596. domModification: {
  10597. // INVESTIGATE: If more efficient, destructure attributes out
  10598. ...detail.domModification,
  10599. attributes: {
  10600. 'role': getItemRole(detail),
  10601. ...detail.domModification.attributes,
  10602. 'aria-haspopup': detail.hasSubmenu,
  10603. ...(detail.hasSubmenu ? { 'aria-expanded': false } : {})
  10604. }
  10605. },
  10606. behaviours: SketchBehaviours.augment(detail.itemBehaviours, [
  10607. // Investigate, is the Toggling.revoke still necessary here?
  10608. detail.toggling.fold(Toggling.revoke, (tConfig) => Toggling.config(getTogglingSpec(tConfig, detail.role.exists((role) => role === 'option')))),
  10609. Focusing.config({
  10610. ignore: detail.ignoreFocus,
  10611. // Rationale: because nothing is focusable, when you click
  10612. // on the items to choose them, the focus jumps to the first
  10613. // focusable outer container ... often the body. If we prevent
  10614. // mouseDown ... that doesn't happen. But only tested on Chrome/FF.
  10615. stopMousedown: detail.ignoreFocus,
  10616. onFocus: (component) => {
  10617. onFocus$1(component);
  10618. }
  10619. }),
  10620. Keying.config({
  10621. mode: 'execution'
  10622. }),
  10623. Representing.config({
  10624. store: {
  10625. mode: 'memory',
  10626. initialValue: detail.data
  10627. }
  10628. }),
  10629. config('item-type-events', [
  10630. // Treat clicks the same as a button
  10631. ...pointerEvents(),
  10632. run$1(mouseover(), onHover),
  10633. run$1(focusItem(), Focusing.focus)
  10634. ])
  10635. ]),
  10636. components: detail.components,
  10637. eventOrder: detail.eventOrder
  10638. });
  10639. const schema$l = [
  10640. required$1('data'),
  10641. required$1('components'),
  10642. required$1('dom'),
  10643. defaulted('hasSubmenu', false),
  10644. option$3('toggling'),
  10645. option$3('role'),
  10646. // Maybe this needs to have fewer behaviours
  10647. SketchBehaviours.field('itemBehaviours', [Toggling, Focusing, Keying, Representing]),
  10648. defaulted('ignoreFocus', false),
  10649. defaulted('domModification', {}),
  10650. output$1('builder', builder$2),
  10651. defaulted('eventOrder', {})
  10652. ];
  10653. var ItemType = schema$l;
  10654. const builder$1 = (detail) => ({
  10655. dom: detail.dom,
  10656. components: detail.components,
  10657. events: derive$2([
  10658. stopper(focusItem())
  10659. ])
  10660. });
  10661. const schema$k = [
  10662. required$1('dom'),
  10663. required$1('components'),
  10664. output$1('builder', builder$1)
  10665. ];
  10666. var SeparatorType = schema$k;
  10667. const owner$2 = constant$1('item-widget');
  10668. const parts$e = constant$1([
  10669. required({
  10670. name: 'widget',
  10671. overrides: (detail) => {
  10672. return {
  10673. behaviours: derive$1([
  10674. Representing.config({
  10675. store: {
  10676. mode: 'manual',
  10677. getValue: (_component) => {
  10678. return detail.data;
  10679. },
  10680. setValue: noop
  10681. }
  10682. })
  10683. ])
  10684. };
  10685. }
  10686. })
  10687. ]);
  10688. const builder = (detail) => {
  10689. const subs = substitutes(owner$2(), detail, parts$e());
  10690. const components = components$1(owner$2(), detail, subs.internals());
  10691. const focusWidget = (component) => getPart(component, detail, 'widget').map((widget) => {
  10692. Keying.focusIn(widget);
  10693. return widget;
  10694. });
  10695. const onHorizontalArrow = (component, simulatedEvent) => inside(simulatedEvent.event.target) ? Optional.none() : (() => {
  10696. if (detail.autofocus) {
  10697. simulatedEvent.setSource(component.element);
  10698. return Optional.none();
  10699. }
  10700. else {
  10701. return Optional.none();
  10702. }
  10703. })();
  10704. return {
  10705. dom: detail.dom,
  10706. components,
  10707. domModification: detail.domModification,
  10708. events: derive$2([
  10709. runOnExecute$1((component, simulatedEvent) => {
  10710. focusWidget(component).each((_widget) => {
  10711. simulatedEvent.stop();
  10712. });
  10713. }),
  10714. run$1(mouseover(), onHover),
  10715. run$1(focusItem(), (component, _simulatedEvent) => {
  10716. if (detail.autofocus) {
  10717. focusWidget(component);
  10718. }
  10719. else {
  10720. Focusing.focus(component);
  10721. }
  10722. })
  10723. ]),
  10724. behaviours: SketchBehaviours.augment(detail.widgetBehaviours, [
  10725. Representing.config({
  10726. store: {
  10727. mode: 'memory',
  10728. initialValue: detail.data
  10729. }
  10730. }),
  10731. Focusing.config({
  10732. ignore: detail.ignoreFocus,
  10733. // What about stopMousedown from ItemType?
  10734. onFocus: (component) => {
  10735. onFocus$1(component);
  10736. }
  10737. }),
  10738. Keying.config({
  10739. mode: 'special',
  10740. // This is required as long as Highlighting tries to focus the first thing (after focusItem fires)
  10741. focusIn: detail.autofocus ? (component) => {
  10742. focusWidget(component);
  10743. } : revoke(),
  10744. onLeft: onHorizontalArrow,
  10745. onRight: onHorizontalArrow,
  10746. onEscape: (component, simulatedEvent) => {
  10747. // If the outer list item didn't have focus,
  10748. // then focus it (i.e. escape the inner widget). Only do if not autofocusing
  10749. // Autofocusing should treat the widget like it is the only item, so it should
  10750. // let its outer menu handle escape
  10751. if (!Focusing.isFocused(component) && !detail.autofocus) {
  10752. Focusing.focus(component);
  10753. return Optional.some(true);
  10754. }
  10755. else if (detail.autofocus) {
  10756. simulatedEvent.setSource(component.element);
  10757. return Optional.none();
  10758. }
  10759. else {
  10760. return Optional.none();
  10761. }
  10762. }
  10763. })
  10764. ])
  10765. };
  10766. };
  10767. const schema$j = [
  10768. required$1('uid'),
  10769. required$1('data'),
  10770. required$1('components'),
  10771. required$1('dom'),
  10772. defaulted('autofocus', false),
  10773. defaulted('ignoreFocus', false),
  10774. SketchBehaviours.field('widgetBehaviours', [Representing, Focusing, Keying]),
  10775. defaulted('domModification', {}),
  10776. // We don't have the uid at this point
  10777. defaultUidsSchema(parts$e()),
  10778. output$1('builder', builder)
  10779. ];
  10780. var WidgetType = schema$j;
  10781. const itemSchema$2 = choose$1('type', {
  10782. widget: WidgetType,
  10783. item: ItemType,
  10784. separator: SeparatorType
  10785. });
  10786. const configureGrid = (detail, movementInfo) => ({
  10787. mode: 'flatgrid',
  10788. selector: '.' + detail.markers.item,
  10789. initSize: {
  10790. numColumns: movementInfo.initSize.numColumns,
  10791. numRows: movementInfo.initSize.numRows
  10792. },
  10793. focusManager: detail.focusManager
  10794. });
  10795. const configureMatrix = (detail, movementInfo) => ({
  10796. mode: 'matrix',
  10797. selectors: {
  10798. row: movementInfo.rowSelector,
  10799. cell: '.' + detail.markers.item
  10800. },
  10801. previousSelector: movementInfo.previousSelector,
  10802. focusManager: detail.focusManager
  10803. });
  10804. const configureMenu = (detail, movementInfo) => ({
  10805. mode: 'menu',
  10806. selector: '.' + detail.markers.item,
  10807. moveOnTab: movementInfo.moveOnTab,
  10808. focusManager: detail.focusManager
  10809. });
  10810. const parts$d = constant$1([
  10811. group({
  10812. factory: {
  10813. sketch: (spec) => {
  10814. const itemInfo = asRawOrDie$1('menu.spec item', itemSchema$2, spec);
  10815. return itemInfo.builder(itemInfo);
  10816. }
  10817. },
  10818. name: 'items',
  10819. unit: 'item',
  10820. defaults: (detail, u) => {
  10821. // Switch this to a common library
  10822. // The WidgetItemSpec is just because it has uid, and the others don't
  10823. // for some reason. So there is nothing guaranteeing that `u` is a WidgetItemSpec,
  10824. // so we should probably rework this code.
  10825. return has$2(u, 'uid') ? u : {
  10826. ...u,
  10827. uid: generate$4('item')
  10828. };
  10829. },
  10830. overrides: (detail, u) => {
  10831. return {
  10832. type: u.type,
  10833. ignoreFocus: detail.fakeFocus,
  10834. domModification: {
  10835. classes: [detail.markers.item]
  10836. }
  10837. };
  10838. }
  10839. })
  10840. ]);
  10841. const schema$i = constant$1([
  10842. optionString('role'),
  10843. required$1('value'),
  10844. required$1('items'),
  10845. required$1('dom'),
  10846. required$1('components'),
  10847. defaulted('eventOrder', {}),
  10848. field('menuBehaviours', [Highlighting, Representing, Composing, Keying]),
  10849. defaultedOf('movement', {
  10850. // When you don't specify movement for a Menu, this is what you get
  10851. // a "menu" type of movement that moves on tab. If you want finer-grained
  10852. // control, like disabling moveOnTab, then you need to specify
  10853. // your entire movement configuration when creating your MenuSpec.
  10854. mode: 'menu',
  10855. moveOnTab: true
  10856. }, choose$1('mode', {
  10857. grid: [
  10858. initSize(),
  10859. output$1('config', configureGrid)
  10860. ],
  10861. matrix: [
  10862. output$1('config', configureMatrix),
  10863. required$1('rowSelector'),
  10864. defaulted('previousSelector', Optional.none),
  10865. ],
  10866. menu: [
  10867. defaulted('moveOnTab', true),
  10868. output$1('config', configureMenu)
  10869. ]
  10870. })),
  10871. itemMarkers(),
  10872. defaulted('fakeFocus', false),
  10873. defaulted('focusManager', dom$2()),
  10874. onHandler('onHighlight'),
  10875. onHandler('onDehighlight'),
  10876. defaulted('showMenuRole', true),
  10877. ]);
  10878. const focus = constant$1('alloy.menu-focus');
  10879. const deselectOtherRadioItems = (menu, item) => {
  10880. // TODO: TINY-8812 - This ideally should be done in a way such that a menu can have multiple radio groups.
  10881. const checkedRadioItems = descendants(menu.element, '[role="menuitemradio"][aria-checked="true"]');
  10882. each$1(checkedRadioItems, (ele) => {
  10883. if (!eq(ele, item.element)) {
  10884. menu.getSystem().getByDom(ele).each((c) => {
  10885. Toggling.off(c);
  10886. });
  10887. }
  10888. });
  10889. };
  10890. const make$6 = (detail, components, _spec, _externals) => ({
  10891. uid: detail.uid,
  10892. dom: detail.dom,
  10893. markers: detail.markers,
  10894. behaviours: augment(detail.menuBehaviours, [
  10895. Highlighting.config({
  10896. // Highlighting for a menu is selecting items inside the menu
  10897. highlightClass: detail.markers.selectedItem,
  10898. itemClass: detail.markers.item,
  10899. onHighlight: detail.onHighlight,
  10900. onDehighlight: detail.onDehighlight
  10901. }),
  10902. Representing.config({
  10903. store: {
  10904. mode: 'memory',
  10905. initialValue: detail.value
  10906. }
  10907. }),
  10908. Composing.config({
  10909. find: Optional.some
  10910. }),
  10911. Keying.config(detail.movement.config(detail, detail.movement))
  10912. ]),
  10913. events: derive$2([
  10914. // This is dispatched from a menu to tell an item to be highlighted.
  10915. run$1(focus$1(), (menu, simulatedEvent) => {
  10916. // Highlight the item
  10917. const event = simulatedEvent.event;
  10918. menu.getSystem().getByDom(event.target).each((item) => {
  10919. Highlighting.highlight(menu, item);
  10920. simulatedEvent.stop();
  10921. // Trigger the focus event on the menu.
  10922. emitWith(menu, focus(), { menu, item });
  10923. });
  10924. }),
  10925. // Highlight the item that the cursor is over. The onHighlight
  10926. // code needs to handle updating focus if required
  10927. run$1(hover(), (menu, simulatedEvent) => {
  10928. const item = simulatedEvent.event.item;
  10929. Highlighting.highlight(menu, item);
  10930. }),
  10931. // Enforce only a single radio menu item is toggled by finding any other toggled
  10932. // radio menu items and untoggling them when a certain item is toggled
  10933. run$1(toggled(), (menu, simulatedEvent) => {
  10934. const { item, state } = simulatedEvent.event;
  10935. if (state && get$g(item.element, 'role') === 'menuitemradio') {
  10936. deselectOtherRadioItems(menu, item);
  10937. }
  10938. })
  10939. ]),
  10940. components,
  10941. eventOrder: detail.eventOrder,
  10942. ...detail.showMenuRole ? {
  10943. domModification: {
  10944. attributes: {
  10945. role: detail.role.getOr('menu')
  10946. }
  10947. }
  10948. } : {}
  10949. });
  10950. const Menu = composite({
  10951. name: 'Menu',
  10952. configFields: schema$i(),
  10953. partFields: parts$d(),
  10954. factory: make$6
  10955. });
  10956. const transpose$1 = (obj) =>
  10957. // Assumes no duplicate fields.
  10958. tupleMap(obj, (v, k) => ({ k: v, v: k }));
  10959. const trace = (items, byItem, byMenu, finish) =>
  10960. // Given a finishing submenu (which will be the value of expansions),
  10961. // find the triggering item, find its menu, and repeat the process. If there
  10962. // is no triggering item, we are done.
  10963. get$h(byMenu, finish).bind((triggerItem) => get$h(items, triggerItem).bind((triggerMenu) => {
  10964. const rest = trace(items, byItem, byMenu, triggerMenu);
  10965. return Optional.some([triggerMenu].concat(rest));
  10966. })).getOr([]);
  10967. const generate$2 = (menus, expansions) => {
  10968. const items = {};
  10969. each(menus, (menuItems, menu) => {
  10970. each$1(menuItems, (item) => {
  10971. items[item] = menu;
  10972. });
  10973. });
  10974. const byItem = expansions;
  10975. const byMenu = transpose$1(expansions);
  10976. // For each menu, calculate the backlog of submenus to get to it.
  10977. const menuPaths = map$1(byMenu, (_triggerItem, submenu) => [submenu].concat(trace(items, byItem, byMenu, submenu)));
  10978. return map$1(items, (menu) => get$h(menuPaths, menu).getOr([menu]));
  10979. };
  10980. const init$2 = () => {
  10981. const expansions = Cell({});
  10982. const menus = Cell({});
  10983. const paths = Cell({});
  10984. const primary = value$2();
  10985. // Probably think of a better way to store this information.
  10986. const directory = Cell({});
  10987. const clear = () => {
  10988. expansions.set({});
  10989. menus.set({});
  10990. paths.set({});
  10991. primary.clear();
  10992. };
  10993. const isClear = () => primary.get().isNone();
  10994. const setMenuBuilt = (menuName, built) => {
  10995. menus.set({
  10996. ...menus.get(),
  10997. [menuName]: {
  10998. type: 'prepared',
  10999. menu: built
  11000. }
  11001. });
  11002. };
  11003. const setContents = (sPrimary, sMenus, sExpansions, dir) => {
  11004. primary.set(sPrimary);
  11005. expansions.set(sExpansions);
  11006. menus.set(sMenus);
  11007. directory.set(dir);
  11008. const sPaths = generate$2(dir, sExpansions);
  11009. paths.set(sPaths);
  11010. };
  11011. const getTriggeringItem = (menuValue) => find$4(expansions.get(), (v, _k) => v === menuValue);
  11012. const getTriggerData = (menuValue, getItemByValue, path) => getPreparedMenu(menuValue).bind((menu) => getTriggeringItem(menuValue).bind((triggeringItemValue) => getItemByValue(triggeringItemValue).map((triggeredItem) => ({
  11013. triggeredMenu: menu,
  11014. triggeringItem: triggeredItem,
  11015. triggeringPath: path
  11016. }))));
  11017. const getTriggeringPath = (itemValue, getItemByValue) => {
  11018. // Get the path up to the last item
  11019. const extraPath = filter$2(lookupItem(itemValue).toArray(), (menuValue) => getPreparedMenu(menuValue).isSome());
  11020. return get$h(paths.get(), itemValue).bind((path) => {
  11021. // remember the path is [ most-recent-menu, next-most-recent-menu ]
  11022. // convert each menu identifier into { triggeringItem: comp, menu: comp }
  11023. // could combine into a fold ... probably a left to reverse ... but we'll take the
  11024. // straightforward version when prototyping
  11025. const revPath = reverse(extraPath.concat(path));
  11026. const triggers = bind$3(revPath, (menuValue, menuIndex) =>
  11027. // finding menuValue, it should match the trigger
  11028. getTriggerData(menuValue, getItemByValue, revPath.slice(0, menuIndex + 1)).fold(() => is$1(primary.get(), menuValue) ? [] : [Optional.none()], (data) => [Optional.some(data)]));
  11029. // Convert List<Optional<X>> to Optional<List<X>> if ALL are Some
  11030. return sequence(triggers);
  11031. });
  11032. };
  11033. // Given an item, return a list of all menus including the one that it triggered (if there is one)
  11034. const expand = (itemValue) => get$h(expansions.get(), itemValue).map((menu) => {
  11035. const current = get$h(paths.get(), itemValue).getOr([]);
  11036. return [menu].concat(current);
  11037. });
  11038. const collapse = (itemValue) =>
  11039. // Look up which key has the itemValue
  11040. get$h(paths.get(), itemValue).bind((path) => path.length > 1 ? Optional.some(path.slice(1)) : Optional.none());
  11041. const refresh = (itemValue) => get$h(paths.get(), itemValue);
  11042. const getPreparedMenu = (menuValue) => lookupMenu(menuValue).bind(extractPreparedMenu);
  11043. const lookupMenu = (menuValue) => get$h(menus.get(), menuValue);
  11044. const lookupItem = (itemValue) => get$h(expansions.get(), itemValue);
  11045. const otherMenus = (path) => {
  11046. const menuValues = directory.get();
  11047. return difference(keys(menuValues), path);
  11048. };
  11049. const getPrimary = () => primary.get().bind(getPreparedMenu);
  11050. const getMenus = () => menus.get();
  11051. return {
  11052. setMenuBuilt,
  11053. setContents,
  11054. expand,
  11055. refresh,
  11056. collapse,
  11057. lookupMenu,
  11058. lookupItem,
  11059. otherMenus,
  11060. getPrimary,
  11061. getMenus,
  11062. clear,
  11063. isClear,
  11064. getTriggeringPath
  11065. };
  11066. };
  11067. const extractPreparedMenu = (prep) => prep.type === 'prepared' ? Optional.some(prep.menu) : Optional.none();
  11068. const LayeredState = {
  11069. init: init$2,
  11070. extractPreparedMenu
  11071. };
  11072. const onMenuItemHighlightedEvent = generate$6('tiered-menu-item-highlight');
  11073. const onMenuItemDehighlightedEvent = generate$6('tiered-menu-item-dehighlight');
  11074. const make$5 = (detail, _rawUiSpec) => {
  11075. const submenuParentItems = value$2();
  11076. // So the way to provide extra configuration for the menus that tiered menus create is just
  11077. // to provide different menu specs when building up the TieredData. The TieredMenu itself
  11078. // does not control it, except to set: markers, fakeFocus, onHighlight, and focusManager
  11079. const buildMenus = (container, primaryName, menus) => map$1(menus, (spec, name) => {
  11080. const makeSketch = () => Menu.sketch({
  11081. ...spec,
  11082. value: name,
  11083. // The TieredMenu markers should be inherited by the Menu. "Markers" are things like
  11084. // what is the class for the currently selected item
  11085. markers: detail.markers,
  11086. // If the TieredMenu has been configured with FakeFocus, it needs the menus that it generates
  11087. // to preserve that configuration. Generally, FakeFocus is used for situations where the user
  11088. // wants to keep focus inside some editable element (like an input, or editor content)
  11089. fakeFocus: detail.fakeFocus,
  11090. // The TieredMenu detail.onHighlight function only relates to selecting an item,
  11091. // not a menu, and the menuComp it is passed is the menu, not the tiered menu.
  11092. // This makes it a difficult handler to use for a tieredmenu, so we are
  11093. // deprecating it.
  11094. onHighlight: (menuComp, itemComp) => {
  11095. // Trigger an internal event so that we can listen to it at the tieredmenu
  11096. // level, and call detail.onHighlightItem handler with tmenu, menu, and item.
  11097. const highlightData = {
  11098. menuComp,
  11099. itemComp
  11100. };
  11101. emitWith(menuComp, onMenuItemHighlightedEvent, highlightData);
  11102. },
  11103. onDehighlight: (menuComp, itemComp) => {
  11104. const dehighlightData = {
  11105. menuComp,
  11106. itemComp
  11107. };
  11108. // Trigger an internal event so that we can listen to it at the tieredmenu
  11109. // level, and call detail.onDehighlightItem handler with tmenu, menu, and item.
  11110. emitWith(menuComp, onMenuItemDehighlightedEvent, dehighlightData);
  11111. },
  11112. // The Menu itself doesn't set the focusManager based on the value of fakeFocus. It only uses
  11113. // its fakeFocus configuration for creating items that ignore focus, but it still needs to be
  11114. // told which focusManager to use. Perhaps we should change this, though it does allow for more
  11115. // complex focusManagers in single menus.
  11116. focusManager: detail.fakeFocus ? highlights() : dom$2()
  11117. });
  11118. // Only build the primary at first. Build the others as needed.
  11119. return name === primaryName ? {
  11120. type: 'prepared',
  11121. menu: container.getSystem().build(makeSketch())
  11122. } : {
  11123. type: 'notbuilt',
  11124. nbMenu: makeSketch
  11125. };
  11126. });
  11127. const layeredState = LayeredState.init();
  11128. const setup = (container) => {
  11129. const componentMap = buildMenus(container, detail.data.primary, detail.data.menus);
  11130. const directory = toDirectory();
  11131. layeredState.setContents(detail.data.primary, componentMap, detail.data.expansions, directory);
  11132. return layeredState.getPrimary();
  11133. };
  11134. const getItemValue = (item) => Representing.getValue(item).value;
  11135. // Find the first item with value `itemValue` in any of the menus inside this tiered menu structure
  11136. const getItemByValue = (_container, menus, itemValue) =>
  11137. // Can *greatly* improve the performance of this by calculating things up front.
  11138. findMap(menus, (menu) => {
  11139. if (!menu.getSystem().isConnected()) {
  11140. return Optional.none();
  11141. }
  11142. const candidates = Highlighting.getCandidates(menu);
  11143. return find$5(candidates, (c) => getItemValue(c) === itemValue);
  11144. });
  11145. const toDirectory = (_container) => map$1(detail.data.menus, (data, _menuName) => bind$3(data.items, (item) => item.type === 'separator' ? [] : [item.data.value]));
  11146. // This just sets the active menu. It will not set any active items.
  11147. const setActiveMenu = Highlighting.highlight;
  11148. // The item highlighted as active is either the currently active item in the menu,
  11149. // or the first one.
  11150. const setActiveMenuAndItem = (container, menu) => {
  11151. // Firstly, choose the active menu
  11152. setActiveMenu(container, menu);
  11153. // Then, choose the active item inside the active menu
  11154. Highlighting.getHighlighted(menu).orThunk(() => Highlighting.getFirst(menu)).each((item) => {
  11155. if (detail.fakeFocus) {
  11156. // When using fakeFocus, the items won't have a tab-index, so calling focusItem on them
  11157. // won't do anything. So we need to manually call highlighting, which is what fakeFocus
  11158. // uses. It would probably be better to use the focusManager specified.
  11159. Highlighting.highlight(menu, item);
  11160. }
  11161. else {
  11162. // We don't just use Focusing.focus here, because some items can have slightly different
  11163. // handling when they respond to a focusItem event. Widgets with autofocus, for example,
  11164. // will trigger a Keying.focusIn instead of Focusing.focus call, because they want to move
  11165. // the focus _inside_ the widget, not just to its outer level. The focusItem event
  11166. // performs a similar purpose to SystemEvents.focus() and potentially, could be consolidated.
  11167. dispatch(container, item.element, focusItem());
  11168. }
  11169. });
  11170. };
  11171. const getMenus = (state, menuValues) => cat(map$2(menuValues, (mv) => state.lookupMenu(mv).bind((prep) => prep.type === 'prepared' ? Optional.some(prep.menu) : Optional.none())));
  11172. const closeOthers = (container, state, path) => {
  11173. const others = getMenus(state, state.otherMenus(path));
  11174. each$1(others, (o) => {
  11175. // May not need to do the active menu thing.
  11176. remove$2(o.element, [detail.markers.backgroundMenu]);
  11177. if (!detail.stayInDom) {
  11178. Replacing.remove(container, o);
  11179. }
  11180. });
  11181. };
  11182. const getSubmenuParents = (container) => submenuParentItems.get().getOrThunk(() => {
  11183. const r = {};
  11184. const items = descendants(container.element, `.${detail.markers.item}`);
  11185. const parentItems = filter$2(items, (i) => get$g(i, 'aria-haspopup') === 'true');
  11186. each$1(parentItems, (i) => {
  11187. container.getSystem().getByDom(i).each((itemComp) => {
  11188. const key = getItemValue(itemComp);
  11189. r[key] = itemComp;
  11190. });
  11191. });
  11192. submenuParentItems.set(r);
  11193. return r;
  11194. });
  11195. // Not ideal. Ideally, we would like a map of item keys to components.
  11196. const updateAriaExpansions = (container, path) => {
  11197. const parentItems = getSubmenuParents(container);
  11198. each(parentItems, (v, k) => {
  11199. // Really should turn path into a Set
  11200. const expanded = contains$2(path, k);
  11201. set$9(v.element, 'aria-expanded', expanded);
  11202. });
  11203. };
  11204. const updateMenuPath = (container, state, path) => Optional.from(path[0]).bind((latestMenuName) => state.lookupMenu(latestMenuName).bind((menuPrep) => {
  11205. if (menuPrep.type === 'notbuilt') {
  11206. return Optional.none();
  11207. }
  11208. else {
  11209. const activeMenu = menuPrep.menu;
  11210. const rest = getMenus(state, path.slice(1));
  11211. each$1(rest, (r) => {
  11212. add$2(r.element, detail.markers.backgroundMenu);
  11213. });
  11214. if (!inBody(activeMenu.element)) {
  11215. Replacing.append(container, premade(activeMenu));
  11216. }
  11217. // Remove the background-menu class from the active menu
  11218. remove$2(activeMenu.element, [detail.markers.backgroundMenu]);
  11219. setActiveMenuAndItem(container, activeMenu);
  11220. closeOthers(container, state, path);
  11221. return Optional.some(activeMenu);
  11222. }
  11223. }));
  11224. let ExpandHighlightDecision;
  11225. (function (ExpandHighlightDecision) {
  11226. ExpandHighlightDecision[ExpandHighlightDecision["HighlightSubmenu"] = 0] = "HighlightSubmenu";
  11227. ExpandHighlightDecision[ExpandHighlightDecision["HighlightParent"] = 1] = "HighlightParent";
  11228. })(ExpandHighlightDecision || (ExpandHighlightDecision = {}));
  11229. const buildIfRequired = (container, menuName, menuPrep) => {
  11230. if (menuPrep.type === 'notbuilt') {
  11231. const menu = container.getSystem().build(menuPrep.nbMenu());
  11232. layeredState.setMenuBuilt(menuName, menu);
  11233. return menu;
  11234. }
  11235. else {
  11236. return menuPrep.menu;
  11237. }
  11238. };
  11239. const expandRight = (container, item, decision = ExpandHighlightDecision.HighlightSubmenu) => {
  11240. if (item.hasConfigured(Disabling) && Disabling.isDisabled(item)) {
  11241. return Optional.some(item);
  11242. }
  11243. else {
  11244. const value = getItemValue(item);
  11245. return layeredState.expand(value).bind((path) => {
  11246. // Called when submenus are opened by keyboard AND hovering navigation
  11247. updateAriaExpansions(container, path);
  11248. // When expanding, always select the first.
  11249. return Optional.from(path[0]).bind((menuName) => layeredState.lookupMenu(menuName).bind((activeMenuPrep) => {
  11250. const activeMenu = buildIfRequired(container, menuName, activeMenuPrep);
  11251. // DUPE with above. Fix later.
  11252. if (!inBody(activeMenu.element)) {
  11253. Replacing.append(container, premade(activeMenu));
  11254. }
  11255. // updateMenuPath is the code which changes the active menu. We don't always
  11256. // want to change the active menu. Sometimes, we just want to show it (e.g. hover)
  11257. detail.onOpenSubmenu(container, item, activeMenu, reverse(path));
  11258. if (decision === ExpandHighlightDecision.HighlightSubmenu) {
  11259. Highlighting.highlightFirst(activeMenu);
  11260. return updateMenuPath(container, layeredState, path);
  11261. }
  11262. else {
  11263. Highlighting.dehighlightAll(activeMenu);
  11264. return Optional.some(item);
  11265. }
  11266. }));
  11267. });
  11268. }
  11269. };
  11270. const collapseLeft = (container, item) => {
  11271. const value = getItemValue(item);
  11272. return layeredState.collapse(value).bind((path) => {
  11273. // Called when submenus are closed because of KEYBOARD navigation
  11274. updateAriaExpansions(container, path);
  11275. return updateMenuPath(container, layeredState, path).map((activeMenu) => {
  11276. detail.onCollapseMenu(container, item, activeMenu);
  11277. return activeMenu;
  11278. });
  11279. });
  11280. };
  11281. const updateView = (container, item) => {
  11282. const value = getItemValue(item);
  11283. return layeredState.refresh(value).bind((path) => {
  11284. // Only this function collapses irrelevant submenus when navigating by HOVERING.
  11285. // Does mean this is called twice when navigating by hovering, since both
  11286. // updateView and expandRight are called by the ItemEvents.hover() handler
  11287. updateAriaExpansions(container, path);
  11288. return updateMenuPath(container, layeredState, path);
  11289. });
  11290. };
  11291. const onRight = (container, item) => inside(item.element) ? Optional.none() : expandRight(container, item, ExpandHighlightDecision.HighlightSubmenu);
  11292. const onLeft = (container, item) =>
  11293. // Exclude inputs, textareas etc.
  11294. inside(item.element) ? Optional.none() : collapseLeft(container, item);
  11295. const onEscape = (container, item) => collapseLeft(container, item).orThunk(() => detail.onEscape(container, item).map(() => container) // This should only fire when the user presses ESC ... not any other close.
  11296. );
  11297. const keyOnItem = (f) => (container, simulatedEvent) => {
  11298. // 2022-08-16 This seems to be the only code in alloy that actually uses
  11299. // the getSource aspect of an event. Remember, that this code is firing
  11300. // when an event bubbles up the tiered menu, e.g. left arrow key.
  11301. // The only current code that sets the source manually is in the Widget item
  11302. // type, and it only sets the source when it is using autofocus. Autofocus
  11303. // is used to essentially treat the widget like it is the top-level item, so
  11304. // when events originate from *within* the widget, their source is changed to
  11305. // the top-level item. Consider removing EventSource from alloy altogether.
  11306. return closest$3(simulatedEvent.getSource(), `.${detail.markers.item}`)
  11307. .bind((target) => container.getSystem().getByDom(target).toOptional().bind((item) => f(container, item).map(always)));
  11308. };
  11309. // NOTE: Many of these events rely on identifying the current item by information
  11310. // sent with the event. However, in situations where you are using fakeFocus, but
  11311. // the real focus is still somewhere in the menu (e.g. search bar), this will lead to
  11312. // an incorrect identification of the active item. Ideally, instead of pulling the
  11313. // item from the event, we should just use Highlighting to identify the active item,
  11314. // and operate on it. However, not all events will necessarily have to happen on the
  11315. // active item, so we need to consider all the cases before making this change. For now,
  11316. // there will be a known limitation that if the real focus is still inside the TieredMenu,
  11317. // but the menu is using fakeFocus, then the actions will operate on the wrong targets.
  11318. // A workaround for that is to stop or cut or redispatch the events in whichever
  11319. // component has the real focus.
  11320. // TODO: TINY-9011 Introduce proper handling of fakeFocus in TieredMenu
  11321. const events = derive$2([
  11322. // Set "active-menu" for the menu with focus
  11323. run$1(focus(), (tmenu, simulatedEvent) => {
  11324. // Ensure the item is actually part of this menu structure, and not part of another menu structure that's bubbling.
  11325. const item = simulatedEvent.event.item;
  11326. layeredState.lookupItem(getItemValue(item)).each(() => {
  11327. const menu = simulatedEvent.event.menu;
  11328. Highlighting.highlight(tmenu, menu);
  11329. const value = getItemValue(simulatedEvent.event.item);
  11330. layeredState.refresh(value).each((path) => closeOthers(tmenu, layeredState, path));
  11331. });
  11332. }),
  11333. runOnExecute$1((component, simulatedEvent) => {
  11334. // Trigger on execute on the targeted element
  11335. // I.e. clicking on menu item
  11336. const target = simulatedEvent.event.target;
  11337. component.getSystem().getByDom(target).each((item) => {
  11338. const itemValue = getItemValue(item);
  11339. // INVESTIGATE: I don't know if this is doing anything any more. Check.
  11340. if (itemValue.indexOf('collapse-item') === 0) {
  11341. collapseLeft(component, item);
  11342. }
  11343. expandRight(component, item, ExpandHighlightDecision.HighlightSubmenu).fold(() => {
  11344. detail.onExecute(component, item);
  11345. }, noop);
  11346. });
  11347. }),
  11348. // Open the menu as soon as it is added to the DOM
  11349. runOnAttached((container, _simulatedEvent) => {
  11350. setup(container).each((primary) => {
  11351. Replacing.append(container, premade(primary));
  11352. detail.onOpenMenu(container, primary);
  11353. if (detail.highlightOnOpen === HighlightOnOpen.HighlightMenuAndItem) {
  11354. setActiveMenuAndItem(container, primary);
  11355. }
  11356. else if (detail.highlightOnOpen === HighlightOnOpen.HighlightJustMenu) {
  11357. setActiveMenu(container, primary);
  11358. }
  11359. });
  11360. }),
  11361. // Listen to the events bubbling up from menu about highlighting, and trigger
  11362. // our handlers with tmenu, menu and item
  11363. run$1(onMenuItemHighlightedEvent, (tmenuComp, se) => {
  11364. detail.onHighlightItem(tmenuComp, se.event.menuComp, se.event.itemComp);
  11365. }),
  11366. run$1(onMenuItemDehighlightedEvent, (tmenuComp, se) => {
  11367. detail.onDehighlightItem(tmenuComp, se.event.menuComp, se.event.itemComp);
  11368. }),
  11369. ...(detail.navigateOnHover ? [
  11370. // Hide any irrelevant submenus and expand any submenus based
  11371. // on hovered item
  11372. run$1(hover(), (tmenu, simulatedEvent) => {
  11373. const item = simulatedEvent.event.item;
  11374. updateView(tmenu, item);
  11375. expandRight(tmenu, item, ExpandHighlightDecision.HighlightParent);
  11376. detail.onHover(tmenu, item);
  11377. })
  11378. ] : [])
  11379. ]);
  11380. const getActiveItem = (container) => Highlighting.getHighlighted(container).bind(Highlighting.getHighlighted);
  11381. const collapseMenuApi = (container) => {
  11382. getActiveItem(container).each((currentItem) => {
  11383. collapseLeft(container, currentItem);
  11384. });
  11385. };
  11386. const highlightPrimary = (container) => {
  11387. layeredState.getPrimary().each((primary) => {
  11388. setActiveMenuAndItem(container, primary);
  11389. });
  11390. };
  11391. const extractMenuFromContainer = (container) => Optional.from(container.components()[0]).filter((comp) => get$g(comp.element, 'role') === 'menu');
  11392. const repositionMenus = (container) => {
  11393. // Get the primary menu
  11394. const maybeActivePrimary = layeredState.getPrimary().bind((primary) =>
  11395. // Get the triggering path (item, menu) up to the active item
  11396. getActiveItem(container).bind((currentItem) => {
  11397. const itemValue = getItemValue(currentItem);
  11398. const allMenus = values(layeredState.getMenus());
  11399. const preparedMenus = cat(map$2(allMenus, LayeredState.extractPreparedMenu));
  11400. return layeredState.getTriggeringPath(itemValue, (v) => getItemByValue(container, preparedMenus, v));
  11401. }).map((triggeringPath) => ({ primary, triggeringPath })));
  11402. maybeActivePrimary.fold(() => {
  11403. // When a menu is open but there is no activeItem, we get the menu from the container.
  11404. extractMenuFromContainer(container).each((primaryMenu) => {
  11405. detail.onRepositionMenu(container, primaryMenu, []);
  11406. });
  11407. }, ({ primary, triggeringPath }) => {
  11408. // Refresh all the menus up to the active item
  11409. detail.onRepositionMenu(container, primary, triggeringPath);
  11410. });
  11411. };
  11412. const apis = {
  11413. collapseMenu: collapseMenuApi,
  11414. highlightPrimary,
  11415. repositionMenus
  11416. };
  11417. return {
  11418. uid: detail.uid,
  11419. dom: detail.dom,
  11420. markers: detail.markers,
  11421. behaviours: augment(detail.tmenuBehaviours, [
  11422. Keying.config({
  11423. mode: 'special',
  11424. onRight: keyOnItem(onRight),
  11425. onLeft: keyOnItem(onLeft),
  11426. onEscape: keyOnItem(onEscape),
  11427. focusIn: (container, _keyInfo) => {
  11428. layeredState.getPrimary().each((primary) => {
  11429. dispatch(container, primary.element, focusItem());
  11430. });
  11431. }
  11432. }),
  11433. // Highlighting is used for highlighting the active menu
  11434. Highlighting.config({
  11435. highlightClass: detail.markers.selectedMenu,
  11436. itemClass: detail.markers.menu
  11437. }),
  11438. Composing.config({
  11439. find: (container) => {
  11440. return Highlighting.getHighlighted(container);
  11441. }
  11442. }),
  11443. Replacing.config({})
  11444. ]),
  11445. eventOrder: detail.eventOrder,
  11446. apis,
  11447. events
  11448. };
  11449. };
  11450. const collapseItem$1 = constant$1('collapse-item');
  11451. const tieredData = (primary, menus, expansions) => ({
  11452. primary,
  11453. menus,
  11454. expansions
  11455. });
  11456. const singleData = (name, menu) => ({
  11457. primary: name,
  11458. menus: wrap(name, menu),
  11459. expansions: {}
  11460. });
  11461. const collapseItem = (text) => ({
  11462. value: generate$6(collapseItem$1()),
  11463. meta: {
  11464. text
  11465. }
  11466. });
  11467. const tieredMenu = single({
  11468. name: 'TieredMenu',
  11469. configFields: [
  11470. onStrictKeyboardHandler('onExecute'),
  11471. onStrictKeyboardHandler('onEscape'),
  11472. onStrictHandler('onOpenMenu'),
  11473. onStrictHandler('onOpenSubmenu'),
  11474. onHandler('onRepositionMenu'),
  11475. onHandler('onCollapseMenu'),
  11476. // Ideally, we should validate that this is a valid value, but
  11477. // this is an number-based enum, so it would just be a number.
  11478. defaulted('highlightOnOpen', HighlightOnOpen.HighlightMenuAndItem),
  11479. requiredObjOf('data', [
  11480. required$1('primary'),
  11481. required$1('menus'),
  11482. required$1('expansions')
  11483. ]),
  11484. defaulted('fakeFocus', false),
  11485. onHandler('onHighlightItem'),
  11486. onHandler('onDehighlightItem'),
  11487. onHandler('onHover'),
  11488. tieredMenuMarkers(),
  11489. required$1('dom'),
  11490. defaulted('navigateOnHover', true),
  11491. defaulted('stayInDom', false),
  11492. field('tmenuBehaviours', [Keying, Highlighting, Composing, Replacing]),
  11493. defaulted('eventOrder', {})
  11494. ],
  11495. apis: {
  11496. collapseMenu: (apis, tmenu) => {
  11497. apis.collapseMenu(tmenu);
  11498. },
  11499. // This will highlight the primary menu AND an item in the primary menu
  11500. // Do not use just to set the active menu.
  11501. highlightPrimary: (apis, tmenu) => {
  11502. apis.highlightPrimary(tmenu);
  11503. },
  11504. repositionMenus: (apis, tmenu) => {
  11505. apis.repositionMenus(tmenu);
  11506. }
  11507. },
  11508. factory: make$5,
  11509. extraApis: {
  11510. tieredData,
  11511. singleData,
  11512. collapseItem
  11513. }
  11514. });
  11515. const suffix = constant$1('sink');
  11516. const partType = constant$1(optional({
  11517. name: suffix(),
  11518. overrides: constant$1({
  11519. dom: {
  11520. tag: 'div'
  11521. },
  11522. behaviours: derive$1([
  11523. Positioning.config({
  11524. // TODO: Make an internal sink also be able to be used with relative layouts
  11525. useFixed: always
  11526. })
  11527. ]),
  11528. events: derive$2([
  11529. // Sinks should not let keydown or click propagate
  11530. cutter(keydown()),
  11531. cutter(mousedown()),
  11532. cutter(click())
  11533. ])
  11534. })
  11535. }));
  11536. const schema$h = objOfOnly([
  11537. defaulted('isExtraPart', never),
  11538. optionObjOf('fireEventInstead', [
  11539. defaulted('event', dismissRequested())
  11540. ])
  11541. ]);
  11542. const receivingChannel$1 = (rawSpec) => {
  11543. const detail = asRawOrDie$1('Dismissal', schema$h, rawSpec);
  11544. return {
  11545. [dismissPopups()]: {
  11546. schema: objOfOnly([
  11547. required$1('target')
  11548. ]),
  11549. onReceive: (sandbox, data) => {
  11550. if (Sandboxing.isOpen(sandbox)) {
  11551. const isPart = Sandboxing.isPartOf(sandbox, data.target) || detail.isExtraPart(sandbox, data.target);
  11552. if (!isPart) {
  11553. detail.fireEventInstead.fold(() => Sandboxing.close(sandbox), (fe) => emit(sandbox, fe.event));
  11554. }
  11555. }
  11556. }
  11557. }
  11558. };
  11559. };
  11560. const schema$g = objOfOnly([
  11561. optionObjOf('fireEventInstead', [
  11562. defaulted('event', repositionRequested())
  11563. ]),
  11564. requiredFunction('doReposition')
  11565. ]);
  11566. const receivingChannel = (rawSpec) => {
  11567. const detail = asRawOrDie$1('Reposition', schema$g, rawSpec);
  11568. return {
  11569. [repositionPopups()]: {
  11570. onReceive: (sandbox) => {
  11571. if (Sandboxing.isOpen(sandbox)) {
  11572. detail.fireEventInstead.fold(() => detail.doReposition(sandbox), (fe) => emit(sandbox, fe.event));
  11573. }
  11574. }
  11575. }
  11576. };
  11577. };
  11578. const getAnchor = (detail, component) => {
  11579. const hotspot = detail.getHotspot(component).getOr(component);
  11580. const type = 'hotspot';
  11581. const overrides = detail.getAnchorOverrides();
  11582. return detail.layouts.fold(() => ({ type, hotspot, overrides }), (layouts) => ({ type, hotspot, overrides, layouts }));
  11583. };
  11584. const fetch$1 = (detail, mapFetch, component) => {
  11585. const fetcher = detail.fetch;
  11586. return fetcher(component).map(mapFetch);
  11587. };
  11588. const openF = (detail, mapFetch, anchor, component, sandbox, externals, highlightOnOpen) => {
  11589. const futureData = fetch$1(detail, mapFetch, component);
  11590. const getLazySink = getSink(component, detail);
  11591. // TODO: Make this potentially a single menu also
  11592. return futureData.map((tdata) => tdata.bind((data) => {
  11593. const primaryMenu = data.menus[data.primary];
  11594. Optional.from(primaryMenu).each((menu) => {
  11595. detail.listRole.each((listRole) => {
  11596. menu.role = listRole;
  11597. });
  11598. });
  11599. return Optional.from(tieredMenu.sketch({
  11600. // Externals are configured by the "menu" part. It's called external because it isn't contained
  11601. // within the DOM descendants of the dropdown. You can configure things like `fakeFocus` here.
  11602. ...externals.menu(),
  11603. uid: generate$4(''),
  11604. data,
  11605. highlightOnOpen,
  11606. onOpenMenu: (tmenu, menu) => {
  11607. const sink = getLazySink().getOrDie();
  11608. Positioning.position(sink, menu, { anchor });
  11609. Sandboxing.decloak(sandbox);
  11610. },
  11611. onOpenSubmenu: (tmenu, item, submenu) => {
  11612. const sink = getLazySink().getOrDie();
  11613. Positioning.position(sink, submenu, {
  11614. anchor: {
  11615. type: 'submenu',
  11616. item
  11617. }
  11618. });
  11619. Sandboxing.decloak(sandbox);
  11620. },
  11621. onRepositionMenu: (tmenu, primaryMenu, submenuTriggers) => {
  11622. const sink = getLazySink().getOrDie();
  11623. Positioning.position(sink, primaryMenu, { anchor });
  11624. each$1(submenuTriggers, (st) => {
  11625. Positioning.position(sink, st.triggeredMenu, {
  11626. anchor: { type: 'submenu', item: st.triggeringItem }
  11627. });
  11628. });
  11629. },
  11630. onEscape: () => {
  11631. // Focus the triggering component after escaping the menu
  11632. Focusing.focus(component);
  11633. Sandboxing.close(sandbox);
  11634. return Optional.some(true);
  11635. }
  11636. }));
  11637. }));
  11638. };
  11639. // onOpenSync is because some operations need to be applied immediately, not wrapped in a future
  11640. // It can avoid things like flickering due to asynchronous bouncing
  11641. const open = (detail, mapFetch, hotspot, sandbox, externals, onOpenSync, highlightOnOpen) => {
  11642. const anchor = getAnchor(detail, hotspot);
  11643. const processed = openF(detail, mapFetch, anchor, hotspot, sandbox, externals, highlightOnOpen);
  11644. return processed.map((tdata) => {
  11645. // If we have data, display a menu. Else, close the menu if it was open
  11646. tdata.fold(() => {
  11647. if (Sandboxing.isOpen(sandbox)) {
  11648. Sandboxing.close(sandbox);
  11649. }
  11650. }, (data) => {
  11651. Sandboxing.cloak(sandbox);
  11652. Sandboxing.open(sandbox, data);
  11653. onOpenSync(sandbox);
  11654. });
  11655. return sandbox;
  11656. });
  11657. };
  11658. const close = (detail, mapFetch, component, sandbox, _externals, _onOpenSync, _highlightOnOpen) => {
  11659. Sandboxing.close(sandbox);
  11660. return Future.pure(sandbox);
  11661. };
  11662. const togglePopup = (detail, mapFetch, hotspot, externals, onOpenSync, highlightOnOpen) => {
  11663. const sandbox = Coupling.getCoupled(hotspot, 'sandbox');
  11664. const showing = Sandboxing.isOpen(sandbox);
  11665. const action = showing ? close : open;
  11666. return action(detail, mapFetch, hotspot, sandbox, externals, onOpenSync, highlightOnOpen);
  11667. };
  11668. const matchWidth = (hotspot, container, useMinWidth) => {
  11669. const menu = Composing.getCurrent(container).getOr(container);
  11670. const buttonWidth = get$c(hotspot.element);
  11671. if (useMinWidth) {
  11672. set$7(menu.element, 'min-width', buttonWidth + 'px');
  11673. }
  11674. else {
  11675. set$6(menu.element, buttonWidth);
  11676. }
  11677. };
  11678. const getSink = (anyInSystem, sinkDetail) => anyInSystem
  11679. .getSystem()
  11680. .getByUid(sinkDetail.uid + '-' + suffix())
  11681. .map((internalSink) => () => Result.value(internalSink))
  11682. .getOrThunk(() => sinkDetail.lazySink.fold(() => () => Result.error(new Error('No internal sink is specified, nor could an external sink be found')), (lazySinkFn) => () => lazySinkFn(anyInSystem)));
  11683. const doRepositionMenus = (sandbox) => {
  11684. Sandboxing.getState(sandbox).each((tmenu) => {
  11685. tieredMenu.repositionMenus(tmenu);
  11686. });
  11687. };
  11688. const makeSandbox$1 = (detail, hotspot, extras) => {
  11689. const ariaControls = manager();
  11690. const onOpen = (component, menu) => {
  11691. const anchor = getAnchor(detail, hotspot);
  11692. ariaControls.link(hotspot.element);
  11693. if (detail.matchWidth) {
  11694. matchWidth(anchor.hotspot, menu, detail.useMinWidth);
  11695. }
  11696. detail.onOpen(anchor, component, menu);
  11697. if (extras !== undefined && extras.onOpen !== undefined) {
  11698. extras.onOpen(component, menu);
  11699. }
  11700. };
  11701. const onClose = (component, menu) => {
  11702. ariaControls.unlink(hotspot.element);
  11703. lazySink().getOr(menu).element.dom.dispatchEvent(new window.FocusEvent('focusout'));
  11704. if (extras !== undefined && extras.onClose !== undefined) {
  11705. extras.onClose(component, menu);
  11706. }
  11707. };
  11708. const lazySink = getSink(hotspot, detail);
  11709. return {
  11710. dom: {
  11711. tag: 'div',
  11712. classes: detail.sandboxClasses,
  11713. // TODO: Add aria-selected attribute
  11714. attributes: {
  11715. id: ariaControls.id,
  11716. }
  11717. },
  11718. behaviours: SketchBehaviours.augment(detail.sandboxBehaviours, [
  11719. Representing.config({
  11720. store: {
  11721. mode: 'memory',
  11722. initialValue: hotspot
  11723. }
  11724. }),
  11725. Sandboxing.config({
  11726. onOpen,
  11727. onClose,
  11728. isPartOf: (container, data, queryElem) => {
  11729. return isPartOf(data, queryElem) || isPartOf(hotspot, queryElem);
  11730. },
  11731. getAttachPoint: () => {
  11732. return lazySink().getOrDie();
  11733. }
  11734. }),
  11735. // The Composing of the dropdown here is the the active menu of the TieredMenu
  11736. // inside the sandbox.
  11737. Composing.config({
  11738. find: (sandbox) => {
  11739. return Sandboxing.getState(sandbox).bind((menu) => Composing.getCurrent(menu));
  11740. }
  11741. }),
  11742. Receiving.config({
  11743. channels: {
  11744. ...receivingChannel$1({
  11745. isExtraPart: never
  11746. }),
  11747. ...receivingChannel({
  11748. doReposition: doRepositionMenus
  11749. })
  11750. }
  11751. })
  11752. ])
  11753. };
  11754. };
  11755. const repositionMenus = (comp) => {
  11756. const sandbox = Coupling.getCoupled(comp, 'sandbox');
  11757. doRepositionMenus(sandbox);
  11758. };
  11759. // TODO: Roll this back into Fields at some point
  11760. // Unfortunately there appears to be a cyclical dependency or something that's preventing it, but for now this will do as it's home
  11761. const sandboxFields = () => [
  11762. defaulted('sandboxClasses', []),
  11763. SketchBehaviours.field('sandboxBehaviours', [Composing, Receiving, Sandboxing, Representing])
  11764. ];
  11765. const schema$f = constant$1([
  11766. required$1('dom'),
  11767. required$1('fetch'),
  11768. onHandler('onOpen'),
  11769. onKeyboardHandler('onExecute'),
  11770. defaulted('getHotspot', Optional.some),
  11771. defaulted('getAnchorOverrides', constant$1({})),
  11772. schema$n(),
  11773. field('dropdownBehaviours', [Toggling, Coupling, Keying, Focusing]),
  11774. required$1('toggleClass'),
  11775. defaulted('eventOrder', {}),
  11776. option$3('lazySink'),
  11777. defaulted('matchWidth', false),
  11778. defaulted('useMinWidth', false),
  11779. option$3('role'),
  11780. option$3('listRole'),
  11781. ].concat(sandboxFields()));
  11782. const parts$c = constant$1([
  11783. external$1({
  11784. schema: [
  11785. tieredMenuMarkers(),
  11786. // Defining a defaulted field isn't necessary when dealing with
  11787. // external parts, because the post-boulder part spec is not passed
  11788. // through to any of these functions (defaults, overrides etc.). So all
  11789. // this does is make it a bit clearer what you should expect, but remember
  11790. // that the default value here is irrelevant!
  11791. defaulted('fakeFocus', false)
  11792. ],
  11793. name: 'menu',
  11794. defaults: (detail) => {
  11795. return {
  11796. onExecute: detail.onExecute
  11797. };
  11798. }
  11799. }),
  11800. partType()
  11801. ]);
  11802. const factory$k = (detail, components, _spec, externals) => {
  11803. const lookupAttr = (attr) => get$h(detail.dom, 'attributes').bind((attrs) => get$h(attrs, attr));
  11804. const switchToMenu = (sandbox) => {
  11805. Sandboxing.getState(sandbox).each((tmenu) => {
  11806. // This will highlight the menu AND the item
  11807. tieredMenu.highlightPrimary(tmenu);
  11808. });
  11809. };
  11810. const togglePopup$1 = (dropdownComp, onOpenSync, highlightOnOpen) => {
  11811. return togglePopup(detail, identity, dropdownComp, externals, onOpenSync, highlightOnOpen);
  11812. };
  11813. const action = (component) => {
  11814. const onOpenSync = switchToMenu;
  11815. togglePopup$1(component, onOpenSync, HighlightOnOpen.HighlightMenuAndItem).get(noop);
  11816. };
  11817. const apis = {
  11818. expand: (comp) => {
  11819. if (!Toggling.isOn(comp)) {
  11820. togglePopup$1(comp, noop, HighlightOnOpen.HighlightNone).get(noop);
  11821. }
  11822. },
  11823. open: (comp) => {
  11824. if (!Toggling.isOn(comp)) {
  11825. togglePopup$1(comp, noop, HighlightOnOpen.HighlightMenuAndItem).get(noop);
  11826. }
  11827. },
  11828. refetch: (comp) => {
  11829. // Generally, the triggers for a refetch should make it so that the
  11830. // sandbox has been created, but it's not guaranteed, so we still handle the
  11831. // case where there isn't yet a sandbox.
  11832. const optSandbox = Coupling.getExistingCoupled(comp, 'sandbox');
  11833. return optSandbox.fold(() => {
  11834. // If we don't have a sandbox, refetch is the same as open,
  11835. // except we return when it is completed.
  11836. return togglePopup$1(comp, noop, HighlightOnOpen.HighlightMenuAndItem)
  11837. .map(noop);
  11838. }, (sandboxComp) => {
  11839. // We are intentionally not preserving the selected items when
  11840. // triggering a refetch, and will just highlight the first item.
  11841. // Note: this will mean that submenus will close. If we want to start
  11842. // preserving the selected items, we can't rely on the components themselves,
  11843. // so we'd need to use the item and menu values through Representing.
  11844. // However, be aware that alloy menus and items often have randomised values,
  11845. // so these might not be reliable either.
  11846. // NOTE: We use DropdownUtils.open directly, because we want it to 'open',
  11847. // even if it's already open. If we just used apis.open, it wouldn't do
  11848. // anything if it was already open, which means we wouldn't see the new
  11849. // refetched data.
  11850. return open(detail, identity, comp,
  11851. // NOTE: The TieredMenu is inside the sandbox. They aren't the same component.
  11852. sandboxComp, externals, noop, HighlightOnOpen.HighlightMenuAndItem).map(noop);
  11853. });
  11854. },
  11855. isOpen: Toggling.isOn,
  11856. close: (comp) => {
  11857. if (Toggling.isOn(comp)) {
  11858. togglePopup$1(comp, noop, HighlightOnOpen.HighlightMenuAndItem).get(noop);
  11859. }
  11860. },
  11861. // If we are open, refresh the menus in the tiered menu system
  11862. repositionMenus: (comp) => {
  11863. if (Toggling.isOn(comp)) {
  11864. repositionMenus(comp);
  11865. }
  11866. }
  11867. };
  11868. const triggerExecute = (comp, _se) => {
  11869. emitExecute(comp);
  11870. return Optional.some(true);
  11871. };
  11872. return {
  11873. uid: detail.uid,
  11874. dom: detail.dom,
  11875. components,
  11876. behaviours: augment(detail.dropdownBehaviours, [
  11877. Toggling.config({
  11878. toggleClass: detail.toggleClass,
  11879. aria: {
  11880. mode: 'expanded'
  11881. }
  11882. }),
  11883. Coupling.config({
  11884. others: {
  11885. sandbox: (hotspot) => {
  11886. return makeSandbox$1(detail, hotspot, {
  11887. onOpen: () => Toggling.on(hotspot),
  11888. onClose: () => Toggling.off(hotspot)
  11889. });
  11890. }
  11891. }
  11892. }),
  11893. Keying.config({
  11894. mode: 'special',
  11895. onSpace: triggerExecute,
  11896. onEnter: triggerExecute,
  11897. onDown: (comp, _se) => {
  11898. if (Dropdown.isOpen(comp)) {
  11899. const sandbox = Coupling.getCoupled(comp, 'sandbox');
  11900. switchToMenu(sandbox);
  11901. }
  11902. else {
  11903. Dropdown.open(comp);
  11904. }
  11905. return Optional.some(true);
  11906. },
  11907. onEscape: (comp, _se) => {
  11908. if (Dropdown.isOpen(comp)) {
  11909. Dropdown.close(comp);
  11910. return Optional.some(true);
  11911. }
  11912. else {
  11913. return Optional.none();
  11914. }
  11915. }
  11916. }),
  11917. Focusing.config({})
  11918. ]),
  11919. events: events(Optional.some(action)),
  11920. eventOrder: {
  11921. ...detail.eventOrder,
  11922. // Order, the button state is toggled first, so assumed !selected means close.
  11923. [execute$5()]: ['disabling', 'toggling', 'alloy.base.behaviour']
  11924. },
  11925. apis,
  11926. domModification: {
  11927. attributes: {
  11928. 'aria-haspopup': detail.listRole.getOr('true'),
  11929. ...detail.role.fold(() => ({}), (role) => ({ role })),
  11930. ...detail.dom.tag === 'button' ? { type: lookupAttr('type').getOr('button') } : {}
  11931. }
  11932. }
  11933. };
  11934. };
  11935. const Dropdown = composite({
  11936. name: 'Dropdown',
  11937. configFields: schema$f(),
  11938. partFields: parts$c(),
  11939. factory: factory$k,
  11940. apis: {
  11941. open: (apis, comp) => apis.open(comp),
  11942. refetch: (apis, comp) => apis.refetch(comp),
  11943. expand: (apis, comp) => apis.expand(comp),
  11944. close: (apis, comp) => apis.close(comp),
  11945. isOpen: (apis, comp) => apis.isOpen(comp),
  11946. repositionMenus: (apis, comp) => apis.repositionMenus(comp)
  11947. }
  11948. });
  11949. const owner$1 = 'form';
  11950. const schema$e = [
  11951. field('formBehaviours', [Representing])
  11952. ];
  11953. const getPartName$1 = (name) => '<alloy.field.' + name + '>';
  11954. const sketch$2 = (fSpec) => {
  11955. const parts = (() => {
  11956. const record = [];
  11957. const field = (name, config) => {
  11958. record.push(name);
  11959. return generateOne$1(owner$1, getPartName$1(name), config);
  11960. };
  11961. return {
  11962. field,
  11963. record: constant$1(record)
  11964. };
  11965. })();
  11966. const spec = fSpec(parts);
  11967. const partNames = parts.record();
  11968. // Unlike other sketches, a form does not know its parts in advance (as they represent each field
  11969. // in a particular form). Therefore, it needs to calculate the part names on the fly
  11970. const fieldParts = map$2(partNames, (n) => required({ name: n, pname: getPartName$1(n) }));
  11971. return composite$1(owner$1, schema$e, fieldParts, make$4, spec);
  11972. };
  11973. const toResult = (o, e) => o.fold(() => Result.error(e), Result.value);
  11974. const make$4 = (detail, components) => ({
  11975. uid: detail.uid,
  11976. dom: detail.dom,
  11977. components,
  11978. // Form has an assumption that every field must have composing, and that the composed element has representing.
  11979. behaviours: augment(detail.formBehaviours, [
  11980. Representing.config({
  11981. store: {
  11982. mode: 'manual',
  11983. getValue: (form) => {
  11984. const resPs = getAllParts(form, detail);
  11985. return map$1(resPs, (resPThunk, pName) => resPThunk().bind((v) => {
  11986. const opt = Composing.getCurrent(v);
  11987. return toResult(opt, new Error(`Cannot find a current component to extract the value from for form part '${pName}': ` + element(v.element)));
  11988. }).map(Representing.getValue));
  11989. },
  11990. setValue: (form, values) => {
  11991. each(values, (newValue, key) => {
  11992. getPart(form, detail, key).each((wrapper) => {
  11993. Composing.getCurrent(wrapper).each((field) => {
  11994. Representing.setValue(field, newValue);
  11995. });
  11996. });
  11997. });
  11998. }
  11999. }
  12000. })
  12001. ]),
  12002. apis: {
  12003. getField: (form, key) => {
  12004. // Returns an Optional (not a result);
  12005. return getPart(form, detail, key).bind(Composing.getCurrent);
  12006. }
  12007. }
  12008. });
  12009. const Form = {
  12010. getField: makeApi((apis, component, key) => apis.getField(component, key)),
  12011. sketch: sketch$2
  12012. };
  12013. const schema$d = constant$1([
  12014. required$1('dom'),
  12015. defaulted('shell', true),
  12016. field('toolbarBehaviours', [Replacing])
  12017. ]);
  12018. // TODO: Dupe with Toolbar
  12019. const enhanceGroups = () => ({
  12020. behaviours: derive$1([
  12021. Replacing.config({})
  12022. ])
  12023. });
  12024. const parts$b = constant$1([
  12025. // Note, is the container for putting all the groups in, not a group itself.
  12026. optional({
  12027. name: 'groups',
  12028. overrides: enhanceGroups
  12029. })
  12030. ]);
  12031. const factory$j = (detail, components, _spec, _externals) => {
  12032. const setGroups = (toolbar, groups) => {
  12033. getGroupContainer(toolbar).fold(() => {
  12034. // check that the group container existed. It may not have if the components
  12035. // did not list anything, and shell was false.
  12036. // eslint-disable-next-line no-console
  12037. console.error('Toolbar was defined to not be a shell, but no groups container was specified in components');
  12038. throw new Error('Toolbar was defined to not be a shell, but no groups container was specified in components');
  12039. }, (container) => {
  12040. Replacing.set(container, groups);
  12041. });
  12042. };
  12043. const getGroupContainer = (component) => detail.shell ? Optional.some(component) : getPart(component, detail, 'groups');
  12044. // In shell mode, the group overrides need to be added to the main container, and there can be no children
  12045. const extra = detail.shell ? { behaviours: [Replacing.config({})], components: [] } : { behaviours: [], components };
  12046. return {
  12047. uid: detail.uid,
  12048. dom: detail.dom,
  12049. components: extra.components,
  12050. behaviours: augment(detail.toolbarBehaviours, extra.behaviours),
  12051. apis: {
  12052. setGroups,
  12053. refresh: noop
  12054. },
  12055. domModification: {
  12056. attributes: {
  12057. role: 'group'
  12058. }
  12059. }
  12060. };
  12061. };
  12062. const Toolbar = composite({
  12063. name: 'Toolbar',
  12064. configFields: schema$d(),
  12065. partFields: parts$b(),
  12066. factory: factory$j,
  12067. apis: {
  12068. setGroups: (apis, toolbar, groups) => {
  12069. apis.setGroups(toolbar, groups);
  12070. }
  12071. }
  12072. });
  12073. const schema$c = constant$1([
  12074. markers$1(['toggledClass']),
  12075. required$1('lazySink'),
  12076. requiredFunction('fetch'),
  12077. optionFunction('getBounds'),
  12078. optionObjOf('fireDismissalEventInstead', [
  12079. defaulted('event', dismissRequested())
  12080. ]),
  12081. schema$n(),
  12082. onHandler('onToggled'),
  12083. ]);
  12084. const parts$a = constant$1([
  12085. external$1({
  12086. name: 'button',
  12087. overrides: (detail) => ({
  12088. dom: {
  12089. attributes: {
  12090. 'aria-haspopup': 'true'
  12091. }
  12092. },
  12093. buttonBehaviours: derive$1([
  12094. Toggling.config({
  12095. toggleClass: detail.markers.toggledClass,
  12096. aria: {
  12097. mode: 'expanded'
  12098. },
  12099. toggleOnExecute: false,
  12100. /**
  12101. * For FloatingToolbars, we can hook up our `onToggled` handler directly to the Toggling
  12102. * because we don't have to worry about any animations.
  12103. *
  12104. * Unfortunately, for SlidingToolbars, Toggling is more directly hooked into the animation for growing,
  12105. * so to have an event `onToggled` that doesn't care about the animation, we can't just hook into the Toggling config.
  12106. */
  12107. onToggled: detail.onToggled
  12108. })
  12109. ])
  12110. })
  12111. }),
  12112. external$1({
  12113. factory: Toolbar,
  12114. schema: schema$d(),
  12115. name: 'toolbar',
  12116. overrides: (detail) => {
  12117. return {
  12118. toolbarBehaviours: derive$1([
  12119. Keying.config({
  12120. mode: 'cyclic',
  12121. onEscape: (comp) => {
  12122. getPart(comp, detail, 'button').each(Focusing.focus);
  12123. // Don't return true here, as we need to allow the sandbox to handle the escape to close the overflow
  12124. return Optional.none();
  12125. }
  12126. })
  12127. ])
  12128. };
  12129. }
  12130. })
  12131. ]);
  12132. const shouldSkipFocus = value$2();
  12133. const toggleWithoutFocusing = (button, externals) => {
  12134. shouldSkipFocus.set(true);
  12135. toggle$1(button, externals);
  12136. shouldSkipFocus.clear();
  12137. };
  12138. const toggle$1 = (button, externals) => {
  12139. const toolbarSandbox = Coupling.getCoupled(button, 'toolbarSandbox');
  12140. if (Sandboxing.isOpen(toolbarSandbox)) {
  12141. Sandboxing.close(toolbarSandbox);
  12142. }
  12143. else {
  12144. Sandboxing.open(toolbarSandbox, externals.toolbar());
  12145. }
  12146. };
  12147. const position = (button, toolbar, detail, layouts) => {
  12148. const bounds = detail.getBounds.map((bounder) => bounder());
  12149. const sink = detail.lazySink(button).getOrDie();
  12150. Positioning.positionWithinBounds(sink, toolbar, {
  12151. anchor: {
  12152. type: 'hotspot',
  12153. hotspot: button,
  12154. layouts,
  12155. overrides: {
  12156. maxWidthFunction: expandable()
  12157. }
  12158. }
  12159. }, bounds);
  12160. };
  12161. const setGroups$1 = (button, toolbar, detail, layouts, groups) => {
  12162. Toolbar.setGroups(toolbar, groups);
  12163. position(button, toolbar, detail, layouts);
  12164. Toggling.on(button);
  12165. };
  12166. const makeSandbox = (button, spec, detail) => {
  12167. const ariaControls = manager();
  12168. const onOpen = (sandbox, toolbar) => {
  12169. const skipFocus = shouldSkipFocus.get().getOr(false);
  12170. detail.fetch().get((groups) => {
  12171. setGroups$1(button, toolbar, detail, spec.layouts, groups);
  12172. ariaControls.link(button.element);
  12173. if (!skipFocus) {
  12174. Keying.focusIn(toolbar);
  12175. }
  12176. });
  12177. };
  12178. const onClose = () => {
  12179. // Toggle and focus the button
  12180. Toggling.off(button);
  12181. if (!shouldSkipFocus.get().getOr(false)) {
  12182. Focusing.focus(button);
  12183. }
  12184. ariaControls.unlink(button.element);
  12185. };
  12186. return {
  12187. dom: {
  12188. tag: 'div',
  12189. attributes: {
  12190. id: ariaControls.id
  12191. }
  12192. },
  12193. behaviours: derive$1([
  12194. Keying.config({
  12195. mode: 'special',
  12196. onEscape: (comp) => {
  12197. Sandboxing.close(comp);
  12198. return Optional.some(true);
  12199. }
  12200. }),
  12201. Sandboxing.config({
  12202. onOpen,
  12203. onClose,
  12204. isPartOf: (container, data, queryElem) => {
  12205. return isPartOf(data, queryElem) || isPartOf(button, queryElem);
  12206. },
  12207. getAttachPoint: () => {
  12208. return detail.lazySink(button).getOrDie();
  12209. }
  12210. }),
  12211. Receiving.config({
  12212. channels: {
  12213. ...receivingChannel$1({
  12214. isExtraPart: never,
  12215. ...detail.fireDismissalEventInstead.map((fe) => ({ fireEventInstead: { event: fe.event } })).getOr({})
  12216. }),
  12217. ...receivingChannel({
  12218. doReposition: () => {
  12219. Sandboxing.getState(Coupling.getCoupled(button, 'toolbarSandbox')).each((toolbar) => {
  12220. position(button, toolbar, detail, spec.layouts);
  12221. });
  12222. }
  12223. })
  12224. }
  12225. })
  12226. ])
  12227. };
  12228. };
  12229. const factory$i = (detail, components, spec, externals) => ({
  12230. ...Button.sketch({
  12231. ...externals.button(),
  12232. action: (button) => {
  12233. toggle$1(button, externals);
  12234. },
  12235. buttonBehaviours: SketchBehaviours.augment({ dump: externals.button().buttonBehaviours }, [
  12236. Coupling.config({
  12237. others: {
  12238. toolbarSandbox: (button) => {
  12239. return makeSandbox(button, spec, detail);
  12240. }
  12241. }
  12242. })
  12243. ])
  12244. }),
  12245. apis: {
  12246. setGroups: (button, groups) => {
  12247. Sandboxing.getState(Coupling.getCoupled(button, 'toolbarSandbox')).each((toolbar) => {
  12248. setGroups$1(button, toolbar, detail, spec.layouts, groups);
  12249. });
  12250. },
  12251. reposition: (button) => {
  12252. Sandboxing.getState(Coupling.getCoupled(button, 'toolbarSandbox')).each((toolbar) => {
  12253. position(button, toolbar, detail, spec.layouts);
  12254. });
  12255. },
  12256. toggle: (button) => {
  12257. toggle$1(button, externals);
  12258. },
  12259. toggleWithoutFocusing: (button) => {
  12260. toggleWithoutFocusing(button, externals);
  12261. },
  12262. getToolbar: (button) => {
  12263. return Sandboxing.getState(Coupling.getCoupled(button, 'toolbarSandbox'));
  12264. },
  12265. isOpen: (button) => {
  12266. return Sandboxing.isOpen(Coupling.getCoupled(button, 'toolbarSandbox'));
  12267. }
  12268. }
  12269. });
  12270. const FloatingToolbarButton = composite({
  12271. name: 'FloatingToolbarButton',
  12272. factory: factory$i,
  12273. configFields: schema$c(),
  12274. partFields: parts$a(),
  12275. apis: {
  12276. setGroups: (apis, button, groups) => {
  12277. apis.setGroups(button, groups);
  12278. },
  12279. reposition: (apis, button) => {
  12280. apis.reposition(button);
  12281. },
  12282. toggle: (apis, button) => {
  12283. apis.toggle(button);
  12284. },
  12285. toggleWithoutFocusing: (apis, button) => {
  12286. apis.toggleWithoutFocusing(button);
  12287. },
  12288. getToolbar: (apis, button) => apis.getToolbar(button),
  12289. isOpen: (apis, button) => apis.isOpen(button)
  12290. }
  12291. });
  12292. const schema$b = constant$1([
  12293. defaulted('prefix', 'form-field'),
  12294. field('fieldBehaviours', [Composing, Representing])
  12295. ]);
  12296. const parts$9 = constant$1([
  12297. optional({
  12298. schema: [required$1('dom')],
  12299. name: 'label'
  12300. }),
  12301. optional({
  12302. factory: {
  12303. sketch: (spec) => {
  12304. return {
  12305. uid: spec.uid,
  12306. dom: {
  12307. tag: 'span',
  12308. styles: {
  12309. display: 'none'
  12310. },
  12311. attributes: {
  12312. 'aria-hidden': 'true'
  12313. },
  12314. innerHtml: spec.text
  12315. }
  12316. };
  12317. }
  12318. },
  12319. schema: [required$1('text')],
  12320. name: 'aria-descriptor'
  12321. }),
  12322. required({
  12323. factory: {
  12324. sketch: (spec) => {
  12325. const excludeFactory = exclude(spec, ['factory']);
  12326. return spec.factory.sketch(excludeFactory);
  12327. }
  12328. },
  12329. schema: [required$1('factory')],
  12330. name: 'field'
  12331. })
  12332. ]);
  12333. const factory$h = (detail, components, _spec, _externals) => {
  12334. const behaviours = augment(detail.fieldBehaviours, [
  12335. Composing.config({
  12336. find: (container) => {
  12337. return getPart(container, detail, 'field');
  12338. }
  12339. }),
  12340. Representing.config({
  12341. store: {
  12342. mode: 'manual',
  12343. getValue: (field) => {
  12344. return Composing.getCurrent(field).bind(Representing.getValue);
  12345. },
  12346. setValue: (field, value) => {
  12347. Composing.getCurrent(field).each((current) => {
  12348. Representing.setValue(current, value);
  12349. });
  12350. }
  12351. }
  12352. })
  12353. ]);
  12354. const events = derive$2([
  12355. // Used to be systemInit
  12356. runOnAttached((component, _simulatedEvent) => {
  12357. const ps = getParts(component, detail, ['label', 'field', 'aria-descriptor']);
  12358. ps.field().each((field) => {
  12359. const id = generate$6(detail.prefix);
  12360. ps.label().each((label) => {
  12361. // TODO: Find a nicer way of doing this.
  12362. set$9(label.element, 'for', id);
  12363. set$9(field.element, 'id', id);
  12364. });
  12365. ps['aria-descriptor']().each((descriptor) => {
  12366. const descriptorId = generate$6(detail.prefix);
  12367. set$9(descriptor.element, 'id', descriptorId);
  12368. set$9(field.element, 'aria-describedby', descriptorId);
  12369. });
  12370. });
  12371. })
  12372. ]);
  12373. const apis = {
  12374. getField: (container) => getPart(container, detail, 'field'),
  12375. getLabel: (container) =>
  12376. // TODO: Use constants for part names
  12377. getPart(container, detail, 'label')
  12378. };
  12379. return {
  12380. uid: detail.uid,
  12381. dom: detail.dom,
  12382. components,
  12383. behaviours,
  12384. events,
  12385. apis
  12386. };
  12387. };
  12388. const FormField = composite({
  12389. name: 'FormField',
  12390. configFields: schema$b(),
  12391. partFields: parts$9(),
  12392. factory: factory$h,
  12393. apis: {
  12394. getField: (apis, comp) => apis.getField(comp),
  12395. getLabel: (apis, comp) => apis.getLabel(comp)
  12396. }
  12397. });
  12398. const schema$a = constant$1([
  12399. defaulted('field1Name', 'field1'),
  12400. defaulted('field2Name', 'field2'),
  12401. onStrictHandler('onLockedChange'),
  12402. markers$1(['lockClass']),
  12403. defaulted('locked', false),
  12404. SketchBehaviours.field('coupledFieldBehaviours', [Composing, Representing]),
  12405. defaultedFunction('onInput', noop)
  12406. ]);
  12407. const getField = (comp, detail, partName) => getPart(comp, detail, partName).bind(Composing.getCurrent);
  12408. const coupledPart = (selfName, otherName) => required({
  12409. factory: FormField,
  12410. name: selfName,
  12411. overrides: (detail) => {
  12412. return {
  12413. fieldBehaviours: derive$1([
  12414. config('coupled-input-behaviour', [
  12415. run$1(input(), (me) => {
  12416. getField(me, detail, otherName).each((other) => {
  12417. getPart(me, detail, 'lock').each((lock) => {
  12418. // TODO IMPROVEMENT: Allow locker to fire onLockedChange if it is turned on after being off.
  12419. if (Toggling.isOn(lock)) {
  12420. detail.onLockedChange(me, other, lock);
  12421. }
  12422. detail.onInput(me);
  12423. });
  12424. });
  12425. })
  12426. ])
  12427. ])
  12428. };
  12429. }
  12430. });
  12431. const parts$8 = constant$1([
  12432. coupledPart('field1', 'field2'),
  12433. coupledPart('field2', 'field1'),
  12434. required({
  12435. factory: Button,
  12436. schema: [
  12437. required$1('dom')
  12438. ],
  12439. name: 'lock',
  12440. overrides: (detail) => {
  12441. return {
  12442. buttonBehaviours: derive$1([
  12443. Toggling.config({
  12444. selected: detail.locked,
  12445. toggleClass: detail.markers.lockClass,
  12446. aria: {
  12447. mode: 'pressed'
  12448. }
  12449. })
  12450. ])
  12451. };
  12452. }
  12453. })
  12454. ]);
  12455. const factory$g = (detail, components, _spec, _externals) => ({
  12456. uid: detail.uid,
  12457. dom: detail.dom,
  12458. components,
  12459. behaviours: SketchBehaviours.augment(detail.coupledFieldBehaviours, [
  12460. Composing.config({ find: Optional.some }),
  12461. Representing.config({
  12462. store: {
  12463. mode: 'manual',
  12464. getValue: (comp) => {
  12465. const parts = getPartsOrDie(comp, detail, ['field1', 'field2']);
  12466. return {
  12467. [detail.field1Name]: Representing.getValue(parts.field1()),
  12468. [detail.field2Name]: Representing.getValue(parts.field2())
  12469. };
  12470. },
  12471. setValue: (comp, value) => {
  12472. const parts = getPartsOrDie(comp, detail, ['field1', 'field2']);
  12473. if (hasNonNullableKey(value, detail.field1Name)) {
  12474. Representing.setValue(parts.field1(), value[detail.field1Name]);
  12475. }
  12476. if (hasNonNullableKey(value, detail.field2Name)) {
  12477. Representing.setValue(parts.field2(), value[detail.field2Name]);
  12478. }
  12479. }
  12480. }
  12481. })
  12482. ]),
  12483. apis: {
  12484. getField1: (component) => getPart(component, detail, 'field1'),
  12485. getField2: (component) => getPart(component, detail, 'field2'),
  12486. getLock: (component) => getPart(component, detail, 'lock')
  12487. }
  12488. });
  12489. const FormCoupledInputs = composite({
  12490. name: 'FormCoupledInputs',
  12491. configFields: schema$a(),
  12492. partFields: parts$8(),
  12493. factory: factory$g,
  12494. apis: {
  12495. getField1: (apis, component) => apis.getField1(component),
  12496. getField2: (apis, component) => apis.getField2(component),
  12497. getLock: (apis, component) => apis.getLock(component)
  12498. }
  12499. });
  12500. const factory$f = (detail, _spec) => {
  12501. const options = map$2(detail.options, (option) => ({
  12502. dom: {
  12503. tag: 'option',
  12504. value: option.value,
  12505. innerHtml: option.text
  12506. }
  12507. }));
  12508. const initialValues = detail.data.map((v) => wrap('initialValue', v)).getOr({});
  12509. return {
  12510. uid: detail.uid,
  12511. dom: {
  12512. tag: 'select',
  12513. classes: detail.selectClasses,
  12514. attributes: detail.selectAttributes
  12515. },
  12516. components: options,
  12517. behaviours: augment(detail.selectBehaviours, [
  12518. Focusing.config({}),
  12519. Representing.config({
  12520. store: {
  12521. mode: 'manual',
  12522. getValue: (select) => {
  12523. return get$5(select.element);
  12524. },
  12525. setValue: (select, newValue) => {
  12526. const firstOption = head(detail.options);
  12527. // This is probably generically useful ... may become a part of Representing.
  12528. const found = find$5(detail.options, (opt) => opt.value === newValue);
  12529. if (found.isSome()) {
  12530. set$4(select.element, newValue);
  12531. }
  12532. else if (select.element.dom.selectedIndex === -1 && newValue === '') {
  12533. /*
  12534. Sometimes after a redial alloy tries to set a new value, but if no value has been set in the data this used to fail. Now we set the value to the first option in the list if:
  12535. The index is out of range, indicating that the list of options have changed, or was never set.
  12536. The user is not trying to set a specific value (which would be user error)
  12537. */
  12538. firstOption.each((value) => set$4(select.element, value.value));
  12539. }
  12540. },
  12541. ...initialValues
  12542. }
  12543. })
  12544. ])
  12545. };
  12546. };
  12547. const HtmlSelect = single({
  12548. name: 'HtmlSelect',
  12549. configFields: [
  12550. required$1('options'),
  12551. field('selectBehaviours', [Focusing, Representing]),
  12552. defaulted('selectClasses', []),
  12553. defaulted('selectAttributes', {}),
  12554. option$3('data')
  12555. ],
  12556. factory: factory$f
  12557. });
  12558. const makeMenu = (detail, menuSandbox, placementSpec, menuSpec, getBounds) => {
  12559. const lazySink = () => detail.lazySink(menuSandbox);
  12560. const layouts = menuSpec.type === 'horizontal' ? { layouts: {
  12561. onLtr: () => belowOrAbove(),
  12562. onRtl: () => belowOrAboveRtl()
  12563. } } : {};
  12564. const isFirstTierSubmenu = (triggeringPaths) => triggeringPaths.length === 2; // primary and first tier menu === 2 items
  12565. const getSubmenuLayouts = (triggeringPaths) => isFirstTierSubmenu(triggeringPaths) ? layouts : {};
  12566. return tieredMenu.sketch({
  12567. dom: {
  12568. tag: 'div'
  12569. },
  12570. data: menuSpec.data,
  12571. markers: menuSpec.menu.markers,
  12572. highlightOnOpen: menuSpec.menu.highlightOnOpen,
  12573. fakeFocus: menuSpec.menu.fakeFocus,
  12574. onEscape: () => {
  12575. // Note for the future: this should possibly also call detail.onHide
  12576. Sandboxing.close(menuSandbox);
  12577. detail.onEscape.map((handler) => handler(menuSandbox));
  12578. return Optional.some(true);
  12579. },
  12580. onExecute: () => {
  12581. return Optional.some(true);
  12582. },
  12583. onOpenMenu: (tmenu, menu) => {
  12584. Positioning.positionWithinBounds(lazySink().getOrDie(), menu, placementSpec, getBounds());
  12585. },
  12586. onOpenSubmenu: (tmenu, item, submenu, triggeringPaths) => {
  12587. const sink = lazySink().getOrDie();
  12588. Positioning.position(sink, submenu, {
  12589. anchor: {
  12590. type: 'submenu',
  12591. item,
  12592. ...getSubmenuLayouts(triggeringPaths)
  12593. }
  12594. });
  12595. },
  12596. onRepositionMenu: (tmenu, primaryMenu, submenuTriggers) => {
  12597. const sink = lazySink().getOrDie();
  12598. Positioning.positionWithinBounds(sink, primaryMenu, placementSpec, getBounds());
  12599. each$1(submenuTriggers, (st) => {
  12600. const submenuLayouts = getSubmenuLayouts(st.triggeringPath);
  12601. Positioning.position(sink, st.triggeredMenu, {
  12602. anchor: { type: 'submenu', item: st.triggeringItem, ...submenuLayouts }
  12603. });
  12604. });
  12605. }
  12606. });
  12607. };
  12608. const factory$e = (detail, spec) => {
  12609. const isPartOfRelated = (sandbox, queryElem) => {
  12610. const related = detail.getRelated(sandbox);
  12611. return related.exists((rel) => isPartOf(rel, queryElem));
  12612. };
  12613. const setContent = (sandbox, thing) => {
  12614. // Keep the same location, and just change the content.
  12615. Sandboxing.setContent(sandbox, thing);
  12616. };
  12617. const showAt = (sandbox, thing, placementSpec) => {
  12618. const getBounds = Optional.none;
  12619. showWithinBounds(sandbox, thing, placementSpec, getBounds);
  12620. };
  12621. const showWithinBounds = (sandbox, thing, placementSpec, getBounds) => {
  12622. const sink = detail.lazySink(sandbox).getOrDie();
  12623. Sandboxing.openWhileCloaked(sandbox, thing, () => Positioning.positionWithinBounds(sink, sandbox, placementSpec, getBounds()));
  12624. Representing.setValue(sandbox, Optional.some({
  12625. mode: 'position',
  12626. config: placementSpec,
  12627. getBounds
  12628. }));
  12629. };
  12630. // TODO AP-191 write a test for showMenuAt
  12631. const showMenuAt = (sandbox, placementSpec, menuSpec) => {
  12632. showMenuWithinBounds(sandbox, placementSpec, menuSpec, Optional.none);
  12633. };
  12634. const showMenuWithinBounds = (sandbox, placementSpec, menuSpec, getBounds) => {
  12635. const menu = makeMenu(detail, sandbox, placementSpec, menuSpec, getBounds);
  12636. Sandboxing.open(sandbox, menu);
  12637. Representing.setValue(sandbox, Optional.some({
  12638. mode: 'menu',
  12639. menu
  12640. }));
  12641. };
  12642. const hide = (sandbox) => {
  12643. if (Sandboxing.isOpen(sandbox)) {
  12644. Representing.setValue(sandbox, Optional.none());
  12645. Sandboxing.close(sandbox);
  12646. }
  12647. };
  12648. const getContent = (sandbox) => Sandboxing.getState(sandbox);
  12649. const reposition = (sandbox) => {
  12650. if (Sandboxing.isOpen(sandbox)) {
  12651. Representing.getValue(sandbox).each((state) => {
  12652. switch (state.mode) {
  12653. case 'menu':
  12654. Sandboxing.getState(sandbox).each(tieredMenu.repositionMenus);
  12655. break;
  12656. case 'position':
  12657. const sink = detail.lazySink(sandbox).getOrDie();
  12658. Positioning.positionWithinBounds(sink, sandbox, state.config, state.getBounds());
  12659. break;
  12660. }
  12661. });
  12662. }
  12663. };
  12664. const apis = {
  12665. setContent,
  12666. showAt,
  12667. showWithinBounds,
  12668. showMenuAt,
  12669. showMenuWithinBounds,
  12670. hide,
  12671. getContent,
  12672. reposition,
  12673. isOpen: Sandboxing.isOpen
  12674. };
  12675. return {
  12676. uid: detail.uid,
  12677. dom: detail.dom,
  12678. behaviours: augment(detail.inlineBehaviours, [
  12679. Sandboxing.config({
  12680. isPartOf: (sandbox, data, queryElem) => {
  12681. return isPartOf(data, queryElem) || isPartOfRelated(sandbox, queryElem);
  12682. },
  12683. getAttachPoint: (sandbox) => {
  12684. return detail.lazySink(sandbox).getOrDie();
  12685. },
  12686. onOpen: (sandbox) => {
  12687. detail.onShow(sandbox);
  12688. },
  12689. onClose: (sandbox) => {
  12690. detail.onHide(sandbox);
  12691. }
  12692. }),
  12693. Representing.config({
  12694. store: {
  12695. mode: 'memory',
  12696. initialValue: Optional.none()
  12697. }
  12698. }),
  12699. Receiving.config({
  12700. channels: {
  12701. ...receivingChannel$1({
  12702. isExtraPart: spec.isExtraPart,
  12703. ...detail.fireDismissalEventInstead.map((fe) => ({ fireEventInstead: { event: fe.event } })).getOr({})
  12704. }),
  12705. ...receivingChannel({
  12706. ...detail.fireRepositionEventInstead.map((fe) => ({ fireEventInstead: { event: fe.event } })).getOr({}),
  12707. doReposition: reposition
  12708. })
  12709. }
  12710. })
  12711. ]),
  12712. eventOrder: detail.eventOrder,
  12713. apis
  12714. };
  12715. };
  12716. const InlineView = single({
  12717. name: 'InlineView',
  12718. configFields: [
  12719. required$1('lazySink'),
  12720. onHandler('onShow'),
  12721. onHandler('onHide'),
  12722. optionFunction('onEscape'),
  12723. field('inlineBehaviours', [Sandboxing, Representing, Receiving]),
  12724. optionObjOf('fireDismissalEventInstead', [
  12725. defaulted('event', dismissRequested())
  12726. ]),
  12727. optionObjOf('fireRepositionEventInstead', [
  12728. defaulted('event', repositionRequested())
  12729. ]),
  12730. defaulted('getRelated', Optional.none),
  12731. defaulted('isExtraPart', never),
  12732. defaulted('eventOrder', Optional.none)
  12733. ],
  12734. factory: factory$e,
  12735. apis: {
  12736. showAt: (apis, component, anchor, thing) => {
  12737. apis.showAt(component, anchor, thing);
  12738. },
  12739. showWithinBounds: (apis, component, anchor, thing, bounds) => {
  12740. apis.showWithinBounds(component, anchor, thing, bounds);
  12741. },
  12742. showMenuAt: (apis, component, anchor, menuSpec) => {
  12743. apis.showMenuAt(component, anchor, menuSpec);
  12744. },
  12745. showMenuWithinBounds: (apis, component, anchor, menuSpec, bounds) => {
  12746. apis.showMenuWithinBounds(component, anchor, menuSpec, bounds);
  12747. },
  12748. hide: (apis, component) => {
  12749. apis.hide(component);
  12750. },
  12751. isOpen: (apis, component) => apis.isOpen(component),
  12752. getContent: (apis, component) => apis.getContent(component),
  12753. setContent: (apis, component, thing) => {
  12754. apis.setContent(component, thing);
  12755. },
  12756. reposition: (apis, component) => {
  12757. apis.reposition(component);
  12758. }
  12759. }
  12760. });
  12761. const schema$9 = constant$1([
  12762. defaultedString('type', 'text'),
  12763. option$3('data'),
  12764. defaulted('inputAttributes', {}),
  12765. defaulted('inputStyles', {}),
  12766. defaulted('tag', 'input'),
  12767. defaulted('inputClasses', []),
  12768. onHandler('onSetValue'),
  12769. defaultedFunction('fromInputValue', identity),
  12770. defaultedFunction('toInputValue', identity),
  12771. defaulted('styles', {}),
  12772. defaulted('eventOrder', {}),
  12773. field('inputBehaviours', [Representing, Focusing]),
  12774. defaulted('selectOnFocus', true)
  12775. ]);
  12776. const focusBehaviours = (detail) => derive$1([
  12777. Focusing.config({
  12778. onFocus: !detail.selectOnFocus ? noop : (component) => {
  12779. const input = component.element;
  12780. const value = get$5(input);
  12781. // TODO: There are probably more types that can't handle setSelectionRange
  12782. if (detail.type !== 'range') {
  12783. input.dom.setSelectionRange(0, value.length);
  12784. }
  12785. }
  12786. })
  12787. ]);
  12788. const behaviours = (detail) => ({
  12789. ...focusBehaviours(detail),
  12790. ...augment(detail.inputBehaviours, [
  12791. Representing.config({
  12792. store: {
  12793. mode: 'manual',
  12794. // Propagating its Optional
  12795. ...detail.data.map((data) => ({ initialValue: data })).getOr({}),
  12796. getValue: (input) => {
  12797. return detail.fromInputValue(get$5(input.element));
  12798. },
  12799. setValue: (input, data) => {
  12800. const current = get$5(input.element);
  12801. // Only set it if it has changed ... otherwise the cursor goes to the end.
  12802. if (current !== data) {
  12803. set$4(input.element, detail.toInputValue(data));
  12804. }
  12805. }
  12806. },
  12807. onSetValue: detail.onSetValue
  12808. })
  12809. ])
  12810. });
  12811. const dom$1 = (detail) => ({
  12812. tag: detail.tag,
  12813. attributes: {
  12814. type: detail.type,
  12815. ...detail.inputAttributes
  12816. },
  12817. styles: detail.inputStyles,
  12818. classes: detail.inputClasses
  12819. });
  12820. const factory$d = (detail, _spec) => ({
  12821. uid: detail.uid,
  12822. dom: dom$1(detail),
  12823. // No children.
  12824. components: [],
  12825. behaviours: behaviours(detail),
  12826. eventOrder: detail.eventOrder
  12827. });
  12828. const Input = single({
  12829. name: 'Input',
  12830. configFields: schema$9(),
  12831. factory: factory$d
  12832. });
  12833. const parts$7 = generate$5(owner$2(), parts$e());
  12834. const labelledBy = (labelledElement, labelElement) => {
  12835. const labelId = getOpt(labelledElement, 'id')
  12836. .fold(() => {
  12837. const id = generate$6('dialog-label');
  12838. set$9(labelElement, 'id', id);
  12839. return id;
  12840. }, identity);
  12841. set$9(labelledElement, 'aria-labelledby', labelId);
  12842. };
  12843. const schema$8 = constant$1([
  12844. required$1('lazySink'),
  12845. option$3('dragBlockClass'),
  12846. defaultedFunction('getBounds', win),
  12847. defaulted('useTabstopAt', always),
  12848. defaulted('firstTabstop', 0),
  12849. defaulted('eventOrder', {}),
  12850. field('modalBehaviours', [Keying]),
  12851. onKeyboardHandler('onExecute'),
  12852. onStrictKeyboardHandler('onEscape')
  12853. ]);
  12854. const basic = { sketch: identity };
  12855. const parts$6 = constant$1([
  12856. optional({
  12857. name: 'draghandle',
  12858. overrides: (detail, spec) => {
  12859. return {
  12860. behaviours: derive$1([
  12861. Dragging.config({
  12862. mode: 'mouse',
  12863. getTarget: (handle) => {
  12864. return ancestor$1(handle, '[role="dialog"]').getOr(handle);
  12865. },
  12866. blockerClass: detail.dragBlockClass.getOrDie(
  12867. // TODO: Support errors in Optional getOrDie.
  12868. new Error('The drag blocker class was not specified for a dialog with a drag handle: \n' +
  12869. JSON.stringify(spec, null, 2)).message),
  12870. getBounds: detail.getDragBounds
  12871. })
  12872. ])
  12873. };
  12874. }
  12875. }),
  12876. required({
  12877. schema: [required$1('dom')],
  12878. name: 'title'
  12879. }),
  12880. required({
  12881. factory: basic,
  12882. schema: [required$1('dom')],
  12883. name: 'close'
  12884. }),
  12885. required({
  12886. factory: basic,
  12887. schema: [required$1('dom')],
  12888. name: 'body'
  12889. }),
  12890. optional({
  12891. factory: basic,
  12892. schema: [required$1('dom')],
  12893. name: 'footer'
  12894. }),
  12895. external$1({
  12896. factory: {
  12897. sketch: (spec, detail) =>
  12898. // Merging should take care of the uid
  12899. ({
  12900. ...spec,
  12901. dom: detail.dom,
  12902. components: detail.components
  12903. })
  12904. },
  12905. schema: [
  12906. defaulted('dom', {
  12907. tag: 'div',
  12908. styles: {
  12909. position: 'fixed',
  12910. left: '0px',
  12911. top: '0px',
  12912. right: '0px',
  12913. bottom: '0px'
  12914. }
  12915. }),
  12916. defaulted('components', [])
  12917. ],
  12918. name: 'blocker'
  12919. })
  12920. ]);
  12921. const factory$c = (detail, components, spec, externals) => {
  12922. const dialogComp = value$2();
  12923. // TODO IMPROVEMENT: Make close actually close the dialog by default!
  12924. const showDialog = (dialog) => {
  12925. dialogComp.set(dialog);
  12926. const sink = detail.lazySink(dialog).getOrDie();
  12927. const externalBlocker = externals.blocker();
  12928. const blocker = sink.getSystem().build({
  12929. ...externalBlocker,
  12930. components: externalBlocker.components.concat([
  12931. premade(dialog)
  12932. ]),
  12933. behaviours: derive$1([
  12934. Focusing.config({}),
  12935. config('dialog-blocker-events', [
  12936. // Ensure we use runOnSource otherwise this would cause an infinite loop, as `focusIn` would fire a `focusin` which would then get responded to and so forth
  12937. runOnSource(focusin(), () => {
  12938. Blocking.isBlocked(dialog) ? noop() : Keying.focusIn(dialog);
  12939. })
  12940. ])
  12941. ])
  12942. });
  12943. attach(sink, blocker);
  12944. Keying.focusIn(dialog);
  12945. };
  12946. const hideDialog = (dialog) => {
  12947. dialogComp.clear();
  12948. parent(dialog.element).each((blockerDom) => {
  12949. dialog.getSystem().getByDom(blockerDom).each((blocker) => {
  12950. detach(blocker);
  12951. });
  12952. });
  12953. };
  12954. const getDialogBody = (dialog) => getPartOrDie(dialog, detail, 'body');
  12955. const getDialogFooter = (dialog) => getPart(dialog, detail, 'footer');
  12956. const setBusy = (dialog, getBusySpec) => {
  12957. Blocking.block(dialog, getBusySpec);
  12958. };
  12959. const setIdle = (dialog) => {
  12960. Blocking.unblock(dialog);
  12961. };
  12962. const modalEventsId = generate$6('modal-events');
  12963. const eventOrder = {
  12964. ...detail.eventOrder,
  12965. [attachedToDom()]: [modalEventsId].concat(detail.eventOrder['alloy.system.attached'] || [])
  12966. };
  12967. const browser = detect$1();
  12968. return {
  12969. uid: detail.uid,
  12970. dom: detail.dom,
  12971. components,
  12972. apis: {
  12973. show: showDialog,
  12974. hide: hideDialog,
  12975. getBody: getDialogBody,
  12976. getFooter: getDialogFooter,
  12977. setIdle,
  12978. setBusy
  12979. },
  12980. eventOrder,
  12981. domModification: {
  12982. attributes: {
  12983. 'role': 'dialog',
  12984. 'aria-modal': 'true'
  12985. }
  12986. },
  12987. behaviours: augment(detail.modalBehaviours, [
  12988. Replacing.config({}),
  12989. Keying.config({
  12990. mode: 'cyclic',
  12991. onEnter: detail.onExecute,
  12992. onEscape: detail.onEscape,
  12993. useTabstopAt: detail.useTabstopAt,
  12994. firstTabstop: detail.firstTabstop
  12995. }),
  12996. Blocking.config({
  12997. getRoot: dialogComp.get
  12998. }),
  12999. config(modalEventsId, [
  13000. runOnAttached((c) => {
  13001. // TINY-10808 - Workaround to address the dialog header not being announced on VoiceOver with aria-labelledby, ideally we should use the aria-labelledby
  13002. const titleElm = getPartOrDie(c, detail, 'title').element;
  13003. const title = get$6(titleElm);
  13004. if (browser.os.isMacOS() && isNonNullable(title)) {
  13005. set$9(c.element, 'aria-label', title);
  13006. }
  13007. else {
  13008. labelledBy(c.element, titleElm);
  13009. }
  13010. })
  13011. ])
  13012. ])
  13013. };
  13014. };
  13015. const ModalDialog = composite({
  13016. name: 'ModalDialog',
  13017. configFields: schema$8(),
  13018. partFields: parts$6(),
  13019. factory: factory$c,
  13020. apis: {
  13021. show: (apis, dialog) => {
  13022. apis.show(dialog);
  13023. },
  13024. hide: (apis, dialog) => {
  13025. apis.hide(dialog);
  13026. },
  13027. getBody: (apis, dialog) => apis.getBody(dialog),
  13028. getFooter: (apis, dialog) => apis.getFooter(dialog),
  13029. setBusy: (apis, dialog, getBusySpec) => {
  13030. apis.setBusy(dialog, getBusySpec);
  13031. },
  13032. setIdle: (apis, dialog) => {
  13033. apis.setIdle(dialog);
  13034. }
  13035. }
  13036. });
  13037. const labelPart = optional({
  13038. schema: [required$1('dom')],
  13039. name: 'label'
  13040. });
  13041. const edgePart = (name) => optional({
  13042. name: '' + name + '-edge',
  13043. overrides: (detail) => {
  13044. const action = detail.model.manager.edgeActions[name];
  13045. // Not all edges have actions for all sliders.
  13046. // A horizontal slider will only have left and right, for instance,
  13047. // ignoring top, bottom and diagonal edges as they don't make sense in context of those sliders.
  13048. return action.fold(() => ({}), (a) => ({
  13049. events: derive$2([
  13050. runActionExtra(touchstart(), (comp, se, d) => a(comp, d), [detail]),
  13051. runActionExtra(mousedown(), (comp, se, d) => a(comp, d), [detail]),
  13052. runActionExtra(mousemove(), (comp, se, det) => {
  13053. if (det.mouseIsDown.get()) {
  13054. a(comp, det);
  13055. }
  13056. }, [detail])
  13057. ])
  13058. }));
  13059. }
  13060. });
  13061. // When the user touches the top left edge, it should move the thumb
  13062. const tlEdgePart = edgePart('top-left');
  13063. // When the user touches the top edge, it should move the thumb
  13064. const tedgePart = edgePart('top');
  13065. // When the user touches the top right edge, it should move the thumb
  13066. const trEdgePart = edgePart('top-right');
  13067. // When the user touches the right edge, it should move the thumb
  13068. const redgePart = edgePart('right');
  13069. // When the user touches the bottom right edge, it should move the thumb
  13070. const brEdgePart = edgePart('bottom-right');
  13071. // When the user touches the bottom edge, it should move the thumb
  13072. const bedgePart = edgePart('bottom');
  13073. // When the user touches the bottom left edge, it should move the thumb
  13074. const blEdgePart = edgePart('bottom-left');
  13075. // When the user touches the left edge, it should move the thumb
  13076. const ledgePart = edgePart('left');
  13077. // The thumb part needs to have position absolute to be positioned correctly
  13078. const thumbPart = required({
  13079. name: 'thumb',
  13080. defaults: constant$1({
  13081. dom: {
  13082. styles: { position: 'absolute' }
  13083. }
  13084. }),
  13085. overrides: (detail) => {
  13086. return {
  13087. events: derive$2([
  13088. // If the user touches the thumb itself, pretend they touched the spectrum instead. This
  13089. // allows sliding even when they touchstart the current value
  13090. redirectToPart(touchstart(), detail, 'spectrum'),
  13091. redirectToPart(touchmove(), detail, 'spectrum'),
  13092. redirectToPart(touchend(), detail, 'spectrum'),
  13093. redirectToPart(mousedown(), detail, 'spectrum'),
  13094. redirectToPart(mousemove(), detail, 'spectrum'),
  13095. redirectToPart(mouseup(), detail, 'spectrum')
  13096. ])
  13097. };
  13098. }
  13099. });
  13100. const isShift = (event) => isShift$1(event.event);
  13101. const spectrumPart = required({
  13102. schema: [
  13103. customField('mouseIsDown', () => Cell(false))
  13104. ],
  13105. name: 'spectrum',
  13106. overrides: (detail) => {
  13107. const modelDetail = detail.model;
  13108. const model = modelDetail.manager;
  13109. const setValueFrom = (component, simulatedEvent) => model.getValueFromEvent(simulatedEvent).map((value) => model.setValueFrom(component, detail, value));
  13110. return {
  13111. behaviours: derive$1([
  13112. // Move left and right along the spectrum
  13113. Keying.config({
  13114. mode: 'special',
  13115. onLeft: (spectrum, event) => model.onLeft(spectrum, detail, isShift(event)),
  13116. onRight: (spectrum, event) => model.onRight(spectrum, detail, isShift(event)),
  13117. onUp: (spectrum, event) => model.onUp(spectrum, detail, isShift(event)),
  13118. onDown: (spectrum, event) => model.onDown(spectrum, detail, isShift(event))
  13119. }),
  13120. Tabstopping.config({}),
  13121. Focusing.config({})
  13122. ]),
  13123. events: derive$2([
  13124. run$1(touchstart(), setValueFrom),
  13125. run$1(touchmove(), setValueFrom),
  13126. run$1(mousedown(), setValueFrom),
  13127. run$1(mousemove(), (spectrum, se) => {
  13128. if (detail.mouseIsDown.get()) {
  13129. setValueFrom(spectrum, se);
  13130. }
  13131. })
  13132. ])
  13133. };
  13134. }
  13135. });
  13136. var SliderParts = [
  13137. labelPart,
  13138. ledgePart,
  13139. redgePart,
  13140. tedgePart,
  13141. bedgePart,
  13142. tlEdgePart,
  13143. trEdgePart,
  13144. blEdgePart,
  13145. brEdgePart,
  13146. thumbPart,
  13147. spectrumPart
  13148. ];
  13149. const _sliderChangeEvent = 'slider.change.value';
  13150. const sliderChangeEvent = constant$1(_sliderChangeEvent);
  13151. const isTouchEvent$2 = (evt) => evt.type.indexOf('touch') !== -1;
  13152. const getEventSource = (simulatedEvent) => {
  13153. const evt = simulatedEvent.event.raw;
  13154. if (isTouchEvent$2(evt)) {
  13155. const touchEvent = evt;
  13156. return touchEvent.touches !== undefined && touchEvent.touches.length === 1 ?
  13157. Optional.some(touchEvent.touches[0]).map((t) => SugarPosition(t.clientX, t.clientY)) : Optional.none();
  13158. }
  13159. else {
  13160. const mouseEvent = evt;
  13161. return mouseEvent.clientX !== undefined ? Optional.some(mouseEvent).map((me) => SugarPosition(me.clientX, me.clientY)) : Optional.none();
  13162. }
  13163. };
  13164. const t = 'top', r = 'right', b = 'bottom', l = 'left';
  13165. // Values
  13166. const minX = (detail) => detail.model.minX;
  13167. const minY = (detail) => detail.model.minY;
  13168. const min1X = (detail) => detail.model.minX - 1;
  13169. const min1Y = (detail) => detail.model.minY - 1;
  13170. const maxX = (detail) => detail.model.maxX;
  13171. const maxY = (detail) => detail.model.maxY;
  13172. const max1X = (detail) => detail.model.maxX + 1;
  13173. const max1Y = (detail) => detail.model.maxY + 1;
  13174. const range = (detail, max, min) => max(detail) - min(detail);
  13175. const xRange = (detail) => range(detail, maxX, minX);
  13176. const yRange = (detail) => range(detail, maxY, minY);
  13177. const halfX = (detail) => xRange(detail) / 2;
  13178. const halfY = (detail) => yRange(detail) / 2;
  13179. const step = (detail, useMultiplier) => useMultiplier ? detail.stepSize * detail.speedMultiplier : detail.stepSize;
  13180. const snap = (detail) => detail.snapToGrid;
  13181. const snapStart = (detail) => detail.snapStart;
  13182. const rounded = (detail) => detail.rounded;
  13183. // Not great but... /shrug
  13184. const hasEdge = (detail, edgeName) => detail[edgeName + '-edge'] !== undefined;
  13185. const hasLEdge = (detail) => hasEdge(detail, l);
  13186. const hasREdge = (detail) => hasEdge(detail, r);
  13187. const hasTEdge = (detail) => hasEdge(detail, t);
  13188. const hasBEdge = (detail) => hasEdge(detail, b);
  13189. // Ew, any
  13190. const currentValue = (detail) => detail.model.value.get();
  13191. const xyValue = (x, y) => ({
  13192. x,
  13193. y
  13194. });
  13195. const fireSliderChange$3 = (component, value) => {
  13196. emitWith(component, sliderChangeEvent(), { value });
  13197. };
  13198. // North West XY
  13199. const setToTLEdgeXY = (edge, detail) => {
  13200. fireSliderChange$3(edge, xyValue(min1X(detail), min1Y(detail)));
  13201. };
  13202. // North
  13203. const setToTEdge = (edge, detail) => {
  13204. fireSliderChange$3(edge, min1Y(detail));
  13205. };
  13206. // North XY
  13207. const setToTEdgeXY = (edge, detail) => {
  13208. fireSliderChange$3(edge, xyValue(halfX(detail), min1Y(detail)));
  13209. };
  13210. // North East XY
  13211. const setToTREdgeXY = (edge, detail) => {
  13212. fireSliderChange$3(edge, xyValue(max1X(detail), min1Y(detail)));
  13213. };
  13214. // East
  13215. const setToREdge = (edge, detail) => {
  13216. fireSliderChange$3(edge, max1X(detail));
  13217. };
  13218. // East XY
  13219. const setToREdgeXY = (edge, detail) => {
  13220. fireSliderChange$3(edge, xyValue(max1X(detail), halfY(detail)));
  13221. };
  13222. // South East XY
  13223. const setToBREdgeXY = (edge, detail) => {
  13224. fireSliderChange$3(edge, xyValue(max1X(detail), max1Y(detail)));
  13225. };
  13226. // South
  13227. const setToBEdge = (edge, detail) => {
  13228. fireSliderChange$3(edge, max1Y(detail));
  13229. };
  13230. // South XY
  13231. const setToBEdgeXY = (edge, detail) => {
  13232. fireSliderChange$3(edge, xyValue(halfX(detail), max1Y(detail)));
  13233. };
  13234. // South West XY
  13235. const setToBLEdgeXY = (edge, detail) => {
  13236. fireSliderChange$3(edge, xyValue(min1X(detail), max1Y(detail)));
  13237. };
  13238. // West
  13239. const setToLEdge = (edge, detail) => {
  13240. fireSliderChange$3(edge, min1X(detail));
  13241. };
  13242. // West XY
  13243. const setToLEdgeXY = (edge, detail) => {
  13244. fireSliderChange$3(edge, xyValue(min1X(detail), halfY(detail)));
  13245. };
  13246. const reduceBy = (value, min, max, step) => {
  13247. if (value < min) {
  13248. return value;
  13249. }
  13250. else if (value > max) {
  13251. return max;
  13252. }
  13253. else if (value === min) {
  13254. return min - 1;
  13255. }
  13256. else {
  13257. return Math.max(min, value - step);
  13258. }
  13259. };
  13260. const increaseBy = (value, min, max, step) => {
  13261. if (value > max) {
  13262. return value;
  13263. }
  13264. else if (value < min) {
  13265. return min;
  13266. }
  13267. else if (value === max) {
  13268. return max + 1;
  13269. }
  13270. else {
  13271. return Math.min(max, value + step);
  13272. }
  13273. };
  13274. const capValue = (value, min, max) => Math.max(min, Math.min(max, value));
  13275. const snapValueOf = (value, min, max, step, snapStart) =>
  13276. // We are snapping by the step size. Therefore, find the nearest multiple of
  13277. // the step
  13278. snapStart.fold(() => {
  13279. // There is no initial snapping start, so just go from the minimum
  13280. const initValue = value - min;
  13281. const extraValue = Math.round(initValue / step) * step;
  13282. return capValue(min + extraValue, min - 1, max + 1);
  13283. }, (start) => {
  13284. // There is an initial snapping start, so using that as the starting point,
  13285. // calculate the nearest snap position based on the value
  13286. const remainder = (value - start) % step;
  13287. const adjustment = Math.round(remainder / step);
  13288. const rawSteps = Math.floor((value - start) / step);
  13289. const maxSteps = Math.floor((max - start) / step);
  13290. const numSteps = Math.min(maxSteps, rawSteps + adjustment);
  13291. const r = start + (numSteps * step);
  13292. return Math.max(start, r);
  13293. });
  13294. const findOffsetOf = (value, min, max) => Math.min(max, Math.max(value, min)) - min;
  13295. const findValueOf = (args) => {
  13296. const { min, max, range, value, step, snap, snapStart, rounded, hasMinEdge, hasMaxEdge, minBound, maxBound, screenRange } = args;
  13297. const capMin = hasMinEdge ? min - 1 : min;
  13298. const capMax = hasMaxEdge ? max + 1 : max;
  13299. if (value < minBound) {
  13300. return capMin;
  13301. }
  13302. else if (value > maxBound) {
  13303. return capMax;
  13304. }
  13305. else {
  13306. const offset = findOffsetOf(value, minBound, maxBound);
  13307. const newValue = capValue(((offset / screenRange) * range) + min, capMin, capMax);
  13308. if (snap && newValue >= min && newValue <= max) {
  13309. return snapValueOf(newValue, min, max, step, snapStart);
  13310. }
  13311. else if (rounded) {
  13312. return Math.round(newValue);
  13313. }
  13314. else {
  13315. return newValue;
  13316. }
  13317. }
  13318. };
  13319. const findOffsetOfValue$2 = (args) => {
  13320. const { min, max, range, value, hasMinEdge, hasMaxEdge, maxBound, maxOffset, centerMinEdge, centerMaxEdge } = args;
  13321. if (value < min) {
  13322. return hasMinEdge ? 0 : centerMinEdge;
  13323. }
  13324. else if (value > max) {
  13325. return hasMaxEdge ? maxBound : centerMaxEdge;
  13326. }
  13327. else {
  13328. // position along the slider
  13329. return (value - min) / range * maxOffset;
  13330. }
  13331. };
  13332. const top = 'top', right = 'right', bottom = 'bottom', left = 'left', width = 'width', height = 'height';
  13333. // Screen offsets from bounding client rect
  13334. const getBounds = (component) => component.element.dom.getBoundingClientRect();
  13335. const getBoundsProperty = (bounds, property) => bounds[property];
  13336. const getMinXBounds = (component) => {
  13337. const bounds = getBounds(component);
  13338. return getBoundsProperty(bounds, left);
  13339. };
  13340. const getMaxXBounds = (component) => {
  13341. const bounds = getBounds(component);
  13342. return getBoundsProperty(bounds, right);
  13343. };
  13344. const getMinYBounds = (component) => {
  13345. const bounds = getBounds(component);
  13346. return getBoundsProperty(bounds, top);
  13347. };
  13348. const getMaxYBounds = (component) => {
  13349. const bounds = getBounds(component);
  13350. return getBoundsProperty(bounds, bottom);
  13351. };
  13352. const getXScreenRange = (component) => {
  13353. const bounds = getBounds(component);
  13354. return getBoundsProperty(bounds, width);
  13355. };
  13356. const getYScreenRange = (component) => {
  13357. const bounds = getBounds(component);
  13358. return getBoundsProperty(bounds, height);
  13359. };
  13360. const getCenterOffsetOf = (componentMinEdge, componentMaxEdge, spectrumMinEdge) => (componentMinEdge + componentMaxEdge) / 2 - spectrumMinEdge;
  13361. const getXCenterOffSetOf = (component, spectrum) => {
  13362. const componentBounds = getBounds(component);
  13363. const spectrumBounds = getBounds(spectrum);
  13364. const componentMinEdge = getBoundsProperty(componentBounds, left);
  13365. const componentMaxEdge = getBoundsProperty(componentBounds, right);
  13366. const spectrumMinEdge = getBoundsProperty(spectrumBounds, left);
  13367. return getCenterOffsetOf(componentMinEdge, componentMaxEdge, spectrumMinEdge);
  13368. };
  13369. const getYCenterOffSetOf = (component, spectrum) => {
  13370. const componentBounds = getBounds(component);
  13371. const spectrumBounds = getBounds(spectrum);
  13372. const componentMinEdge = getBoundsProperty(componentBounds, top);
  13373. const componentMaxEdge = getBoundsProperty(componentBounds, bottom);
  13374. const spectrumMinEdge = getBoundsProperty(spectrumBounds, top);
  13375. return getCenterOffsetOf(componentMinEdge, componentMaxEdge, spectrumMinEdge);
  13376. };
  13377. // fire slider change event with x value
  13378. const fireSliderChange$2 = (spectrum, value) => {
  13379. emitWith(spectrum, sliderChangeEvent(), { value });
  13380. };
  13381. // find the value of the x offset of where the mouse was clicked from the model.
  13382. const findValueOfOffset$1 = (spectrum, detail, left) => {
  13383. const args = {
  13384. min: minX(detail),
  13385. max: maxX(detail),
  13386. range: xRange(detail),
  13387. value: left,
  13388. step: step(detail),
  13389. snap: snap(detail),
  13390. snapStart: snapStart(detail),
  13391. rounded: rounded(detail),
  13392. hasMinEdge: hasLEdge(detail),
  13393. hasMaxEdge: hasREdge(detail),
  13394. minBound: getMinXBounds(spectrum),
  13395. maxBound: getMaxXBounds(spectrum),
  13396. screenRange: getXScreenRange(spectrum)
  13397. };
  13398. return findValueOf(args);
  13399. };
  13400. // find the value and fire a slider change event, returning the value
  13401. const setValueFrom$2 = (spectrum, detail, value) => {
  13402. const xValue = findValueOfOffset$1(spectrum, detail, value);
  13403. const sliderVal = xValue;
  13404. fireSliderChange$2(spectrum, sliderVal);
  13405. return xValue;
  13406. };
  13407. // fire a slider change event with the minimum value
  13408. const setToMin$2 = (spectrum, detail) => {
  13409. const min = minX(detail);
  13410. fireSliderChange$2(spectrum, min);
  13411. };
  13412. // fire a slider change event with the maximum value
  13413. const setToMax$2 = (spectrum, detail) => {
  13414. const max = maxX(detail);
  13415. fireSliderChange$2(spectrum, max);
  13416. };
  13417. // move in a direction by step size. Fire change at the end
  13418. const moveBy$2 = (direction, spectrum, detail, useMultiplier) => {
  13419. const f = (direction > 0) ? increaseBy : reduceBy;
  13420. const xValue = f(currentValue(detail), minX(detail), maxX(detail), step(detail, useMultiplier));
  13421. fireSliderChange$2(spectrum, xValue);
  13422. return Optional.some(xValue);
  13423. };
  13424. const handleMovement$2 = (direction) => (spectrum, detail, useMultiplier) => moveBy$2(direction, spectrum, detail, useMultiplier).map(always);
  13425. // get x offset from event
  13426. const getValueFromEvent$2 = (simulatedEvent) => {
  13427. const pos = getEventSource(simulatedEvent);
  13428. return pos.map((p) => p.left);
  13429. };
  13430. // find the x offset of a given value from the model
  13431. const findOffsetOfValue$1 = (spectrum, detail, value, minEdge, maxEdge) => {
  13432. const minOffset = 0;
  13433. const maxOffset = getXScreenRange(spectrum);
  13434. const centerMinEdge = minEdge.bind((edge) => Optional.some(getXCenterOffSetOf(edge, spectrum))).getOr(minOffset);
  13435. const centerMaxEdge = maxEdge.bind((edge) => Optional.some(getXCenterOffSetOf(edge, spectrum))).getOr(maxOffset);
  13436. const args = {
  13437. min: minX(detail),
  13438. max: maxX(detail),
  13439. range: xRange(detail),
  13440. value,
  13441. hasMinEdge: hasLEdge(detail),
  13442. hasMaxEdge: hasREdge(detail),
  13443. minBound: getMinXBounds(spectrum),
  13444. minOffset,
  13445. maxBound: getMaxXBounds(spectrum),
  13446. maxOffset,
  13447. centerMinEdge,
  13448. centerMaxEdge
  13449. };
  13450. return findOffsetOfValue$2(args);
  13451. };
  13452. // find left offset for absolute positioning from a given value
  13453. const findPositionOfValue$1 = (slider, spectrum, value, minEdge, maxEdge, detail) => {
  13454. const offset = findOffsetOfValue$1(spectrum, detail, value, minEdge, maxEdge);
  13455. return (getMinXBounds(spectrum) - getMinXBounds(slider)) + offset;
  13456. };
  13457. // update the position of the thumb from the slider's current value
  13458. const setPositionFromValue$2 = (slider, thumb, detail, edges) => {
  13459. const value = currentValue(detail);
  13460. const pos = findPositionOfValue$1(slider, edges.getSpectrum(slider), value, edges.getLeftEdge(slider), edges.getRightEdge(slider), detail);
  13461. const thumbRadius = get$c(thumb.element) / 2;
  13462. set$7(thumb.element, 'left', (pos - thumbRadius) + 'px');
  13463. };
  13464. // Key Events
  13465. const onLeft$2 = handleMovement$2(-1);
  13466. const onRight$2 = handleMovement$2(1);
  13467. const onUp$2 = Optional.none;
  13468. const onDown$2 = Optional.none;
  13469. // Edge Click Actions
  13470. const edgeActions$2 = {
  13471. 'top-left': Optional.none(),
  13472. 'top': Optional.none(),
  13473. 'top-right': Optional.none(),
  13474. 'right': Optional.some(setToREdge),
  13475. 'bottom-right': Optional.none(),
  13476. 'bottom': Optional.none(),
  13477. 'bottom-left': Optional.none(),
  13478. 'left': Optional.some(setToLEdge)
  13479. };
  13480. var HorizontalModel = /*#__PURE__*/Object.freeze({
  13481. __proto__: null,
  13482. setValueFrom: setValueFrom$2,
  13483. setToMin: setToMin$2,
  13484. setToMax: setToMax$2,
  13485. findValueOfOffset: findValueOfOffset$1,
  13486. getValueFromEvent: getValueFromEvent$2,
  13487. findPositionOfValue: findPositionOfValue$1,
  13488. setPositionFromValue: setPositionFromValue$2,
  13489. onLeft: onLeft$2,
  13490. onRight: onRight$2,
  13491. onUp: onUp$2,
  13492. onDown: onDown$2,
  13493. edgeActions: edgeActions$2
  13494. });
  13495. // fire slider change event with y value
  13496. const fireSliderChange$1 = (spectrum, value) => {
  13497. emitWith(spectrum, sliderChangeEvent(), { value });
  13498. };
  13499. // find the value of the y offset of where the mouse was clicked from the model.
  13500. const findValueOfOffset = (spectrum, detail, top) => {
  13501. const args = {
  13502. min: minY(detail),
  13503. max: maxY(detail),
  13504. range: yRange(detail),
  13505. value: top,
  13506. step: step(detail),
  13507. snap: snap(detail),
  13508. snapStart: snapStart(detail),
  13509. rounded: rounded(detail),
  13510. hasMinEdge: hasTEdge(detail),
  13511. hasMaxEdge: hasBEdge(detail),
  13512. minBound: getMinYBounds(spectrum),
  13513. maxBound: getMaxYBounds(spectrum),
  13514. screenRange: getYScreenRange(spectrum)
  13515. };
  13516. return findValueOf(args);
  13517. };
  13518. // find the value and fire a slider change event, returning the value
  13519. const setValueFrom$1 = (spectrum, detail, value) => {
  13520. const yValue = findValueOfOffset(spectrum, detail, value);
  13521. const sliderVal = yValue;
  13522. fireSliderChange$1(spectrum, sliderVal);
  13523. return yValue;
  13524. };
  13525. // fire a slider change event with the minimum value
  13526. const setToMin$1 = (spectrum, detail) => {
  13527. const min = minY(detail);
  13528. fireSliderChange$1(spectrum, min);
  13529. };
  13530. // fire a slider change event with the maximum value
  13531. const setToMax$1 = (spectrum, detail) => {
  13532. const max = maxY(detail);
  13533. fireSliderChange$1(spectrum, max);
  13534. };
  13535. // move in a direction by step size. Fire change at the end
  13536. const moveBy$1 = (direction, spectrum, detail, useMultiplier) => {
  13537. const f = (direction > 0) ? increaseBy : reduceBy;
  13538. const yValue = f(currentValue(detail), minY(detail), maxY(detail), step(detail, useMultiplier));
  13539. fireSliderChange$1(spectrum, yValue);
  13540. return Optional.some(yValue);
  13541. };
  13542. const handleMovement$1 = (direction) => (spectrum, detail, useMultiplier) => moveBy$1(direction, spectrum, detail, useMultiplier).map(always);
  13543. // get y offset from event
  13544. const getValueFromEvent$1 = (simulatedEvent) => {
  13545. const pos = getEventSource(simulatedEvent);
  13546. return pos.map((p) => {
  13547. return p.top;
  13548. });
  13549. };
  13550. // find the y offset of a given value from the model
  13551. const findOffsetOfValue = (spectrum, detail, value, minEdge, maxEdge) => {
  13552. const minOffset = 0;
  13553. const maxOffset = getYScreenRange(spectrum);
  13554. const centerMinEdge = minEdge.bind((edge) => Optional.some(getYCenterOffSetOf(edge, spectrum))).getOr(minOffset);
  13555. const centerMaxEdge = maxEdge.bind((edge) => Optional.some(getYCenterOffSetOf(edge, spectrum))).getOr(maxOffset);
  13556. const args = {
  13557. min: minY(detail),
  13558. max: maxY(detail),
  13559. range: yRange(detail),
  13560. value,
  13561. hasMinEdge: hasTEdge(detail),
  13562. hasMaxEdge: hasBEdge(detail),
  13563. minBound: getMinYBounds(spectrum),
  13564. minOffset,
  13565. maxBound: getMaxYBounds(spectrum),
  13566. maxOffset,
  13567. centerMinEdge,
  13568. centerMaxEdge
  13569. };
  13570. return findOffsetOfValue$2(args);
  13571. };
  13572. // find left offset for absolute positioning from a given value
  13573. const findPositionOfValue = (slider, spectrum, value, minEdge, maxEdge, detail) => {
  13574. const offset = findOffsetOfValue(spectrum, detail, value, minEdge, maxEdge);
  13575. return (getMinYBounds(spectrum) - getMinYBounds(slider)) + offset;
  13576. };
  13577. // update the position of the thumb from the slider's current value
  13578. const setPositionFromValue$1 = (slider, thumb, detail, edges) => {
  13579. const value = currentValue(detail);
  13580. const pos = findPositionOfValue(slider, edges.getSpectrum(slider), value, edges.getTopEdge(slider), edges.getBottomEdge(slider), detail);
  13581. const thumbRadius = get$d(thumb.element) / 2;
  13582. set$7(thumb.element, 'top', (pos - thumbRadius) + 'px');
  13583. };
  13584. // Key Events
  13585. const onLeft$1 = Optional.none;
  13586. const onRight$1 = Optional.none;
  13587. const onUp$1 = handleMovement$1(-1);
  13588. const onDown$1 = handleMovement$1(1);
  13589. // Edge Click Actions
  13590. const edgeActions$1 = {
  13591. 'top-left': Optional.none(),
  13592. 'top': Optional.some(setToTEdge),
  13593. 'top-right': Optional.none(),
  13594. 'right': Optional.none(),
  13595. 'bottom-right': Optional.none(),
  13596. 'bottom': Optional.some(setToBEdge),
  13597. 'bottom-left': Optional.none(),
  13598. 'left': Optional.none()
  13599. };
  13600. var VerticalModel = /*#__PURE__*/Object.freeze({
  13601. __proto__: null,
  13602. setValueFrom: setValueFrom$1,
  13603. setToMin: setToMin$1,
  13604. setToMax: setToMax$1,
  13605. findValueOfOffset: findValueOfOffset,
  13606. getValueFromEvent: getValueFromEvent$1,
  13607. findPositionOfValue: findPositionOfValue,
  13608. setPositionFromValue: setPositionFromValue$1,
  13609. onLeft: onLeft$1,
  13610. onRight: onRight$1,
  13611. onUp: onUp$1,
  13612. onDown: onDown$1,
  13613. edgeActions: edgeActions$1
  13614. });
  13615. // fire slider change event with xy value
  13616. const fireSliderChange = (spectrum, value) => {
  13617. emitWith(spectrum, sliderChangeEvent(), { value });
  13618. };
  13619. const sliderValue = (x, y) => ({
  13620. x,
  13621. y
  13622. });
  13623. // find both values of x and y offsets of where the mouse was clicked from the model.
  13624. // then fire a slider change event with those values, returning the values
  13625. const setValueFrom = (spectrum, detail, value) => {
  13626. const xValue = findValueOfOffset$1(spectrum, detail, value.left);
  13627. const yValue = findValueOfOffset(spectrum, detail, value.top);
  13628. const val = sliderValue(xValue, yValue);
  13629. fireSliderChange(spectrum, val);
  13630. return val;
  13631. };
  13632. // move in a direction by step size. Fire change at the end
  13633. const moveBy = (direction, isVerticalMovement, spectrum, detail, useMultiplier) => {
  13634. const f = (direction > 0) ? increaseBy : reduceBy;
  13635. const xValue = isVerticalMovement ? currentValue(detail).x :
  13636. f(currentValue(detail).x, minX(detail), maxX(detail), step(detail, useMultiplier));
  13637. const yValue = !isVerticalMovement ? currentValue(detail).y :
  13638. f(currentValue(detail).y, minY(detail), maxY(detail), step(detail, useMultiplier));
  13639. fireSliderChange(spectrum, sliderValue(xValue, yValue));
  13640. return Optional.some(xValue);
  13641. };
  13642. const handleMovement = (direction, isVerticalMovement) => (spectrum, detail, useMultiplier) => moveBy(direction, isVerticalMovement, spectrum, detail, useMultiplier).map(always);
  13643. // fire a slider change event with the minimum value
  13644. const setToMin = (spectrum, detail) => {
  13645. const mX = minX(detail);
  13646. const mY = minY(detail);
  13647. fireSliderChange(spectrum, sliderValue(mX, mY));
  13648. };
  13649. // fire a slider change event with the maximum value
  13650. const setToMax = (spectrum, detail) => {
  13651. const mX = maxX(detail);
  13652. const mY = maxY(detail);
  13653. fireSliderChange(spectrum, sliderValue(mX, mY));
  13654. };
  13655. // get event data as a SugarPosition
  13656. const getValueFromEvent = (simulatedEvent) => getEventSource(simulatedEvent);
  13657. // update the position of the thumb from the slider's current value
  13658. const setPositionFromValue = (slider, thumb, detail, edges) => {
  13659. const value = currentValue(detail);
  13660. const xPos = findPositionOfValue$1(slider, edges.getSpectrum(slider), value.x, edges.getLeftEdge(slider), edges.getRightEdge(slider), detail);
  13661. const yPos = findPositionOfValue(slider, edges.getSpectrum(slider), value.y, edges.getTopEdge(slider), edges.getBottomEdge(slider), detail);
  13662. const thumbXRadius = get$c(thumb.element) / 2;
  13663. const thumbYRadius = get$d(thumb.element) / 2;
  13664. set$7(thumb.element, 'left', (xPos - thumbXRadius) + 'px');
  13665. set$7(thumb.element, 'top', (yPos - thumbYRadius) + 'px');
  13666. };
  13667. // Key Events
  13668. const onLeft = handleMovement(-1, false);
  13669. const onRight = handleMovement(1, false);
  13670. const onUp = handleMovement(-1, true);
  13671. const onDown = handleMovement(1, true);
  13672. // Edge Click Actions
  13673. const edgeActions = {
  13674. 'top-left': Optional.some(setToTLEdgeXY),
  13675. 'top': Optional.some(setToTEdgeXY),
  13676. 'top-right': Optional.some(setToTREdgeXY),
  13677. 'right': Optional.some(setToREdgeXY),
  13678. 'bottom-right': Optional.some(setToBREdgeXY),
  13679. 'bottom': Optional.some(setToBEdgeXY),
  13680. 'bottom-left': Optional.some(setToBLEdgeXY),
  13681. 'left': Optional.some(setToLEdgeXY)
  13682. };
  13683. var TwoDModel = /*#__PURE__*/Object.freeze({
  13684. __proto__: null,
  13685. setValueFrom: setValueFrom,
  13686. setToMin: setToMin,
  13687. setToMax: setToMax,
  13688. getValueFromEvent: getValueFromEvent,
  13689. setPositionFromValue: setPositionFromValue,
  13690. onLeft: onLeft,
  13691. onRight: onRight,
  13692. onUp: onUp,
  13693. onDown: onDown,
  13694. edgeActions: edgeActions
  13695. });
  13696. const SliderSchema = [
  13697. defaulted('stepSize', 1),
  13698. defaulted('speedMultiplier', 10),
  13699. defaulted('onChange', noop),
  13700. defaulted('onChoose', noop),
  13701. defaulted('onInit', noop),
  13702. defaulted('onDragStart', noop),
  13703. defaulted('onDragEnd', noop),
  13704. defaulted('snapToGrid', false),
  13705. defaulted('rounded', true),
  13706. option$3('snapStart'),
  13707. requiredOf('model', choose$1('mode', {
  13708. x: [
  13709. defaulted('minX', 0),
  13710. defaulted('maxX', 100),
  13711. customField('value', (spec) => Cell(spec.mode.minX)),
  13712. required$1('getInitialValue'),
  13713. output$1('manager', HorizontalModel)
  13714. ],
  13715. y: [
  13716. defaulted('minY', 0),
  13717. defaulted('maxY', 100),
  13718. customField('value', (spec) => Cell(spec.mode.minY)),
  13719. required$1('getInitialValue'),
  13720. output$1('manager', VerticalModel)
  13721. ],
  13722. xy: [
  13723. defaulted('minX', 0),
  13724. defaulted('maxX', 100),
  13725. defaulted('minY', 0),
  13726. defaulted('maxY', 100),
  13727. customField('value', (spec) => Cell({
  13728. x: spec.mode.minX,
  13729. y: spec.mode.minY
  13730. })),
  13731. required$1('getInitialValue'),
  13732. output$1('manager', TwoDModel)
  13733. ]
  13734. })),
  13735. field('sliderBehaviours', [Keying, Representing]),
  13736. customField('mouseIsDown', () => Cell(false))
  13737. ];
  13738. const sketch$1 = (detail, components, _spec, _externals) => {
  13739. const getThumb = (component) => getPartOrDie(component, detail, 'thumb');
  13740. const getSpectrum = (component) => getPartOrDie(component, detail, 'spectrum');
  13741. const getLeftEdge = (component) => getPart(component, detail, 'left-edge');
  13742. const getRightEdge = (component) => getPart(component, detail, 'right-edge');
  13743. const getTopEdge = (component) => getPart(component, detail, 'top-edge');
  13744. const getBottomEdge = (component) => getPart(component, detail, 'bottom-edge');
  13745. const modelDetail = detail.model;
  13746. const model = modelDetail.manager;
  13747. const refresh = (slider, thumb) => {
  13748. model.setPositionFromValue(slider, thumb, detail, {
  13749. getLeftEdge,
  13750. getRightEdge,
  13751. getTopEdge,
  13752. getBottomEdge,
  13753. getSpectrum
  13754. });
  13755. };
  13756. const setValue = (slider, newValue) => {
  13757. modelDetail.value.set(newValue);
  13758. const thumb = getThumb(slider);
  13759. refresh(slider, thumb);
  13760. };
  13761. const changeValue = (slider, newValue) => {
  13762. setValue(slider, newValue);
  13763. const thumb = getThumb(slider);
  13764. detail.onChange(slider, thumb, newValue);
  13765. return Optional.some(true);
  13766. };
  13767. const resetToMin = (slider) => {
  13768. model.setToMin(slider, detail);
  13769. };
  13770. const resetToMax = (slider) => {
  13771. model.setToMax(slider, detail);
  13772. };
  13773. const choose = (slider) => {
  13774. const fireOnChoose = () => {
  13775. getPart(slider, detail, 'thumb').each((thumb) => {
  13776. const value = modelDetail.value.get();
  13777. detail.onChoose(slider, thumb, value);
  13778. });
  13779. };
  13780. const wasDown = detail.mouseIsDown.get();
  13781. detail.mouseIsDown.set(false);
  13782. // We don't want this to fire if the mouse wasn't pressed down over anything other than the slider.
  13783. if (wasDown) {
  13784. fireOnChoose();
  13785. }
  13786. };
  13787. const onDragStart = (slider, simulatedEvent) => {
  13788. simulatedEvent.stop();
  13789. detail.mouseIsDown.set(true);
  13790. detail.onDragStart(slider, getThumb(slider));
  13791. };
  13792. const onDragEnd = (slider, simulatedEvent) => {
  13793. simulatedEvent.stop();
  13794. detail.onDragEnd(slider, getThumb(slider));
  13795. choose(slider);
  13796. };
  13797. const focusWidget = (component) => {
  13798. getPart(component, detail, 'spectrum').map(Keying.focusIn);
  13799. };
  13800. return {
  13801. uid: detail.uid,
  13802. dom: detail.dom,
  13803. components,
  13804. behaviours: augment(detail.sliderBehaviours, [
  13805. Keying.config({
  13806. mode: 'special',
  13807. focusIn: focusWidget
  13808. }),
  13809. Representing.config({
  13810. store: {
  13811. mode: 'manual',
  13812. getValue: (_) => {
  13813. return modelDetail.value.get();
  13814. },
  13815. setValue
  13816. }
  13817. }),
  13818. Receiving.config({
  13819. channels: {
  13820. [mouseReleased()]: {
  13821. onReceive: choose
  13822. }
  13823. }
  13824. })
  13825. ]),
  13826. events: derive$2([
  13827. run$1(sliderChangeEvent(), (slider, simulatedEvent) => {
  13828. changeValue(slider, simulatedEvent.event.value);
  13829. }),
  13830. runOnAttached((slider, _simulatedEvent) => {
  13831. // Set the initial value
  13832. const getInitial = modelDetail.getInitialValue();
  13833. modelDetail.value.set(getInitial);
  13834. const thumb = getThumb(slider);
  13835. refresh(slider, thumb);
  13836. const spectrum = getSpectrum(slider);
  13837. // Call onInit instead of onChange for the first value.
  13838. detail.onInit(slider, thumb, spectrum, modelDetail.value.get());
  13839. }),
  13840. run$1(touchstart(), onDragStart),
  13841. run$1(touchend(), onDragEnd),
  13842. run$1(mousedown(), (component, event) => {
  13843. focusWidget(component);
  13844. onDragStart(component, event);
  13845. }),
  13846. run$1(mouseup(), onDragEnd),
  13847. ]),
  13848. apis: {
  13849. resetToMin,
  13850. resetToMax,
  13851. setValue,
  13852. refresh
  13853. },
  13854. domModification: {
  13855. styles: {
  13856. position: 'relative'
  13857. }
  13858. }
  13859. };
  13860. };
  13861. const Slider = composite({
  13862. name: 'Slider',
  13863. configFields: SliderSchema,
  13864. partFields: SliderParts,
  13865. factory: sketch$1,
  13866. apis: {
  13867. setValue: (apis, slider, value) => {
  13868. apis.setValue(slider, value);
  13869. },
  13870. resetToMin: (apis, slider) => {
  13871. apis.resetToMin(slider);
  13872. },
  13873. resetToMax: (apis, slider) => {
  13874. apis.resetToMax(slider);
  13875. },
  13876. refresh: (apis, slider) => {
  13877. apis.refresh(slider);
  13878. }
  13879. }
  13880. });
  13881. const owner = 'container';
  13882. const schema$7 = [
  13883. field('slotBehaviours', [])
  13884. ];
  13885. const getPartName = (name) => '<alloy.field.' + name + '>';
  13886. const sketch = (sSpec) => {
  13887. // As parts.slot is called, record all of the parts that are registered
  13888. // as part of this SlotContainer.
  13889. const parts = (() => {
  13890. const record = [];
  13891. const slot = (name, config) => {
  13892. record.push(name);
  13893. return generateOne$1(owner, getPartName(name), config);
  13894. };
  13895. return {
  13896. slot,
  13897. record: constant$1(record)
  13898. };
  13899. })();
  13900. const spec = sSpec(parts);
  13901. const partNames = parts.record();
  13902. // Like a Form, a SlotContainer does not know its parts in advance. So the
  13903. // record lists the names of the parts to put in the schema.
  13904. // TODO: Find a nice way to remove dupe with Form
  13905. const fieldParts = map$2(partNames, (n) => required({ name: n, pname: getPartName(n) }));
  13906. return composite$1(owner, schema$7, fieldParts, make$3, spec);
  13907. };
  13908. const make$3 = (detail, components) => {
  13909. const getSlotNames = (_) => getAllPartNames(detail);
  13910. const getSlot = (container, key) => getPart(container, detail, key);
  13911. const onSlot = (f, def) => (container, key) => getPart(container, detail, key).map((slot) => f(slot, key)).getOr(def);
  13912. const onSlots = (f) => (container, keys) => {
  13913. each$1(keys, (key) => f(container, key));
  13914. };
  13915. const doShowing = (comp, _key) => get$g(comp.element, 'aria-hidden') !== 'true';
  13916. const doShow = (comp, key) => {
  13917. // NOTE: May need to restore old values.
  13918. if (!doShowing(comp)) {
  13919. const element = comp.element;
  13920. remove$6(element, 'display');
  13921. remove$8(element, 'aria-hidden');
  13922. emitWith(comp, slotVisibility(), { name: key, visible: true });
  13923. }
  13924. };
  13925. const doHide = (comp, key) => {
  13926. // NOTE: May need to save old values.
  13927. if (doShowing(comp)) {
  13928. const element = comp.element;
  13929. set$7(element, 'display', 'none');
  13930. set$9(element, 'aria-hidden', 'true');
  13931. emitWith(comp, slotVisibility(), { name: key, visible: false });
  13932. }
  13933. };
  13934. const isShowing = onSlot(doShowing, false);
  13935. const hideSlot = onSlot(doHide);
  13936. const hideSlots = onSlots(hideSlot);
  13937. const hideAllSlots = (container) => hideSlots(container, getSlotNames());
  13938. const showSlot = onSlot(doShow);
  13939. const apis = {
  13940. getSlotNames,
  13941. getSlot,
  13942. isShowing,
  13943. hideSlot,
  13944. hideAllSlots,
  13945. showSlot
  13946. };
  13947. return {
  13948. uid: detail.uid,
  13949. dom: detail.dom,
  13950. components,
  13951. behaviours: get$2(detail.slotBehaviours),
  13952. apis
  13953. };
  13954. };
  13955. // No type safety doing it this way. But removes dupe.
  13956. // We could probably use spread operator to help here.
  13957. const slotApis = map$1({
  13958. getSlotNames: (apis, c) => apis.getSlotNames(c),
  13959. getSlot: (apis, c, key) => apis.getSlot(c, key),
  13960. isShowing: (apis, c, key) => apis.isShowing(c, key),
  13961. hideSlot: (apis, c, key) => apis.hideSlot(c, key),
  13962. hideAllSlots: (apis, c) => apis.hideAllSlots(c),
  13963. showSlot: (apis, c, key) => apis.showSlot(c, key)
  13964. }, (value) => makeApi(value));
  13965. const SlotContainer = {
  13966. ...slotApis,
  13967. ...{ sketch }
  13968. };
  13969. const generate$1 = (xs, f) => {
  13970. const init = {
  13971. len: 0,
  13972. list: []
  13973. };
  13974. const r = foldl(xs, (b, a) => {
  13975. const value = f(a, b.len);
  13976. return value.fold(constant$1(b), (v) => ({
  13977. len: v.finish,
  13978. list: b.list.concat([v])
  13979. }));
  13980. }, init);
  13981. return r.list;
  13982. };
  13983. const output = (within, extra, withinWidth) => ({
  13984. within,
  13985. extra,
  13986. withinWidth
  13987. });
  13988. const apportion = (units, total, len) => {
  13989. const parray = generate$1(units, (unit, current) => {
  13990. const width = len(unit);
  13991. return Optional.some({
  13992. element: unit,
  13993. start: current,
  13994. finish: current + width,
  13995. width
  13996. });
  13997. });
  13998. const within = filter$2(parray, (unit) => unit.finish <= total);
  13999. const withinWidth = foldr(within, (acc, el) => acc + el.width, 0);
  14000. const extra = parray.slice(within.length);
  14001. return {
  14002. within,
  14003. extra,
  14004. withinWidth
  14005. };
  14006. };
  14007. const toUnit = (parray) => map$2(parray, (unit) => unit.element);
  14008. const fitLast = (within, extra, withinWidth) => {
  14009. const fits = toUnit(within.concat(extra));
  14010. return output(fits, [], withinWidth);
  14011. };
  14012. const overflow = (within, extra, overflower, withinWidth) => {
  14013. const fits = toUnit(within).concat([overflower]);
  14014. return output(fits, toUnit(extra), withinWidth);
  14015. };
  14016. const fitAll = (within, extra, withinWidth) => output(toUnit(within), [], withinWidth);
  14017. const tryFit = (total, units, len) => {
  14018. const divide = apportion(units, total, len);
  14019. return divide.extra.length === 0 ? Optional.some(divide) : Optional.none();
  14020. };
  14021. const partition = (total, units, len, overflower) => {
  14022. // Firstly, we try without the overflower.
  14023. const divide = tryFit(total, units, len).getOrThunk(() =>
  14024. // If that doesn't work, overflow
  14025. apportion(units, total - len(overflower), len));
  14026. const within = divide.within;
  14027. const extra = divide.extra;
  14028. const withinWidth = divide.withinWidth;
  14029. if (extra.length === 1 && extra[0].width <= len(overflower)) {
  14030. return fitLast(within, extra, withinWidth);
  14031. }
  14032. else if (extra.length >= 1) {
  14033. return overflow(within, extra, overflower, withinWidth);
  14034. }
  14035. else {
  14036. return fitAll(within, extra, withinWidth);
  14037. }
  14038. };
  14039. const setGroups = (toolbar, storedGroups) => {
  14040. const bGroups = map$2(storedGroups, (g) => premade(g));
  14041. Toolbar.setGroups(toolbar, bGroups);
  14042. };
  14043. const findFocusedComp = (comps) => findMap(comps, (comp) => search(comp.element).bind((focusedElm) => comp.getSystem().getByDom(focusedElm).toOptional()));
  14044. const refresh$2 = (toolbar, detail, setOverflow) => {
  14045. // Ensure we have toolbar groups to render
  14046. const builtGroups = detail.builtGroups.get();
  14047. if (builtGroups.length === 0) {
  14048. return;
  14049. }
  14050. const primary = getPartOrDie(toolbar, detail, 'primary');
  14051. const overflowGroup = Coupling.getCoupled(toolbar, 'overflowGroup');
  14052. // Set the primary toolbar to have visibility hidden;
  14053. set$7(primary.element, 'visibility', 'hidden');
  14054. const groups = builtGroups.concat([overflowGroup]);
  14055. // Store the current focus state
  14056. const focusedComp = findFocusedComp(groups);
  14057. // Clear the overflow toolbar
  14058. setOverflow([]);
  14059. // Put all the groups inside the primary toolbar
  14060. setGroups(primary, groups);
  14061. const availableWidth = get$c(primary.element);
  14062. const overflows = partition(availableWidth, detail.builtGroups.get(), (comp) => Math.ceil(comp.element.dom.getBoundingClientRect().width), overflowGroup);
  14063. if (overflows.extra.length === 0) {
  14064. // Not ideal. Breaking abstraction somewhat, though remove is better than insert
  14065. // Can just reset the toolbar groups also ... but may be a bit slower.
  14066. Replacing.remove(primary, overflowGroup);
  14067. setOverflow([]);
  14068. }
  14069. else {
  14070. setGroups(primary, overflows.within);
  14071. setOverflow(overflows.extra);
  14072. }
  14073. remove$6(primary.element, 'visibility');
  14074. reflow(primary.element);
  14075. // Restore the focus
  14076. focusedComp.each(Focusing.focus);
  14077. };
  14078. const schema$6 = constant$1([
  14079. field('splitToolbarBehaviours', [Coupling]),
  14080. customField('builtGroups', () => Cell([]))
  14081. ]);
  14082. const schema$5 = constant$1([
  14083. markers$1(['overflowToggledClass']),
  14084. optionFunction('getOverflowBounds'),
  14085. required$1('lazySink'),
  14086. customField('overflowGroups', () => Cell([])),
  14087. onHandler('onOpened'),
  14088. onHandler('onClosed')
  14089. ].concat(schema$6()));
  14090. const parts$5 = constant$1([
  14091. required({
  14092. factory: Toolbar,
  14093. schema: schema$d(),
  14094. name: 'primary'
  14095. }),
  14096. external$1({
  14097. schema: schema$d(),
  14098. name: 'overflow'
  14099. }),
  14100. external$1({
  14101. name: 'overflow-button'
  14102. }),
  14103. external$1({
  14104. name: 'overflow-group'
  14105. })
  14106. ]);
  14107. const schema$4 = constant$1([
  14108. required$1('items'),
  14109. markers$1(['itemSelector']),
  14110. field('tgroupBehaviours', [Keying])
  14111. ]);
  14112. const parts$4 = constant$1([
  14113. group({
  14114. name: 'items',
  14115. unit: 'item'
  14116. })
  14117. ]);
  14118. const factory$b = (detail, components, _spec, _externals) => ({
  14119. uid: detail.uid,
  14120. dom: detail.dom,
  14121. components,
  14122. behaviours: augment(detail.tgroupBehaviours, [
  14123. Keying.config({
  14124. mode: 'flow',
  14125. selector: detail.markers.itemSelector
  14126. })
  14127. ]),
  14128. domModification: {
  14129. attributes: {
  14130. role: 'toolbar'
  14131. }
  14132. }
  14133. });
  14134. const ToolbarGroup = composite({
  14135. name: 'ToolbarGroup',
  14136. configFields: schema$4(),
  14137. partFields: parts$4(),
  14138. factory: factory$b
  14139. });
  14140. const buildGroups = (comps) => map$2(comps, (g) => premade(g));
  14141. const refresh$1 = (toolbar, memFloatingToolbarButton, detail) => {
  14142. refresh$2(toolbar, detail, (overflowGroups) => {
  14143. detail.overflowGroups.set(overflowGroups);
  14144. memFloatingToolbarButton.getOpt(toolbar).each((floatingToolbarButton) => {
  14145. FloatingToolbarButton.setGroups(floatingToolbarButton, buildGroups(overflowGroups));
  14146. });
  14147. });
  14148. };
  14149. const factory$a = (detail, components, spec, externals) => {
  14150. const memFloatingToolbarButton = record(FloatingToolbarButton.sketch({
  14151. fetch: () => Future.nu((resolve) => {
  14152. resolve(buildGroups(detail.overflowGroups.get()));
  14153. }),
  14154. layouts: {
  14155. onLtr: () => [southwest$2, southeast$2],
  14156. onRtl: () => [southeast$2, southwest$2],
  14157. onBottomLtr: () => [northwest$2, northeast$2],
  14158. onBottomRtl: () => [northeast$2, northwest$2]
  14159. },
  14160. getBounds: spec.getOverflowBounds,
  14161. lazySink: detail.lazySink,
  14162. fireDismissalEventInstead: {},
  14163. markers: {
  14164. toggledClass: detail.markers.overflowToggledClass
  14165. },
  14166. parts: {
  14167. button: externals['overflow-button'](),
  14168. toolbar: externals.overflow()
  14169. },
  14170. onToggled: (comp, state) => detail[state ? 'onOpened' : 'onClosed'](comp)
  14171. }));
  14172. return {
  14173. uid: detail.uid,
  14174. dom: detail.dom,
  14175. components,
  14176. behaviours: augment(detail.splitToolbarBehaviours, [
  14177. Coupling.config({
  14178. others: {
  14179. overflowGroup: () => {
  14180. return ToolbarGroup.sketch({
  14181. ...externals['overflow-group'](),
  14182. items: [
  14183. memFloatingToolbarButton.asSpec()
  14184. ]
  14185. });
  14186. }
  14187. }
  14188. })
  14189. ]),
  14190. apis: {
  14191. setGroups: (toolbar, groups) => {
  14192. detail.builtGroups.set(map$2(groups, toolbar.getSystem().build));
  14193. refresh$1(toolbar, memFloatingToolbarButton, detail);
  14194. },
  14195. refresh: (toolbar) => refresh$1(toolbar, memFloatingToolbarButton, detail),
  14196. toggle: (toolbar) => {
  14197. memFloatingToolbarButton.getOpt(toolbar).each((floatingToolbarButton) => {
  14198. FloatingToolbarButton.toggle(floatingToolbarButton);
  14199. });
  14200. },
  14201. toggleWithoutFocusing: (toolbar) => {
  14202. memFloatingToolbarButton.getOpt(toolbar).each(FloatingToolbarButton.toggleWithoutFocusing);
  14203. },
  14204. isOpen: (toolbar) => memFloatingToolbarButton.getOpt(toolbar).map(FloatingToolbarButton.isOpen).getOr(false),
  14205. reposition: (toolbar) => {
  14206. memFloatingToolbarButton.getOpt(toolbar).each((floatingToolbarButton) => {
  14207. FloatingToolbarButton.reposition(floatingToolbarButton);
  14208. });
  14209. },
  14210. getOverflow: (toolbar) => memFloatingToolbarButton.getOpt(toolbar).bind(FloatingToolbarButton.getToolbar)
  14211. },
  14212. domModification: {
  14213. attributes: { role: 'group' }
  14214. }
  14215. };
  14216. };
  14217. const SplitFloatingToolbar = composite({
  14218. name: 'SplitFloatingToolbar',
  14219. configFields: schema$5(),
  14220. partFields: parts$5(),
  14221. factory: factory$a,
  14222. apis: {
  14223. setGroups: (apis, toolbar, groups) => {
  14224. apis.setGroups(toolbar, groups);
  14225. },
  14226. refresh: (apis, toolbar) => {
  14227. apis.refresh(toolbar);
  14228. },
  14229. reposition: (apis, toolbar) => {
  14230. apis.reposition(toolbar);
  14231. },
  14232. toggle: (apis, toolbar) => {
  14233. apis.toggle(toolbar);
  14234. },
  14235. toggleWithoutFocusing: (apis, toolbar) => {
  14236. apis.toggle(toolbar);
  14237. },
  14238. isOpen: (apis, toolbar) => apis.isOpen(toolbar),
  14239. getOverflow: (apis, toolbar) => apis.getOverflow(toolbar)
  14240. }
  14241. });
  14242. const schema$3 = constant$1([
  14243. markers$1(['closedClass', 'openClass', 'shrinkingClass', 'growingClass', 'overflowToggledClass']),
  14244. onHandler('onOpened'),
  14245. onHandler('onClosed')
  14246. ].concat(schema$6()));
  14247. const parts$3 = constant$1([
  14248. required({
  14249. factory: Toolbar,
  14250. schema: schema$d(),
  14251. name: 'primary'
  14252. }),
  14253. required({
  14254. factory: Toolbar,
  14255. schema: schema$d(),
  14256. name: 'overflow',
  14257. overrides: (detail) => {
  14258. return {
  14259. toolbarBehaviours: derive$1([
  14260. Sliding.config({
  14261. dimension: {
  14262. property: 'height'
  14263. },
  14264. closedClass: detail.markers.closedClass,
  14265. openClass: detail.markers.openClass,
  14266. shrinkingClass: detail.markers.shrinkingClass,
  14267. growingClass: detail.markers.growingClass,
  14268. onShrunk: (comp) => {
  14269. getPart(comp, detail, 'overflow-button').each((button) => {
  14270. Toggling.off(button);
  14271. });
  14272. detail.onClosed(comp);
  14273. },
  14274. onGrown: (comp) => {
  14275. detail.onOpened(comp);
  14276. },
  14277. onStartGrow: (comp) => {
  14278. getPart(comp, detail, 'overflow-button').each(Toggling.on);
  14279. }
  14280. }),
  14281. Keying.config({
  14282. mode: 'acyclic',
  14283. onEscape: (comp) => {
  14284. getPart(comp, detail, 'overflow-button').each(Focusing.focus);
  14285. return Optional.some(true);
  14286. }
  14287. })
  14288. ])
  14289. };
  14290. }
  14291. }),
  14292. external$1({
  14293. name: 'overflow-button',
  14294. overrides: (detail) => ({
  14295. buttonBehaviours: derive$1([
  14296. Toggling.config({
  14297. toggleClass: detail.markers.overflowToggledClass,
  14298. aria: {
  14299. mode: 'expanded'
  14300. },
  14301. toggleOnExecute: false
  14302. })
  14303. ])
  14304. })
  14305. }),
  14306. external$1({
  14307. name: 'overflow-group'
  14308. })
  14309. ]);
  14310. const isOpen = (toolbar, detail) => getPart(toolbar, detail, 'overflow').map(Sliding.hasGrown).getOr(false);
  14311. const toggleToolbar = (toolbar, detail, skipFocus) => {
  14312. // Make sure that the toolbar needs to toggled by checking for overflow button presence
  14313. getPart(toolbar, detail, 'overflow-button')
  14314. .each((oveflowButton) => {
  14315. getPart(toolbar, detail, 'overflow').each((overf) => {
  14316. refresh(toolbar, detail);
  14317. if (Sliding.hasShrunk(overf)) {
  14318. const fn = detail.onOpened;
  14319. detail.onOpened = (comp) => {
  14320. if (!skipFocus) {
  14321. Keying.focusIn(overf);
  14322. }
  14323. fn(comp);
  14324. detail.onOpened = fn;
  14325. };
  14326. }
  14327. else {
  14328. const fn = detail.onClosed;
  14329. detail.onClosed = (comp) => {
  14330. if (!skipFocus) {
  14331. Focusing.focus(oveflowButton);
  14332. }
  14333. fn(comp);
  14334. detail.onClosed = fn;
  14335. };
  14336. }
  14337. Sliding.toggleGrow(overf);
  14338. });
  14339. });
  14340. };
  14341. const refresh = (toolbar, detail) => {
  14342. getPart(toolbar, detail, 'overflow').each((overflow) => {
  14343. refresh$2(toolbar, detail, (groups) => {
  14344. const builtGroups = map$2(groups, (g) => premade(g));
  14345. Toolbar.setGroups(overflow, builtGroups);
  14346. });
  14347. getPart(toolbar, detail, 'overflow-button').each((button) => {
  14348. if (Sliding.hasGrown(overflow)) {
  14349. Toggling.on(button);
  14350. }
  14351. });
  14352. Sliding.refresh(overflow);
  14353. });
  14354. };
  14355. const factory$9 = (detail, components, spec, externals) => {
  14356. const toolbarToggleEvent = 'alloy.toolbar.toggle';
  14357. const doSetGroups = (toolbar, groups) => {
  14358. const built = map$2(groups, toolbar.getSystem().build);
  14359. detail.builtGroups.set(built);
  14360. };
  14361. return {
  14362. uid: detail.uid,
  14363. dom: detail.dom,
  14364. components,
  14365. behaviours: augment(detail.splitToolbarBehaviours, [
  14366. Coupling.config({
  14367. others: {
  14368. overflowGroup: (toolbar) => {
  14369. return ToolbarGroup.sketch({
  14370. ...externals['overflow-group'](),
  14371. items: [
  14372. Button.sketch({
  14373. ...externals['overflow-button'](),
  14374. action: (_button) => {
  14375. emit(toolbar, toolbarToggleEvent);
  14376. }
  14377. })
  14378. ]
  14379. });
  14380. }
  14381. }
  14382. }),
  14383. config('toolbar-toggle-events', [
  14384. run$1(toolbarToggleEvent, (toolbar) => {
  14385. toggleToolbar(toolbar, detail, false);
  14386. })
  14387. ])
  14388. ]),
  14389. apis: {
  14390. setGroups: (toolbar, groups) => {
  14391. doSetGroups(toolbar, groups);
  14392. refresh(toolbar, detail);
  14393. },
  14394. refresh: (toolbar) => refresh(toolbar, detail),
  14395. toggle: (toolbar) => {
  14396. toggleToolbar(toolbar, detail, false);
  14397. },
  14398. toggleWithoutFocusing: (toolbar) => {
  14399. toggleToolbar(toolbar, detail, true);
  14400. },
  14401. isOpen: (toolbar) => isOpen(toolbar, detail)
  14402. },
  14403. domModification: {
  14404. attributes: { role: 'group' }
  14405. }
  14406. };
  14407. };
  14408. const SplitSlidingToolbar = composite({
  14409. name: 'SplitSlidingToolbar',
  14410. configFields: schema$3(),
  14411. partFields: parts$3(),
  14412. factory: factory$9,
  14413. apis: {
  14414. setGroups: (apis, toolbar, groups) => {
  14415. apis.setGroups(toolbar, groups);
  14416. },
  14417. refresh: (apis, toolbar) => {
  14418. apis.refresh(toolbar);
  14419. },
  14420. toggle: (apis, toolbar) => {
  14421. apis.toggle(toolbar);
  14422. },
  14423. isOpen: (apis, toolbar) => apis.isOpen(toolbar)
  14424. }
  14425. });
  14426. const factory$8 = (detail, _spec) => ({
  14427. uid: detail.uid,
  14428. dom: detail.dom,
  14429. components: detail.components,
  14430. events: events(detail.action),
  14431. behaviours: augment(detail.tabButtonBehaviours, [
  14432. Focusing.config({}),
  14433. Keying.config({
  14434. mode: 'execution',
  14435. useSpace: true,
  14436. useEnter: true
  14437. }),
  14438. Representing.config({
  14439. store: {
  14440. mode: 'memory',
  14441. initialValue: detail.value
  14442. }
  14443. })
  14444. ]),
  14445. domModification: detail.domModification
  14446. });
  14447. const TabButton = single({
  14448. name: 'TabButton',
  14449. configFields: [
  14450. defaulted('uid', undefined),
  14451. required$1('value'),
  14452. field$1('dom', 'dom', mergeWithThunk(() => ({
  14453. attributes: {
  14454. 'role': 'tab',
  14455. // NOTE: This is used in TabSection to connect "labelledby"
  14456. 'id': generate$6('aria'),
  14457. 'aria-selected': 'false'
  14458. }
  14459. })), anyValue()),
  14460. option$3('action'),
  14461. defaulted('domModification', {}),
  14462. field('tabButtonBehaviours', [Focusing, Keying, Representing]),
  14463. required$1('view')
  14464. ],
  14465. factory: factory$8
  14466. });
  14467. const schema$2 = constant$1([
  14468. required$1('tabs'),
  14469. required$1('dom'),
  14470. defaulted('clickToDismiss', false),
  14471. field('tabbarBehaviours', [Highlighting, Keying]),
  14472. markers$1(['tabClass', 'selectedClass'])
  14473. ]);
  14474. const tabsPart = group({
  14475. factory: TabButton,
  14476. name: 'tabs',
  14477. unit: 'tab',
  14478. overrides: (barDetail) => {
  14479. const dismissTab$1 = (tabbar, button) => {
  14480. Highlighting.dehighlight(tabbar, button);
  14481. emitWith(tabbar, dismissTab(), {
  14482. tabbar,
  14483. button
  14484. });
  14485. };
  14486. const changeTab$1 = (tabbar, button) => {
  14487. Highlighting.highlight(tabbar, button);
  14488. emitWith(tabbar, changeTab(), {
  14489. tabbar,
  14490. button
  14491. });
  14492. };
  14493. return {
  14494. action: (button) => {
  14495. const tabbar = button.getSystem().getByUid(barDetail.uid).getOrDie();
  14496. const activeButton = Highlighting.isHighlighted(tabbar, button);
  14497. const response = (() => {
  14498. if (activeButton && barDetail.clickToDismiss) {
  14499. return dismissTab$1;
  14500. }
  14501. else if (!activeButton) {
  14502. return changeTab$1;
  14503. }
  14504. else {
  14505. return noop;
  14506. }
  14507. })();
  14508. response(tabbar, button);
  14509. },
  14510. domModification: {
  14511. classes: [barDetail.markers.tabClass]
  14512. }
  14513. };
  14514. }
  14515. });
  14516. const parts$2 = constant$1([
  14517. tabsPart
  14518. ]);
  14519. const factory$7 = (detail, components, _spec, _externals) => ({
  14520. 'uid': detail.uid,
  14521. 'dom': detail.dom,
  14522. components,
  14523. 'debug.sketcher': 'Tabbar',
  14524. 'domModification': {
  14525. attributes: {
  14526. role: 'tablist'
  14527. }
  14528. },
  14529. 'behaviours': augment(detail.tabbarBehaviours, [
  14530. Highlighting.config({
  14531. highlightClass: detail.markers.selectedClass,
  14532. itemClass: detail.markers.tabClass,
  14533. // https://www.w3.org/TR/2010/WD-wai-aria-practices-20100916/#tabpanel
  14534. // Consider a more seam-less way of combining highlighting and toggling
  14535. onHighlight: (tabbar, tab) => {
  14536. // TODO: Integrate highlighting and toggling in a nice way
  14537. set$9(tab.element, 'aria-selected', 'true');
  14538. },
  14539. onDehighlight: (tabbar, tab) => {
  14540. set$9(tab.element, 'aria-selected', 'false');
  14541. }
  14542. }),
  14543. Keying.config({
  14544. mode: 'flow',
  14545. getInitial: (tabbar) => {
  14546. // Restore focus to the previously highlighted tab.
  14547. return Highlighting.getHighlighted(tabbar).map((tab) => tab.element);
  14548. },
  14549. selector: '.' + detail.markers.tabClass,
  14550. executeOnMove: true
  14551. })
  14552. ])
  14553. });
  14554. const Tabbar = composite({
  14555. name: 'Tabbar',
  14556. configFields: schema$2(),
  14557. partFields: parts$2(),
  14558. factory: factory$7
  14559. });
  14560. const factory$6 = (detail, _spec) => ({
  14561. uid: detail.uid,
  14562. dom: detail.dom,
  14563. behaviours: augment(detail.tabviewBehaviours, [
  14564. Replacing.config({})
  14565. ]),
  14566. domModification: {
  14567. attributes: { role: 'tabpanel' }
  14568. }
  14569. });
  14570. const Tabview = single({
  14571. name: 'Tabview',
  14572. configFields: [
  14573. field('tabviewBehaviours', [Replacing])
  14574. ],
  14575. factory: factory$6
  14576. });
  14577. const schema$1 = constant$1([
  14578. defaulted('selectFirst', true),
  14579. onHandler('onChangeTab'),
  14580. onHandler('onDismissTab'),
  14581. defaulted('tabs', []),
  14582. field('tabSectionBehaviours', [])
  14583. ]);
  14584. const barPart = required({
  14585. factory: Tabbar,
  14586. schema: [
  14587. required$1('dom'),
  14588. requiredObjOf('markers', [
  14589. required$1('tabClass'),
  14590. required$1('selectedClass')
  14591. ])
  14592. ],
  14593. name: 'tabbar',
  14594. defaults: (detail) => {
  14595. return {
  14596. tabs: detail.tabs
  14597. };
  14598. }
  14599. });
  14600. const viewPart = required({
  14601. factory: Tabview,
  14602. name: 'tabview'
  14603. });
  14604. const parts$1 = constant$1([
  14605. barPart,
  14606. viewPart
  14607. ]);
  14608. const factory$5 = (detail, components, _spec, _externals) => {
  14609. const changeTab$1 = (button) => {
  14610. const tabValue = Representing.getValue(button);
  14611. getPart(button, detail, 'tabview').each((tabview) => {
  14612. const tabWithValue = find$5(detail.tabs, (t) => t.value === tabValue);
  14613. tabWithValue.each((tabData) => {
  14614. const panel = tabData.view();
  14615. // Update the tabview to refer to the current tab.
  14616. getOpt(button.element, 'id').each((id) => {
  14617. set$9(tabview.element, 'aria-labelledby', id);
  14618. });
  14619. Replacing.set(tabview, panel);
  14620. detail.onChangeTab(tabview, button, panel);
  14621. });
  14622. });
  14623. };
  14624. const changeTabBy = (section, byPred) => {
  14625. getPart(section, detail, 'tabbar').each((tabbar) => {
  14626. byPred(tabbar).each(emitExecute);
  14627. });
  14628. };
  14629. return {
  14630. uid: detail.uid,
  14631. dom: detail.dom,
  14632. components,
  14633. behaviours: get$2(detail.tabSectionBehaviours),
  14634. events: derive$2(flatten([
  14635. detail.selectFirst ? [
  14636. runOnAttached((section, _simulatedEvent) => {
  14637. changeTabBy(section, Highlighting.getFirst);
  14638. })
  14639. ] : [],
  14640. [
  14641. run$1(changeTab(), (section, simulatedEvent) => {
  14642. const button = simulatedEvent.event.button;
  14643. changeTab$1(button);
  14644. }),
  14645. run$1(dismissTab(), (section, simulatedEvent) => {
  14646. const button = simulatedEvent.event.button;
  14647. detail.onDismissTab(section, button);
  14648. })
  14649. ]
  14650. ])),
  14651. apis: {
  14652. getViewItems: (section) => {
  14653. return getPart(section, detail, 'tabview').map((tabview) => Replacing.contents(tabview)).getOr([]);
  14654. },
  14655. // How should "clickToDismiss" interact with this? At the moment, it will never dismiss
  14656. showTab: (section, tabKey) => {
  14657. // We only change the tab if it isn't currently active because that takes
  14658. // the whole "dismiss" issue out of the equation.
  14659. const getTabIfNotActive = (tabbar) => {
  14660. const candidates = Highlighting.getCandidates(tabbar);
  14661. const optTab = find$5(candidates, (c) => Representing.getValue(c) === tabKey);
  14662. return optTab.filter((tab) => !Highlighting.isHighlighted(tabbar, tab));
  14663. };
  14664. changeTabBy(section, getTabIfNotActive);
  14665. }
  14666. }
  14667. };
  14668. };
  14669. const TabSection = composite({
  14670. name: 'TabSection',
  14671. configFields: schema$1(),
  14672. partFields: parts$1(),
  14673. factory: factory$5,
  14674. apis: {
  14675. getViewItems: (apis, component) => apis.getViewItems(component),
  14676. showTab: (apis, component, tabKey) => {
  14677. apis.showTab(component, tabKey);
  14678. }
  14679. }
  14680. });
  14681. // When showing a value in an input field, which part of the item do we use?
  14682. const setValueFromItem = (model, input, item) => {
  14683. const itemData = Representing.getValue(item);
  14684. Representing.setValue(input, itemData);
  14685. setCursorAtEnd(input);
  14686. };
  14687. const setSelectionOn = (input, f) => {
  14688. const el = input.element;
  14689. const value = get$5(el);
  14690. const node = el.dom;
  14691. // Only do for valid input types.
  14692. if (get$g(el, 'type') !== 'number') {
  14693. f(node, value);
  14694. }
  14695. };
  14696. const setCursorAtEnd = (input) => {
  14697. setSelectionOn(input, (node, value) => node.setSelectionRange(value.length, value.length));
  14698. };
  14699. const setSelectionToEnd = (input, startOffset) => {
  14700. setSelectionOn(input, (node, value) => node.setSelectionRange(startOffset, value.length));
  14701. };
  14702. const attemptSelectOver = (model, input, item) => {
  14703. if (!model.selectsOver) {
  14704. return Optional.none();
  14705. }
  14706. else {
  14707. const currentValue = Representing.getValue(input);
  14708. const inputDisplay = model.getDisplayText(currentValue);
  14709. const itemValue = Representing.getValue(item);
  14710. const itemDisplay = model.getDisplayText(itemValue);
  14711. return itemDisplay.indexOf(inputDisplay) === 0 ?
  14712. Optional.some(() => {
  14713. setValueFromItem(model, input, item);
  14714. setSelectionToEnd(input, inputDisplay.length);
  14715. })
  14716. : Optional.none();
  14717. }
  14718. };
  14719. const itemExecute = constant$1('alloy.typeahead.itemexecute');
  14720. // TODO: Fix this.
  14721. const make$2 = (detail, components, spec, externals) => {
  14722. const navigateList = (comp, simulatedEvent, highlighter) => {
  14723. /*
  14724. * If we have an open Sandbox with an active menu,
  14725. * but no highlighted item, then highlight the menu
  14726. *
  14727. * If we have an open Sandbox with an active menu,
  14728. * and there is a highlighted item, simulated a keydown
  14729. * on the menu
  14730. *
  14731. * If we have a closed sandbox, open the sandbox
  14732. *
  14733. * Regardless, this is a user initiated action. End previewing.
  14734. */
  14735. detail.previewing.set(false);
  14736. const sandbox = Coupling.getCoupled(comp, 'sandbox');
  14737. if (Sandboxing.isOpen(sandbox)) {
  14738. Composing.getCurrent(sandbox).each((menu) => {
  14739. Highlighting.getHighlighted(menu).fold(() => {
  14740. highlighter(menu);
  14741. }, () => {
  14742. dispatchEvent(sandbox, menu.element, 'keydown', simulatedEvent);
  14743. });
  14744. });
  14745. }
  14746. else {
  14747. const onOpenSync = (sandbox) => {
  14748. Composing.getCurrent(sandbox).each(highlighter);
  14749. };
  14750. open(detail, mapFetch(comp), comp, sandbox, externals, onOpenSync, HighlightOnOpen.HighlightMenuAndItem).get(noop);
  14751. }
  14752. };
  14753. // Due to the fact that typeahead probably need to separate value from text, they can't reuse
  14754. // (easily) the same representing logic as input fields.
  14755. const focusBehaviours$1 = focusBehaviours(detail);
  14756. const mapFetch = (comp) => (tdata) => tdata.map((data) => {
  14757. const menus = values(data.menus);
  14758. const items = bind$3(menus, (menu) => filter$2(menu.items, (item) => item.type === 'item'));
  14759. const repState = Representing.getState(comp);
  14760. repState.update(map$2(items, (item) => item.data));
  14761. return data;
  14762. });
  14763. // This function (getActiveMenu) is intended to make it easier to read what is happening
  14764. // without having to decipher the Highlighting and Composing calls.
  14765. const getActiveMenu = (sandboxComp) => Composing.getCurrent(sandboxComp);
  14766. const typeaheadCustomEvents = 'typeaheadevents';
  14767. const behaviours = [
  14768. Focusing.config({}),
  14769. Representing.config({
  14770. onSetValue: detail.onSetValue,
  14771. store: {
  14772. mode: 'dataset',
  14773. getDataKey: (comp) => get$5(comp.element),
  14774. // This really needs to be configurable
  14775. getFallbackEntry: (itemString) => ({
  14776. value: itemString,
  14777. meta: {}
  14778. }),
  14779. setValue: (comp, data) => {
  14780. set$4(comp.element, detail.model.getDisplayText(data));
  14781. },
  14782. ...detail.initialData.map((d) => wrap('initialValue', d)).getOr({})
  14783. }
  14784. }),
  14785. Streaming.config({
  14786. stream: {
  14787. mode: 'throttle',
  14788. delay: detail.responseTime,
  14789. stopEvent: false
  14790. },
  14791. onStream: (component, _simulatedEvent) => {
  14792. const sandbox = Coupling.getCoupled(component, 'sandbox');
  14793. const focusInInput = Focusing.isFocused(component);
  14794. // You don't want it to change when something else has triggered the change.
  14795. if (focusInInput) {
  14796. if (get$5(component.element).length >= detail.minChars) {
  14797. // Get the value of the previously active (selected/highlighted) item. We
  14798. // are going to try to preserve this.
  14799. const previousValue = getActiveMenu(sandbox).bind((activeMenu) => Highlighting.getHighlighted(activeMenu).map(Representing.getValue));
  14800. // Turning previewing ON here every keystroke is unnecessary, but relies
  14801. // on the fact that it will be turned off if required by highlighting events.
  14802. // So even if previewing was supposed to be off, turning it on here is
  14803. // just temporary, because the onOpenSync below will trigger a highlight
  14804. // if there was meant to be one, which will turn it off if required.
  14805. detail.previewing.set(true);
  14806. const onOpenSync = (_sandbox) => {
  14807. // This getActiveMenu relies on a menu being highlighted / active
  14808. getActiveMenu(sandbox).each((activeMenu) => {
  14809. // The folds can make this hard to follow, but the basic gist of it is
  14810. // that we want to see if we need to highlight one of the items in the
  14811. // menu that we just opened. If we do highlight an item, then that
  14812. // highlighting action will clear previewing (handled by the TieredMenu
  14813. // part configuration for onHighlight). Note: that onOpenSync runs
  14814. // *after* the highlightOnOpen setting.
  14815. //
  14816. // 1. If in "selectsOver" mode and we don't have a previous item,
  14817. // then highlight the first one. This one will be used as the basis
  14818. // for the "selectsOver" text selection. The act of highlighting the
  14819. // first item will take us out of previewing mode. If the "selectsOver"
  14820. // operation fails, it should clear the highlight, and restore previewing
  14821. // 2. If not in "selectsOver" mode, and we don't have a previous item,
  14822. // then we don't highlight anything. This will keep us in previewing
  14823. // mode until the menu is interacted with (hover, navigation etc.)
  14824. // 3. If we have a previous item, then try and rehighlight it. But if
  14825. // we can't, the just highlight the first. Either action will take us
  14826. // out of previewing mode.
  14827. previousValue.fold(() => {
  14828. // We are using "selectOver", so we need *something* to highlight
  14829. if (detail.model.selectsOver) {
  14830. Highlighting.highlightFirst(activeMenu);
  14831. }
  14832. // We aren't using "selectOver", so don't highlight anything
  14833. // to preserve our "previewing" mode.
  14834. }, (pv) => {
  14835. // We have a previous item, so if we can't rehighlight it, then
  14836. // we'll change to the first item. We want to keep some selection.
  14837. Highlighting.highlightBy(activeMenu, (item) => {
  14838. const itemData = Representing.getValue(item);
  14839. return itemData.value === pv.value;
  14840. });
  14841. // Highlight first if could not find it?
  14842. Highlighting.getHighlighted(activeMenu).orThunk(() => {
  14843. Highlighting.highlightFirst(activeMenu);
  14844. return Optional.none();
  14845. });
  14846. });
  14847. });
  14848. };
  14849. open(detail, mapFetch(component), component, sandbox, externals, onOpenSync,
  14850. // The onOpenSync takes care of what should be given the highlights, but
  14851. // we want to highlight just the menu so that the onOpenSync can find the
  14852. // activeMenu.
  14853. HighlightOnOpen.HighlightJustMenu).get(noop);
  14854. }
  14855. }
  14856. },
  14857. cancelEvent: typeaheadCancel()
  14858. }),
  14859. Keying.config({
  14860. mode: 'special',
  14861. onDown: (comp, simulatedEvent) => {
  14862. // The navigation here will stop the "previewing" mode, because
  14863. // now the menu will get focus (fake focus, but focus nevertheless)
  14864. navigateList(comp, simulatedEvent, Highlighting.highlightFirst);
  14865. return Optional.some(true);
  14866. },
  14867. onEscape: (comp) => {
  14868. // Escape only has handling if the sandbox is visible. It has no meaning
  14869. // to the input itself.
  14870. const sandbox = Coupling.getCoupled(comp, 'sandbox');
  14871. if (Sandboxing.isOpen(sandbox)) {
  14872. Sandboxing.close(sandbox);
  14873. return Optional.some(true);
  14874. }
  14875. return Optional.none();
  14876. },
  14877. onUp: (comp, simulatedEvent) => {
  14878. // The navigation here will stop the "previewing" mode, because
  14879. // now the menu will get focus (fake focus, but focus nevertheless)
  14880. navigateList(comp, simulatedEvent, Highlighting.highlightLast);
  14881. return Optional.some(true);
  14882. },
  14883. onEnter: (comp) => {
  14884. const sandbox = Coupling.getCoupled(comp, 'sandbox');
  14885. const sandboxIsOpen = Sandboxing.isOpen(sandbox);
  14886. // 'Previewing' means that items are shown but none has been actively selected by the user.
  14887. // When previewing, all keyboard input should still be processed by the
  14888. // input itself, not the menu. The menu is not considered to have focus.
  14889. // 'Previewing' is turned on by (streaming) keystrokes, and turned off by
  14890. // successful interaction with the menu (navigation, highlighting, hovering).
  14891. // So if we aren't previewing, and the dropdown sandbox is open, then
  14892. // we process <enter> keys on the items in the menu. All this will do
  14893. // is trigger an itemExecute event. The typeahead events (in the spec below)
  14894. // are responsible for doing something with that event.
  14895. if (sandboxIsOpen && !detail.previewing.get()) {
  14896. return getActiveMenu(sandbox).bind((activeMenu) => Highlighting.getHighlighted(activeMenu)).map((item) => {
  14897. // And item was selected, so trigger execute and consider the
  14898. // <enter> key 'handled'
  14899. emitWith(comp, itemExecute(), { item });
  14900. return true;
  14901. });
  14902. }
  14903. else {
  14904. // We are either previewing, or the sandbox isn't open, so we should
  14905. // process the <enter> key inside the input itself. This should cancel
  14906. // any attempt to fetch data (the typeaheadCancel), and trigger the execute.
  14907. // We also close the sandbox if it's open.
  14908. const currentValue = Representing.getValue(comp);
  14909. emit(comp, typeaheadCancel());
  14910. detail.onExecute(sandbox, comp, currentValue);
  14911. // If we're open and previewing, close the sandbox after firing execute.
  14912. if (sandboxIsOpen) {
  14913. Sandboxing.close(sandbox);
  14914. }
  14915. return Optional.some(true);
  14916. }
  14917. }
  14918. }),
  14919. Toggling.config({
  14920. toggleClass: detail.markers.openClass,
  14921. aria: {
  14922. mode: 'expanded'
  14923. }
  14924. }),
  14925. Coupling.config({
  14926. others: {
  14927. sandbox: (hotspot) => {
  14928. return makeSandbox$1(detail, hotspot, {
  14929. onOpen: () => Toggling.on(hotspot),
  14930. onClose: () => {
  14931. // TINY-9280: Remove aria-activedescendant that is set when menu item is highlighted
  14932. detail.lazyTypeaheadComp.get().each((input) => remove$8(input.element, 'aria-activedescendant'));
  14933. Toggling.off(hotspot);
  14934. }
  14935. });
  14936. }
  14937. }
  14938. }),
  14939. config(typeaheadCustomEvents, [
  14940. runOnAttached((typeaheadComp) => {
  14941. // Set up the reference to the typeahead, so that it can retrieved from
  14942. // the tiered menu part, even if the tieredmenu is in a different
  14943. // system / alloy root / mothership.
  14944. detail.lazyTypeaheadComp.set(Optional.some(typeaheadComp));
  14945. }),
  14946. runOnDetached((_typeaheadComp) => {
  14947. detail.lazyTypeaheadComp.set(Optional.none());
  14948. }),
  14949. runOnExecute$1((comp) => {
  14950. const onOpenSync = noop;
  14951. togglePopup(detail, mapFetch(comp), comp, externals, onOpenSync, HighlightOnOpen.HighlightMenuAndItem).get(noop);
  14952. }),
  14953. run$1(itemExecute(), (comp, se) => {
  14954. const sandbox = Coupling.getCoupled(comp, 'sandbox');
  14955. // Copy the value from the executed item into the input, because it was "chosen"
  14956. setValueFromItem(detail.model, comp, se.event.item);
  14957. emit(comp, typeaheadCancel());
  14958. detail.onItemExecute(comp, sandbox, se.event.item, Representing.getValue(comp));
  14959. Sandboxing.close(sandbox);
  14960. setCursorAtEnd(comp);
  14961. })
  14962. ].concat(detail.dismissOnBlur ? [
  14963. run$1(postBlur(), (typeahead) => {
  14964. const sandbox = Coupling.getCoupled(typeahead, 'sandbox');
  14965. // Only close the sandbox if the focus isn't inside it!
  14966. if (search(sandbox.element).isNone()) {
  14967. Sandboxing.close(sandbox);
  14968. }
  14969. })
  14970. ] : []))
  14971. ];
  14972. // The order specified here isn't important. Alloy just requires a
  14973. // deterministic order for the configured behaviours.
  14974. const eventOrder = {
  14975. [detachedFromDom()]: [
  14976. Representing.name(),
  14977. Streaming.name(),
  14978. typeaheadCustomEvents
  14979. ],
  14980. ...detail.eventOrder,
  14981. };
  14982. return {
  14983. uid: detail.uid,
  14984. dom: dom$1(deepMerge(detail, {
  14985. // TODO: Add aria-activedescendant attribute
  14986. inputAttributes: {
  14987. 'role': 'combobox',
  14988. 'aria-autocomplete': 'list',
  14989. 'aria-haspopup': 'true'
  14990. }
  14991. })),
  14992. behaviours: {
  14993. ...focusBehaviours$1,
  14994. ...augment(detail.typeaheadBehaviours, behaviours)
  14995. },
  14996. eventOrder
  14997. };
  14998. };
  14999. const schema = constant$1([
  15000. option$3('lazySink'),
  15001. required$1('fetch'),
  15002. defaulted('minChars', 5),
  15003. defaulted('responseTime', 1000),
  15004. onHandler('onOpen'),
  15005. // TODO: Remove dupe with Dropdown
  15006. defaulted('getHotspot', Optional.some),
  15007. defaulted('getAnchorOverrides', constant$1({})),
  15008. defaulted('layouts', Optional.none()),
  15009. defaulted('eventOrder', {}),
  15010. // Information about what these model settings do can be found in TypeaheadTypes
  15011. defaultedObjOf('model', {}, [
  15012. defaulted('getDisplayText', (itemData) => itemData.meta !== undefined && itemData.meta.text !== undefined ? itemData.meta.text : itemData.value),
  15013. defaulted('selectsOver', true),
  15014. defaulted('populateFromBrowse', true)
  15015. ]),
  15016. onHandler('onSetValue'),
  15017. onKeyboardHandler('onExecute'),
  15018. onHandler('onItemExecute'),
  15019. defaulted('inputClasses', []),
  15020. defaulted('inputAttributes', {}),
  15021. defaulted('inputStyles', {}),
  15022. defaulted('matchWidth', true),
  15023. defaulted('useMinWidth', false),
  15024. defaulted('dismissOnBlur', true),
  15025. markers$1(['openClass']),
  15026. option$3('initialData'),
  15027. option$3('listRole'),
  15028. field('typeaheadBehaviours', [
  15029. Focusing, Representing, Streaming, Keying, Toggling, Coupling
  15030. ]),
  15031. customField('lazyTypeaheadComp', () => Cell(Optional.none)),
  15032. customField('previewing', () => Cell(true))
  15033. ].concat(schema$9()).concat(sandboxFields()));
  15034. const parts = constant$1([
  15035. external$1({
  15036. schema: [
  15037. tieredMenuMarkers()
  15038. ],
  15039. name: 'menu',
  15040. overrides: (detail) => {
  15041. return {
  15042. fakeFocus: true,
  15043. onHighlightItem: (_tmenu, menu, item) => {
  15044. if (!detail.previewing.get()) {
  15045. // We need to use this type of reference, rather than just looking
  15046. // it up from the system by uid, because the input and the tieredmenu
  15047. // might be in different systems.
  15048. detail.lazyTypeaheadComp.get().each((input) => {
  15049. if (detail.model.populateFromBrowse) {
  15050. setValueFromItem(detail.model, input, item);
  15051. }
  15052. // The focus is retained on the input element when the menu is shown, unlike the combobox, in which the focus is passed to the menu.
  15053. // This results in screen readers not being able to announce the menu or highlighted item.
  15054. // The solution is to tell screen readers which menu item is highlighted using the `aria-activedescendant` attribute.
  15055. // TINY-9280: The aria attribute is removed when the menu is closed.
  15056. // Since `onDehighlight` is called only when highlighting a new menu item, this will be handled in
  15057. // https://github.com/tinymce/tinymce/blob/2d8c1c034e8aa484b868a0c44605489ee0ca9cd4/modules/alloy/src/main/ts/ephox/alloy/ui/composite/TypeaheadSpec.ts#L282
  15058. getOpt(item.element, 'id').each((id) => set$9(input.element, 'aria-activedescendant', id));
  15059. });
  15060. }
  15061. else {
  15062. // ASSUMPTION: Currently, any interaction with the menu via the keyboard or the mouse
  15063. // will firstly clear previewing mode before triggering any highlights
  15064. // so if we are still in previewing mode by the time we get to the highlight call,
  15065. // that means that the highlight was triggered NOT by the user interacting
  15066. // with the menu, but instead by the Highlighting API call that happens automatically
  15067. // when a streamed keyboard input event is updating its results. That call will
  15068. // try to keep any active highlight if there already was one (defaulting to first
  15069. // if it can't find the original), but if there wasn't an active highlight, but
  15070. // it is using "selectsOver", it will just highlight the first item. In this
  15071. // latter case, it is only doing that so that selectsOver has something to copy.
  15072. // So all of the complex code below is trying to handle whether we should stay
  15073. // in previewing mode after this highlight, and the ONLY case where we should stay
  15074. // in previewing mode is that we were in previewing mode, we are using selectsOver,
  15075. // and the selectsOver failed to succeed. In that case, to stay in previewing mode,
  15076. // we want to cancel the highlight that we just made via the highlighting API
  15077. // and reset previewing to true. Otherwise, all codepaths should set previewing
  15078. // to false, because now we have a valid highlight.
  15079. //
  15080. // As of 2022-08-18, the selectsOver model is not in use by TinyMCE, so
  15081. // this subtle interaction is unfortunately largely untested. Also, if we can't
  15082. // get a reference to the typeahead input by lazyTypeaheadComp, then we don't
  15083. // change previewing, either. Note also, that it is likely that if we checked
  15084. // if selectsOver would succeed before setting the highlight in the streaming
  15085. // response, this could might be a lot easier to follow.
  15086. detail.lazyTypeaheadComp.get().each((input) => {
  15087. attemptSelectOver(detail.model, input, item).fold(
  15088. // If we are in "previewing" mode and we can't select over the
  15089. // thing that is first, then clear the highlight.
  15090. // Hopefully, this doesn't cause a flicker. Find a better
  15091. // way to do this.
  15092. () => {
  15093. // If using "selectOver", we essentially want to cancel the highlight
  15094. // that was only invoked just so that we'd have something to selectOver,
  15095. // so we dehighlight, and then, importantly, *DON'T* clear previewing.
  15096. // We'll set it to be true to be explicit, although it should
  15097. // always be true if it reached here (unless an above function changed
  15098. // it)
  15099. if (detail.model.selectsOver) {
  15100. Highlighting.dehighlight(menu, item);
  15101. detail.previewing.set(true);
  15102. }
  15103. else {
  15104. // Because we aren't using selectsOver mode, we now want to keep
  15105. // whatever highlight we just made, and because we have a highlighted
  15106. // item in the menu, we are no longer previewing.
  15107. detail.previewing.set(false);
  15108. }
  15109. }, ((selectOverTextInInput) => {
  15110. // We have made a selection in the menu, and have selected over text
  15111. // in the input, so clear previewing.
  15112. selectOverTextInInput();
  15113. detail.previewing.set(false);
  15114. }));
  15115. });
  15116. }
  15117. },
  15118. // Because the focus stays inside the input, this onExecute is fired when the
  15119. // user "clicks" on an item. The focusing behaviour should be configured
  15120. // so that items don't get focus, but they prevent a mousedown event from
  15121. // firing so that the typeahead doesn't lose focus. This is the handler
  15122. // for clicking on an item. We need to close the sandbox, update the typeahead
  15123. // to show the item clicked on, and fire an execute.
  15124. onExecute: (_menu, item) => {
  15125. // Note: This will only work when the typeahead and menu are in the same system.
  15126. return detail.lazyTypeaheadComp.get().map((typeahead) => {
  15127. emitWith(typeahead, itemExecute(), { item });
  15128. return true;
  15129. });
  15130. },
  15131. onHover: (menu, item) => {
  15132. // Hovering is also a user-initiated action, so previewing mode is over.
  15133. // TODO: Have a better API for managing state in between parts.
  15134. detail.previewing.set(false);
  15135. detail.lazyTypeaheadComp.get().each((input) => {
  15136. if (detail.model.populateFromBrowse) {
  15137. setValueFromItem(detail.model, input, item);
  15138. }
  15139. });
  15140. }
  15141. };
  15142. }
  15143. })
  15144. ]);
  15145. const Typeahead = composite({
  15146. name: 'Typeahead',
  15147. configFields: schema(),
  15148. partFields: parts(),
  15149. factory: make$2
  15150. });
  15151. var global$b = tinymce.util.Tools.resolve('tinymce.ThemeManager');
  15152. var global$a = tinymce.util.Tools.resolve('tinymce.util.Delay');
  15153. var global$9 = tinymce.util.Tools.resolve('tinymce.dom.DOMUtils');
  15154. var global$8 = tinymce.util.Tools.resolve('tinymce.EditorManager');
  15155. var global$7 = tinymce.util.Tools.resolve('tinymce.Env');
  15156. var ToolbarMode$1;
  15157. (function (ToolbarMode) {
  15158. ToolbarMode["default"] = "wrap";
  15159. ToolbarMode["floating"] = "floating";
  15160. ToolbarMode["sliding"] = "sliding";
  15161. ToolbarMode["scrolling"] = "scrolling";
  15162. })(ToolbarMode$1 || (ToolbarMode$1 = {}));
  15163. var ToolbarLocation$1;
  15164. (function (ToolbarLocation) {
  15165. ToolbarLocation["auto"] = "auto";
  15166. ToolbarLocation["top"] = "top";
  15167. ToolbarLocation["bottom"] = "bottom";
  15168. })(ToolbarLocation$1 || (ToolbarLocation$1 = {}));
  15169. const option$2 = (name) => (editor) => editor.options.get(name);
  15170. const wrapOptional = (fn) => (editor) => Optional.from(fn(editor));
  15171. const register$f = (editor) => {
  15172. const isPhone = global$7.deviceType.isPhone();
  15173. const isMobile = global$7.deviceType.isTablet() || isPhone;
  15174. const registerOption = editor.options.register;
  15175. const stringOrFalseProcessor = (value) => isString(value) || value === false;
  15176. const stringOrNumberProcessor = (value) => isString(value) || isNumber(value);
  15177. registerOption('skin', {
  15178. processor: (value) => isString(value) || value === false,
  15179. default: 'oxide'
  15180. });
  15181. registerOption('skin_url', {
  15182. processor: 'string'
  15183. });
  15184. registerOption('height', {
  15185. processor: stringOrNumberProcessor,
  15186. default: Math.max(editor.getElement().offsetHeight, 400)
  15187. });
  15188. registerOption('width', {
  15189. processor: stringOrNumberProcessor,
  15190. default: global$9.DOM.getStyle(editor.getElement(), 'width')
  15191. });
  15192. registerOption('min_height', {
  15193. processor: 'number',
  15194. default: 100
  15195. });
  15196. registerOption('min_width', {
  15197. processor: 'number'
  15198. });
  15199. registerOption('max_height', {
  15200. processor: 'number'
  15201. });
  15202. registerOption('max_width', {
  15203. processor: 'number'
  15204. });
  15205. registerOption('style_formats', {
  15206. processor: 'object[]'
  15207. });
  15208. registerOption('style_formats_merge', {
  15209. processor: 'boolean',
  15210. default: false
  15211. });
  15212. registerOption('style_formats_autohide', {
  15213. processor: 'boolean',
  15214. default: false
  15215. });
  15216. registerOption('line_height_formats', {
  15217. processor: 'string',
  15218. default: '1 1.1 1.2 1.3 1.4 1.5 2'
  15219. });
  15220. registerOption('font_family_formats', {
  15221. processor: 'string',
  15222. default: 'Andale Mono=andale mono,monospace;' +
  15223. 'Arial=arial,helvetica,sans-serif;' +
  15224. 'Arial Black=arial black,sans-serif;' +
  15225. 'Book Antiqua=book antiqua,palatino,serif;' +
  15226. 'Comic Sans MS=comic sans ms,sans-serif;' +
  15227. 'Courier New=courier new,courier,monospace;' +
  15228. 'Georgia=georgia,palatino,serif;' +
  15229. 'Helvetica=helvetica,arial,sans-serif;' +
  15230. 'Impact=impact,sans-serif;' +
  15231. 'Symbol=symbol;' +
  15232. 'Tahoma=tahoma,arial,helvetica,sans-serif;' +
  15233. 'Terminal=terminal,monaco,monospace;' +
  15234. 'Times New Roman=times new roman,times,serif;' +
  15235. 'Trebuchet MS=trebuchet ms,geneva,sans-serif;' +
  15236. 'Verdana=verdana,geneva,sans-serif;' +
  15237. 'Webdings=webdings;' +
  15238. 'Wingdings=wingdings,zapf dingbats'
  15239. });
  15240. registerOption('font_size_formats', {
  15241. processor: 'string',
  15242. default: '8pt 10pt 12pt 14pt 18pt 24pt 36pt'
  15243. });
  15244. registerOption('font_size_input_default_unit', {
  15245. processor: 'string',
  15246. default: 'pt'
  15247. });
  15248. registerOption('block_formats', {
  15249. processor: 'string',
  15250. default: 'Paragraph=p;' +
  15251. 'Heading 1=h1;' +
  15252. 'Heading 2=h2;' +
  15253. 'Heading 3=h3;' +
  15254. 'Heading 4=h4;' +
  15255. 'Heading 5=h5;' +
  15256. 'Heading 6=h6;' +
  15257. 'Preformatted=pre'
  15258. });
  15259. registerOption('content_langs', {
  15260. processor: 'object[]'
  15261. });
  15262. registerOption('removed_menuitems', {
  15263. processor: 'string',
  15264. default: ''
  15265. });
  15266. registerOption('menubar', {
  15267. processor: (value) => isString(value) || isBoolean(value),
  15268. // Phones don't have a lot of screen space so disable the menubar
  15269. default: !isPhone
  15270. });
  15271. registerOption('menu', {
  15272. processor: 'object',
  15273. default: {}
  15274. });
  15275. registerOption('toolbar', {
  15276. processor: (value) => {
  15277. if (isBoolean(value) || isString(value) || isArray(value)) {
  15278. return { value, valid: true };
  15279. }
  15280. else {
  15281. return { valid: false, message: 'Must be a boolean, string or array.' };
  15282. }
  15283. },
  15284. default: true
  15285. });
  15286. // Register the toolbarN variations: toolbar1 -> toolbar9
  15287. range$2(9, (num) => {
  15288. registerOption('toolbar' + (num + 1), {
  15289. processor: 'string'
  15290. });
  15291. });
  15292. registerOption('toolbar_mode', {
  15293. processor: 'string',
  15294. // Use the default side-scrolling toolbar for tablets/phones
  15295. default: isMobile ? 'scrolling' : 'floating'
  15296. });
  15297. registerOption('toolbar_groups', {
  15298. processor: 'object',
  15299. default: {}
  15300. });
  15301. registerOption('toolbar_location', {
  15302. processor: 'string',
  15303. default: ToolbarLocation$1.auto
  15304. });
  15305. registerOption('toolbar_persist', {
  15306. processor: 'boolean',
  15307. default: false
  15308. });
  15309. registerOption('toolbar_sticky', {
  15310. processor: 'boolean',
  15311. default: editor.inline
  15312. });
  15313. registerOption('toolbar_sticky_offset', {
  15314. processor: 'number',
  15315. default: 0
  15316. });
  15317. registerOption('fixed_toolbar_container', {
  15318. processor: 'string',
  15319. default: ''
  15320. });
  15321. registerOption('fixed_toolbar_container_target', {
  15322. processor: 'object'
  15323. });
  15324. registerOption('ui_mode', {
  15325. processor: 'string',
  15326. default: 'combined'
  15327. });
  15328. registerOption('file_picker_callback', {
  15329. processor: 'function'
  15330. });
  15331. registerOption('file_picker_validator_handler', {
  15332. processor: 'function'
  15333. });
  15334. registerOption('file_picker_types', {
  15335. processor: 'string'
  15336. });
  15337. registerOption('typeahead_urls', {
  15338. processor: 'boolean',
  15339. default: true
  15340. });
  15341. registerOption('anchor_top', {
  15342. processor: stringOrFalseProcessor,
  15343. default: '#top'
  15344. });
  15345. registerOption('anchor_bottom', {
  15346. processor: stringOrFalseProcessor,
  15347. default: '#bottom'
  15348. });
  15349. registerOption('draggable_modal', {
  15350. processor: 'boolean',
  15351. default: false
  15352. });
  15353. registerOption('statusbar', {
  15354. processor: 'boolean',
  15355. default: true
  15356. });
  15357. registerOption('elementpath', {
  15358. processor: 'boolean',
  15359. default: true
  15360. });
  15361. registerOption('branding', {
  15362. processor: 'boolean',
  15363. default: true
  15364. });
  15365. registerOption('promotion', {
  15366. processor: 'boolean',
  15367. default: true
  15368. });
  15369. registerOption('resize', {
  15370. processor: (value) => value === 'both' || isBoolean(value),
  15371. // Editor resize doesn't work on touch devices at this stage
  15372. default: !global$7.deviceType.isTouch()
  15373. });
  15374. registerOption('sidebar_show', {
  15375. processor: 'string'
  15376. });
  15377. // This option is being registered in the theme instead of the help plugin as it cannot be accessed from the theme when registered there
  15378. registerOption('help_accessibility', {
  15379. processor: 'boolean',
  15380. default: editor.hasPlugin('help')
  15381. });
  15382. registerOption('default_font_stack', {
  15383. processor: 'string[]',
  15384. default: []
  15385. });
  15386. };
  15387. const isReadOnly = option$2('readonly');
  15388. const isDisabled = option$2('disabled');
  15389. const getHeightOption = option$2('height');
  15390. const getWidthOption = option$2('width');
  15391. const getMinWidthOption = wrapOptional(option$2('min_width'));
  15392. const getMinHeightOption = wrapOptional(option$2('min_height'));
  15393. const getMaxWidthOption = wrapOptional(option$2('max_width'));
  15394. const getMaxHeightOption = wrapOptional(option$2('max_height'));
  15395. const getUserStyleFormats = wrapOptional(option$2('style_formats'));
  15396. const shouldMergeStyleFormats = option$2('style_formats_merge');
  15397. const shouldAutoHideStyleFormats = option$2('style_formats_autohide');
  15398. const getContentLanguages = option$2('content_langs');
  15399. const getRemovedMenuItems = option$2('removed_menuitems');
  15400. const getToolbarMode = option$2('toolbar_mode');
  15401. const getToolbarGroups = option$2('toolbar_groups');
  15402. const getToolbarLocation = option$2('toolbar_location');
  15403. const fixedContainerSelector = option$2('fixed_toolbar_container');
  15404. const fixedToolbarContainerTarget = option$2('fixed_toolbar_container_target');
  15405. const isToolbarPersist = option$2('toolbar_persist');
  15406. const getStickyToolbarOffset = option$2('toolbar_sticky_offset');
  15407. const getMenubar = option$2('menubar');
  15408. const getToolbar = option$2('toolbar');
  15409. const getFilePickerCallback = option$2('file_picker_callback');
  15410. const getFilePickerValidatorHandler = option$2('file_picker_validator_handler');
  15411. const getFontSizeInputDefaultUnit = option$2('font_size_input_default_unit');
  15412. const getFilePickerTypes = option$2('file_picker_types');
  15413. const useTypeaheadUrls = option$2('typeahead_urls');
  15414. const getAnchorTop = option$2('anchor_top');
  15415. const getAnchorBottom = option$2('anchor_bottom');
  15416. const isDraggableModal$1 = option$2('draggable_modal');
  15417. const useStatusBar = option$2('statusbar');
  15418. const useElementPath = option$2('elementpath');
  15419. const useBranding = option$2('branding');
  15420. const getResize = option$2('resize');
  15421. const getPasteAsText = option$2('paste_as_text');
  15422. const getSidebarShow = option$2('sidebar_show');
  15423. const promotionEnabled = option$2('promotion');
  15424. const useHelpAccessibility = option$2('help_accessibility');
  15425. const getDefaultFontStack = option$2('default_font_stack');
  15426. const getSkin = option$2('skin');
  15427. const isSkinDisabled = (editor) => editor.options.get('skin') === false;
  15428. const isMenubarEnabled = (editor) => editor.options.get('menubar') !== false;
  15429. const getSkinUrl = (editor) => {
  15430. const skinUrl = editor.options.get('skin_url');
  15431. if (isSkinDisabled(editor)) {
  15432. return skinUrl;
  15433. }
  15434. else {
  15435. if (skinUrl) {
  15436. return editor.documentBaseURI.toAbsolute(skinUrl);
  15437. }
  15438. else {
  15439. const skin = editor.options.get('skin');
  15440. return global$8.baseURL + '/skins/ui/' + skin;
  15441. }
  15442. }
  15443. };
  15444. const getSkinUrlOption = (editor) => Optional.from(editor.options.get('skin_url'));
  15445. const getLineHeightFormats = (editor) => editor.options.get('line_height_formats').split(' ');
  15446. const isToolbarEnabled = (editor) => {
  15447. const toolbar = getToolbar(editor);
  15448. const isToolbarString = isString(toolbar);
  15449. const isToolbarObjectArray = isArray(toolbar) && toolbar.length > 0;
  15450. // Toolbar is enabled if its value is true, a string or non-empty object array, but not string array
  15451. return !isMultipleToolbars(editor) && (isToolbarObjectArray || isToolbarString || toolbar === true);
  15452. };
  15453. // Convert toolbar<n> into toolbars array
  15454. const getMultipleToolbarsOption = (editor) => {
  15455. const toolbars = range$2(9, (num) => editor.options.get('toolbar' + (num + 1)));
  15456. const toolbarArray = filter$2(toolbars, isString);
  15457. return someIf(toolbarArray.length > 0, toolbarArray);
  15458. };
  15459. // Check if multiple toolbars is enabled
  15460. // Multiple toolbars is enabled if toolbar value is a string array or if toolbar<n> is present
  15461. const isMultipleToolbars = (editor) => getMultipleToolbarsOption(editor).fold(() => {
  15462. const toolbar = getToolbar(editor);
  15463. return isArrayOf(toolbar, isString) && toolbar.length > 0;
  15464. }, always);
  15465. const isToolbarLocationBottom = (editor) => getToolbarLocation(editor) === ToolbarLocation$1.bottom;
  15466. const fixedContainerTarget = (editor) => {
  15467. var _a;
  15468. if (!editor.inline) {
  15469. // fixed_toolbar_container(_target) is only available in inline mode
  15470. return Optional.none();
  15471. }
  15472. const selector = (_a = fixedContainerSelector(editor)) !== null && _a !== void 0 ? _a : '';
  15473. if (selector.length > 0) {
  15474. // If we have a valid selector
  15475. return descendant(body(), selector);
  15476. }
  15477. const element = fixedToolbarContainerTarget(editor);
  15478. if (isNonNullable(element)) {
  15479. // If we have a valid target
  15480. return Optional.some(SugarElement.fromDom(element));
  15481. }
  15482. return Optional.none();
  15483. };
  15484. const useFixedContainer = (editor) => editor.inline && fixedContainerTarget(editor).isSome();
  15485. const getUiContainer = (editor) => {
  15486. const fixedContainer = fixedContainerTarget(editor);
  15487. return fixedContainer.getOrThunk(() => getContentContainer(getRootNode(SugarElement.fromDom(editor.getElement()))));
  15488. };
  15489. const isDistractionFree = (editor) => editor.inline && !isMenubarEnabled(editor) && !isToolbarEnabled(editor) && !isMultipleToolbars(editor);
  15490. const isStickyToolbar = (editor) => {
  15491. const isStickyToolbar = editor.options.get('toolbar_sticky');
  15492. return (isStickyToolbar || editor.inline) && !useFixedContainer(editor) && !isDistractionFree(editor);
  15493. };
  15494. const isSplitUiMode = (editor) => !useFixedContainer(editor) && editor.options.get('ui_mode') === 'split';
  15495. const getMenus = (editor) => {
  15496. const menu = editor.options.get('menu');
  15497. return map$1(menu, (menu) => ({ ...menu, items: menu.items }));
  15498. };
  15499. var Options = /*#__PURE__*/Object.freeze({
  15500. __proto__: null,
  15501. get ToolbarMode () { return ToolbarMode$1; },
  15502. get ToolbarLocation () { return ToolbarLocation$1; },
  15503. register: register$f,
  15504. getSkinUrl: getSkinUrl,
  15505. getSkinUrlOption: getSkinUrlOption,
  15506. isReadOnly: isReadOnly,
  15507. isDisabled: isDisabled,
  15508. getSkin: getSkin,
  15509. isSkinDisabled: isSkinDisabled,
  15510. getHeightOption: getHeightOption,
  15511. getWidthOption: getWidthOption,
  15512. getMinWidthOption: getMinWidthOption,
  15513. getMinHeightOption: getMinHeightOption,
  15514. getMaxWidthOption: getMaxWidthOption,
  15515. getMaxHeightOption: getMaxHeightOption,
  15516. getUserStyleFormats: getUserStyleFormats,
  15517. shouldMergeStyleFormats: shouldMergeStyleFormats,
  15518. shouldAutoHideStyleFormats: shouldAutoHideStyleFormats,
  15519. getLineHeightFormats: getLineHeightFormats,
  15520. getContentLanguages: getContentLanguages,
  15521. getRemovedMenuItems: getRemovedMenuItems,
  15522. isMenubarEnabled: isMenubarEnabled,
  15523. isMultipleToolbars: isMultipleToolbars,
  15524. isToolbarEnabled: isToolbarEnabled,
  15525. isToolbarPersist: isToolbarPersist,
  15526. getMultipleToolbarsOption: getMultipleToolbarsOption,
  15527. getUiContainer: getUiContainer,
  15528. useFixedContainer: useFixedContainer,
  15529. isSplitUiMode: isSplitUiMode,
  15530. getToolbarMode: getToolbarMode,
  15531. isDraggableModal: isDraggableModal$1,
  15532. isDistractionFree: isDistractionFree,
  15533. isStickyToolbar: isStickyToolbar,
  15534. getStickyToolbarOffset: getStickyToolbarOffset,
  15535. getToolbarLocation: getToolbarLocation,
  15536. isToolbarLocationBottom: isToolbarLocationBottom,
  15537. getToolbarGroups: getToolbarGroups,
  15538. getMenus: getMenus,
  15539. getMenubar: getMenubar,
  15540. getToolbar: getToolbar,
  15541. getFilePickerCallback: getFilePickerCallback,
  15542. getFilePickerTypes: getFilePickerTypes,
  15543. useTypeaheadUrls: useTypeaheadUrls,
  15544. getAnchorTop: getAnchorTop,
  15545. getAnchorBottom: getAnchorBottom,
  15546. getFilePickerValidatorHandler: getFilePickerValidatorHandler,
  15547. getFontSizeInputDefaultUnit: getFontSizeInputDefaultUnit,
  15548. useStatusBar: useStatusBar,
  15549. useElementPath: useElementPath,
  15550. promotionEnabled: promotionEnabled,
  15551. useBranding: useBranding,
  15552. getResize: getResize,
  15553. getPasteAsText: getPasteAsText,
  15554. getSidebarShow: getSidebarShow,
  15555. useHelpAccessibility: useHelpAccessibility,
  15556. getDefaultFontStack: getDefaultFontStack
  15557. });
  15558. // See https://developer.mozilla.org/en-US/docs/Glossary/Scroll_container for what makes an element scrollable
  15559. const nonScrollingOverflows = ['visible', 'hidden', 'clip'];
  15560. const isScrollingOverflowValue = (value) => trim$1(value).length > 0 && !contains$2(nonScrollingOverflows, value);
  15561. const isScroller = (elem) => {
  15562. if (isHTMLElement(elem)) {
  15563. const overflowX = get$e(elem, 'overflow-x');
  15564. const overflowY = get$e(elem, 'overflow-y');
  15565. return isScrollingOverflowValue(overflowX) || isScrollingOverflowValue(overflowY);
  15566. }
  15567. else {
  15568. return false;
  15569. }
  15570. };
  15571. const isFullscreen = (editor) => editor.plugins.fullscreen && editor.plugins.fullscreen.isFullscreen();
  15572. // NOTE: Calculating the list of scrolling ancestors each time this function is called might
  15573. // be unnecessary. It will depend on its usage.
  15574. const detect = (editor, popupSinkElem) => {
  15575. const ancestorsScrollers = ancestors(popupSinkElem, isScroller);
  15576. // If there is no scrollable container, we try to see if it's in a shadow root, and try to traverse beyond the host of shadow root to retrieve the scrollable container
  15577. // If it is not within a ShadowRoot, since if there's a scrollable container as the ancestors, then it would not execute the code below, or return an empty array if it's not in a ShadowRoot
  15578. const scrollers = ancestorsScrollers.length === 0
  15579. ? getShadowRoot(popupSinkElem).map(getShadowHost).map((x) => ancestors(x, isScroller)).getOr([])
  15580. : ancestorsScrollers;
  15581. return head(scrollers)
  15582. .map((element) => ({
  15583. element,
  15584. // A list of all scrolling elements above the nearest scroller,
  15585. // ordered from closest to popup -> closest to top of document
  15586. others: scrollers.slice(1),
  15587. isFullscreen: () => isFullscreen(editor)
  15588. }));
  15589. };
  15590. const detectWhenSplitUiMode = (editor, popupSinkElem) => isSplitUiMode(editor) ? detect(editor, popupSinkElem) : Optional.none();
  15591. // Using all the scrolling viewports in the ancestry, limit the absolute
  15592. // coordinates of window so that the bounds are limited by all the scrolling
  15593. // viewports.
  15594. const getBoundsFrom = (sc) => {
  15595. const scrollableBoxes = [
  15596. // sc.element is the main scroller, others are *additional* scrollers above that
  15597. // we need to combine all of them to constrain the bounds
  15598. ...map$2(sc.others, box$1),
  15599. win()
  15600. ];
  15601. return sc.isFullscreen() ? win() : constrainByMany(box$1(sc.element), scrollableBoxes);
  15602. };
  15603. /*! @license DOMPurify 3.2.6 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.2.6/LICENSE */
  15604. const {
  15605. entries,
  15606. setPrototypeOf,
  15607. isFrozen,
  15608. getPrototypeOf,
  15609. getOwnPropertyDescriptor
  15610. } = Object;
  15611. let {
  15612. freeze,
  15613. seal,
  15614. create: create$1
  15615. } = Object; // eslint-disable-line import/no-mutable-exports
  15616. let {
  15617. apply,
  15618. construct
  15619. } = typeof Reflect !== 'undefined' && Reflect;
  15620. if (!freeze) {
  15621. freeze = function freeze(x) {
  15622. return x;
  15623. };
  15624. }
  15625. if (!seal) {
  15626. seal = function seal(x) {
  15627. return x;
  15628. };
  15629. }
  15630. if (!apply) {
  15631. apply = function apply(fun, thisValue, args) {
  15632. return fun.apply(thisValue, args);
  15633. };
  15634. }
  15635. if (!construct) {
  15636. construct = function construct(Func, args) {
  15637. return new Func(...args);
  15638. };
  15639. }
  15640. const arrayForEach = unapply(Array.prototype.forEach);
  15641. const arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
  15642. const arrayPop = unapply(Array.prototype.pop);
  15643. const arrayPush = unapply(Array.prototype.push);
  15644. const arraySplice = unapply(Array.prototype.splice);
  15645. const stringToLowerCase = unapply(String.prototype.toLowerCase);
  15646. const stringToString = unapply(String.prototype.toString);
  15647. const stringMatch = unapply(String.prototype.match);
  15648. const stringReplace = unapply(String.prototype.replace);
  15649. const stringIndexOf = unapply(String.prototype.indexOf);
  15650. const stringTrim = unapply(String.prototype.trim);
  15651. const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
  15652. const regExpTest = unapply(RegExp.prototype.test);
  15653. const typeErrorCreate = unconstruct(TypeError);
  15654. /**
  15655. * Creates a new function that calls the given function with a specified thisArg and arguments.
  15656. *
  15657. * @param func - The function to be wrapped and called.
  15658. * @returns A new function that calls the given function with a specified thisArg and arguments.
  15659. */
  15660. function unapply(func) {
  15661. return function (thisArg) {
  15662. if (thisArg instanceof RegExp) {
  15663. thisArg.lastIndex = 0;
  15664. }
  15665. for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
  15666. args[_key - 1] = arguments[_key];
  15667. }
  15668. return apply(func, thisArg, args);
  15669. };
  15670. }
  15671. /**
  15672. * Creates a new function that constructs an instance of the given constructor function with the provided arguments.
  15673. *
  15674. * @param func - The constructor function to be wrapped and called.
  15675. * @returns A new function that constructs an instance of the given constructor function with the provided arguments.
  15676. */
  15677. function unconstruct(func) {
  15678. return function () {
  15679. for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
  15680. args[_key2] = arguments[_key2];
  15681. }
  15682. return construct(func, args);
  15683. };
  15684. }
  15685. /**
  15686. * Add properties to a lookup table
  15687. *
  15688. * @param set - The set to which elements will be added.
  15689. * @param array - The array containing elements to be added to the set.
  15690. * @param transformCaseFunc - An optional function to transform the case of each element before adding to the set.
  15691. * @returns The modified set with added elements.
  15692. */
  15693. function addToSet(set, array) {
  15694. let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase;
  15695. if (setPrototypeOf) {
  15696. // Make 'in' and truthy checks like Boolean(set.constructor)
  15697. // independent of any properties defined on Object.prototype.
  15698. // Prevent prototype setters from intercepting set as a this value.
  15699. setPrototypeOf(set, null);
  15700. }
  15701. let l = array.length;
  15702. while (l--) {
  15703. let element = array[l];
  15704. if (typeof element === 'string') {
  15705. const lcElement = transformCaseFunc(element);
  15706. if (lcElement !== element) {
  15707. // Config presets (e.g. tags.js, attrs.js) are immutable.
  15708. if (!isFrozen(array)) {
  15709. array[l] = lcElement;
  15710. }
  15711. element = lcElement;
  15712. }
  15713. }
  15714. set[element] = true;
  15715. }
  15716. return set;
  15717. }
  15718. /**
  15719. * Clean up an array to harden against CSPP
  15720. *
  15721. * @param array - The array to be cleaned.
  15722. * @returns The cleaned version of the array
  15723. */
  15724. function cleanArray(array) {
  15725. for (let index = 0; index < array.length; index++) {
  15726. const isPropertyExist = objectHasOwnProperty(array, index);
  15727. if (!isPropertyExist) {
  15728. array[index] = null;
  15729. }
  15730. }
  15731. return array;
  15732. }
  15733. /**
  15734. * Shallow clone an object
  15735. *
  15736. * @param object - The object to be cloned.
  15737. * @returns A new object that copies the original.
  15738. */
  15739. function clone(object) {
  15740. const newObject = create$1(null);
  15741. for (const [property, value] of entries(object)) {
  15742. const isPropertyExist = objectHasOwnProperty(object, property);
  15743. if (isPropertyExist) {
  15744. if (Array.isArray(value)) {
  15745. newObject[property] = cleanArray(value);
  15746. } else if (value && typeof value === 'object' && value.constructor === Object) {
  15747. newObject[property] = clone(value);
  15748. } else {
  15749. newObject[property] = value;
  15750. }
  15751. }
  15752. }
  15753. return newObject;
  15754. }
  15755. /**
  15756. * This method automatically checks if the prop is function or getter and behaves accordingly.
  15757. *
  15758. * @param object - The object to look up the getter function in its prototype chain.
  15759. * @param prop - The property name for which to find the getter function.
  15760. * @returns The getter function found in the prototype chain or a fallback function.
  15761. */
  15762. function lookupGetter(object, prop) {
  15763. while (object !== null) {
  15764. const desc = getOwnPropertyDescriptor(object, prop);
  15765. if (desc) {
  15766. if (desc.get) {
  15767. return unapply(desc.get);
  15768. }
  15769. if (typeof desc.value === 'function') {
  15770. return unapply(desc.value);
  15771. }
  15772. }
  15773. object = getPrototypeOf(object);
  15774. }
  15775. function fallbackValue() {
  15776. return null;
  15777. }
  15778. return fallbackValue;
  15779. }
  15780. const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);
  15781. const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);
  15782. const svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']);
  15783. // List of SVG elements that are disallowed by default.
  15784. // We still need to know them so that we can do namespace
  15785. // checks properly in case one wants to add them to
  15786. // allow-list.
  15787. const svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']);
  15788. const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'mprescripts']);
  15789. // Similarly to SVG, we want to know all MathML elements,
  15790. // even those that we disallow by default.
  15791. const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
  15792. const text$1 = freeze(['#text']);
  15793. const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']);
  15794. const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
  15795. const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);
  15796. const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
  15797. // eslint-disable-next-line unicorn/better-regex
  15798. const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
  15799. const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
  15800. const TMPLIT_EXPR = seal(/\$\{[\w\W]*/gm); // eslint-disable-line unicorn/better-regex
  15801. const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]+$/); // eslint-disable-line no-useless-escape
  15802. const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
  15803. const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|matrix):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
  15804. );
  15805. const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
  15806. const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
  15807. );
  15808. const DOCTYPE_NAME = seal(/^html$/i);
  15809. const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);
  15810. var EXPRESSIONS = /*#__PURE__*/Object.freeze({
  15811. __proto__: null,
  15812. ARIA_ATTR: ARIA_ATTR,
  15813. ATTR_WHITESPACE: ATTR_WHITESPACE,
  15814. CUSTOM_ELEMENT: CUSTOM_ELEMENT,
  15815. DATA_ATTR: DATA_ATTR,
  15816. DOCTYPE_NAME: DOCTYPE_NAME,
  15817. ERB_EXPR: ERB_EXPR,
  15818. IS_ALLOWED_URI: IS_ALLOWED_URI,
  15819. IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
  15820. MUSTACHE_EXPR: MUSTACHE_EXPR,
  15821. TMPLIT_EXPR: TMPLIT_EXPR
  15822. });
  15823. /* eslint-disable @typescript-eslint/indent */
  15824. // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
  15825. const NODE_TYPE = {
  15826. element: 1,
  15827. attribute: 2,
  15828. text: 3,
  15829. cdataSection: 4,
  15830. entityReference: 5,
  15831. // Deprecated
  15832. entityNode: 6,
  15833. // Deprecated
  15834. progressingInstruction: 7,
  15835. comment: 8,
  15836. document: 9,
  15837. documentType: 10,
  15838. documentFragment: 11,
  15839. notation: 12 // Deprecated
  15840. };
  15841. const getGlobal = function getGlobal() {
  15842. return typeof window === 'undefined' ? null : window;
  15843. };
  15844. /**
  15845. * Creates a no-op policy for internal use only.
  15846. * Don't export this function outside this module!
  15847. * @param trustedTypes The policy factory.
  15848. * @param purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).
  15849. * @return The policy created (or null, if Trusted Types
  15850. * are not supported or creating the policy failed).
  15851. */
  15852. const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) {
  15853. if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') {
  15854. return null;
  15855. }
  15856. // Allow the callers to control the unique policy name
  15857. // by adding a data-tt-policy-suffix to the script element with the DOMPurify.
  15858. // Policy creation with duplicate names throws in Trusted Types.
  15859. let suffix = null;
  15860. const ATTR_NAME = 'data-tt-policy-suffix';
  15861. if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {
  15862. suffix = purifyHostElement.getAttribute(ATTR_NAME);
  15863. }
  15864. const policyName = 'dompurify' + (suffix ? '#' + suffix : '');
  15865. try {
  15866. return trustedTypes.createPolicy(policyName, {
  15867. createHTML(html) {
  15868. return html;
  15869. },
  15870. createScriptURL(scriptUrl) {
  15871. return scriptUrl;
  15872. }
  15873. });
  15874. } catch (_) {
  15875. // Policy creation failed (most likely another DOMPurify script has
  15876. // already run). Skip creating the policy, as this will only cause errors
  15877. // if TT are enforced.
  15878. console.warn('TrustedTypes policy ' + policyName + ' could not be created.');
  15879. return null;
  15880. }
  15881. };
  15882. const _createHooksMap = function _createHooksMap() {
  15883. return {
  15884. afterSanitizeAttributes: [],
  15885. afterSanitizeElements: [],
  15886. afterSanitizeShadowDOM: [],
  15887. beforeSanitizeAttributes: [],
  15888. beforeSanitizeElements: [],
  15889. beforeSanitizeShadowDOM: [],
  15890. uponSanitizeAttribute: [],
  15891. uponSanitizeElement: [],
  15892. uponSanitizeShadowNode: []
  15893. };
  15894. };
  15895. function createDOMPurify() {
  15896. let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
  15897. const DOMPurify = root => createDOMPurify(root);
  15898. DOMPurify.version = '3.2.6';
  15899. DOMPurify.removed = [];
  15900. if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
  15901. // Not running in a browser, provide a factory function
  15902. // so that you can pass your own Window
  15903. DOMPurify.isSupported = false;
  15904. return DOMPurify;
  15905. }
  15906. let {
  15907. document
  15908. } = window;
  15909. const originalDocument = document;
  15910. const currentScript = originalDocument.currentScript;
  15911. const {
  15912. DocumentFragment,
  15913. HTMLTemplateElement,
  15914. Node,
  15915. Element,
  15916. NodeFilter,
  15917. NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,
  15918. HTMLFormElement,
  15919. DOMParser,
  15920. trustedTypes
  15921. } = window;
  15922. const ElementPrototype = Element.prototype;
  15923. const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
  15924. const remove = lookupGetter(ElementPrototype, 'remove');
  15925. const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
  15926. const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
  15927. const getParentNode = lookupGetter(ElementPrototype, 'parentNode');
  15928. // As per issue #47, the web-components registry is inherited by a
  15929. // new document created via createHTMLDocument. As per the spec
  15930. // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
  15931. // a new empty registry is used when creating a template contents owner
  15932. // document, so we use that as our parent document to ensure nothing
  15933. // is inherited.
  15934. if (typeof HTMLTemplateElement === 'function') {
  15935. const template = document.createElement('template');
  15936. if (template.content && template.content.ownerDocument) {
  15937. document = template.content.ownerDocument;
  15938. }
  15939. }
  15940. let trustedTypesPolicy;
  15941. let emptyHTML = '';
  15942. const {
  15943. implementation,
  15944. createNodeIterator,
  15945. createDocumentFragment,
  15946. getElementsByTagName
  15947. } = document;
  15948. const {
  15949. importNode
  15950. } = originalDocument;
  15951. let hooks = _createHooksMap();
  15952. /**
  15953. * Expose whether this browser supports running the full DOMPurify.
  15954. */
  15955. DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
  15956. const {
  15957. MUSTACHE_EXPR,
  15958. ERB_EXPR,
  15959. TMPLIT_EXPR,
  15960. DATA_ATTR,
  15961. ARIA_ATTR,
  15962. IS_SCRIPT_OR_DATA,
  15963. ATTR_WHITESPACE,
  15964. CUSTOM_ELEMENT
  15965. } = EXPRESSIONS;
  15966. let {
  15967. IS_ALLOWED_URI: IS_ALLOWED_URI$1
  15968. } = EXPRESSIONS;
  15969. /**
  15970. * We consider the elements and attributes below to be safe. Ideally
  15971. * don't add any new ones but feel free to remove unwanted ones.
  15972. */
  15973. /* allowed element names */
  15974. let ALLOWED_TAGS = null;
  15975. const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text$1]);
  15976. /* Allowed attribute names */
  15977. let ALLOWED_ATTR = null;
  15978. const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]);
  15979. /*
  15980. * Configure how DOMPurify should handle custom elements and their attributes as well as customized built-in elements.
  15981. * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
  15982. * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
  15983. * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
  15984. */
  15985. let CUSTOM_ELEMENT_HANDLING = Object.seal(create$1(null, {
  15986. tagNameCheck: {
  15987. writable: true,
  15988. configurable: false,
  15989. enumerable: true,
  15990. value: null
  15991. },
  15992. attributeNameCheck: {
  15993. writable: true,
  15994. configurable: false,
  15995. enumerable: true,
  15996. value: null
  15997. },
  15998. allowCustomizedBuiltInElements: {
  15999. writable: true,
  16000. configurable: false,
  16001. enumerable: true,
  16002. value: false
  16003. }
  16004. }));
  16005. /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
  16006. let FORBID_TAGS = null;
  16007. /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
  16008. let FORBID_ATTR = null;
  16009. /* Decide if ARIA attributes are okay */
  16010. let ALLOW_ARIA_ATTR = true;
  16011. /* Decide if custom data attributes are okay */
  16012. let ALLOW_DATA_ATTR = true;
  16013. /* Decide if unknown protocols are okay */
  16014. let ALLOW_UNKNOWN_PROTOCOLS = false;
  16015. /* Decide if self-closing tags in attributes are allowed.
  16016. * Usually removed due to a mXSS issue in jQuery 3.0 */
  16017. let ALLOW_SELF_CLOSE_IN_ATTR = true;
  16018. /* Output should be safe for common template engines.
  16019. * This means, DOMPurify removes data attributes, mustaches and ERB
  16020. */
  16021. let SAFE_FOR_TEMPLATES = false;
  16022. /* Output should be safe even for XML used within HTML and alike.
  16023. * This means, DOMPurify removes comments when containing risky content.
  16024. */
  16025. let SAFE_FOR_XML = true;
  16026. /* Decide if document with <html>... should be returned */
  16027. let WHOLE_DOCUMENT = false;
  16028. /* Track whether config is already set on this instance of DOMPurify. */
  16029. let SET_CONFIG = false;
  16030. /* Decide if all elements (e.g. style, script) must be children of
  16031. * document.body. By default, browsers might move them to document.head */
  16032. let FORCE_BODY = false;
  16033. /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
  16034. * string (or a TrustedHTML object if Trusted Types are supported).
  16035. * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
  16036. */
  16037. let RETURN_DOM = false;
  16038. /* Decide if a DOM `DocumentFragment` should be returned, instead of a html
  16039. * string (or a TrustedHTML object if Trusted Types are supported) */
  16040. let RETURN_DOM_FRAGMENT = false;
  16041. /* Try to return a Trusted Type object instead of a string, return a string in
  16042. * case Trusted Types are not supported */
  16043. let RETURN_TRUSTED_TYPE = false;
  16044. /* Output should be free from DOM clobbering attacks?
  16045. * This sanitizes markups named with colliding, clobberable built-in DOM APIs.
  16046. */
  16047. let SANITIZE_DOM = true;
  16048. /* Achieve full DOM Clobbering protection by isolating the namespace of named
  16049. * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.
  16050. *
  16051. * HTML/DOM spec rules that enable DOM Clobbering:
  16052. * - Named Access on Window (§7.3.3)
  16053. * - DOM Tree Accessors (§3.1.5)
  16054. * - Form Element Parent-Child Relations (§4.10.3)
  16055. * - Iframe srcdoc / Nested WindowProxies (§4.8.5)
  16056. * - HTMLCollection (§4.2.10.2)
  16057. *
  16058. * Namespace isolation is implemented by prefixing `id` and `name` attributes
  16059. * with a constant string, i.e., `user-content-`
  16060. */
  16061. let SANITIZE_NAMED_PROPS = false;
  16062. const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';
  16063. /* Keep element content when removing element? */
  16064. let KEEP_CONTENT = true;
  16065. /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
  16066. * of importing it into a new Document and returning a sanitized copy */
  16067. let IN_PLACE = false;
  16068. /* Allow usage of profiles like html, svg and mathMl */
  16069. let USE_PROFILES = {};
  16070. /* Tags to ignore content of when KEEP_CONTENT is true */
  16071. let FORBID_CONTENTS = null;
  16072. const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
  16073. /* Tags that are safe for data: URIs */
  16074. let DATA_URI_TAGS = null;
  16075. const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
  16076. /* Attributes safe for values like "javascript:" */
  16077. let URI_SAFE_ATTRIBUTES = null;
  16078. const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
  16079. const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
  16080. const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
  16081. const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
  16082. /* Document namespace */
  16083. let NAMESPACE = HTML_NAMESPACE;
  16084. let IS_EMPTY_INPUT = false;
  16085. /* Allowed XHTML+XML namespaces */
  16086. let ALLOWED_NAMESPACES = null;
  16087. const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);
  16088. let MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
  16089. let HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']);
  16090. // Certain elements are allowed in both SVG and HTML
  16091. // namespace. We need to specify them explicitly
  16092. // so that they don't get erroneously deleted from
  16093. // HTML namespace.
  16094. const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
  16095. /* Parsing of strict XHTML documents */
  16096. let PARSER_MEDIA_TYPE = null;
  16097. const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
  16098. const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
  16099. let transformCaseFunc = null;
  16100. /* Keep a reference to config to pass to hooks */
  16101. let CONFIG = null;
  16102. /* Ideally, do not touch anything below this line */
  16103. /* ______________________________________________ */
  16104. const formElement = document.createElement('form');
  16105. const isRegexOrFunction = function isRegexOrFunction(testValue) {
  16106. return testValue instanceof RegExp || testValue instanceof Function;
  16107. };
  16108. /**
  16109. * _parseConfig
  16110. *
  16111. * @param cfg optional config literal
  16112. */
  16113. // eslint-disable-next-line complexity
  16114. const _parseConfig = function _parseConfig() {
  16115. let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  16116. if (CONFIG && CONFIG === cfg) {
  16117. return;
  16118. }
  16119. /* Shield configuration object from tampering */
  16120. if (!cfg || typeof cfg !== 'object') {
  16121. cfg = {};
  16122. }
  16123. /* Shield configuration object from prototype pollution */
  16124. cfg = clone(cfg);
  16125. PARSER_MEDIA_TYPE =
  16126. // eslint-disable-next-line unicorn/prefer-includes
  16127. SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;
  16128. // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
  16129. transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;
  16130. /* Set configuration parameters */
  16131. ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
  16132. ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
  16133. ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
  16134. URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR, transformCaseFunc) : DEFAULT_URI_SAFE_ATTRIBUTES;
  16135. DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS, transformCaseFunc) : DEFAULT_DATA_URI_TAGS;
  16136. FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
  16137. FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : clone({});
  16138. FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : clone({});
  16139. USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false;
  16140. ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
  16141. ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
  16142. ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
  16143. ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true
  16144. SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
  16145. SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; // Default true
  16146. WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
  16147. RETURN_DOM = cfg.RETURN_DOM || false; // Default false
  16148. RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
  16149. RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
  16150. FORCE_BODY = cfg.FORCE_BODY || false; // Default false
  16151. SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
  16152. SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
  16153. KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
  16154. IN_PLACE = cfg.IN_PLACE || false; // Default false
  16155. IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;
  16156. NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
  16157. MATHML_TEXT_INTEGRATION_POINTS = cfg.MATHML_TEXT_INTEGRATION_POINTS || MATHML_TEXT_INTEGRATION_POINTS;
  16158. HTML_INTEGRATION_POINTS = cfg.HTML_INTEGRATION_POINTS || HTML_INTEGRATION_POINTS;
  16159. CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};
  16160. if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
  16161. CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
  16162. }
  16163. if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
  16164. CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
  16165. }
  16166. if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {
  16167. CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
  16168. }
  16169. if (SAFE_FOR_TEMPLATES) {
  16170. ALLOW_DATA_ATTR = false;
  16171. }
  16172. if (RETURN_DOM_FRAGMENT) {
  16173. RETURN_DOM = true;
  16174. }
  16175. /* Parse profile info */
  16176. if (USE_PROFILES) {
  16177. ALLOWED_TAGS = addToSet({}, text$1);
  16178. ALLOWED_ATTR = [];
  16179. if (USE_PROFILES.html === true) {
  16180. addToSet(ALLOWED_TAGS, html$1);
  16181. addToSet(ALLOWED_ATTR, html);
  16182. }
  16183. if (USE_PROFILES.svg === true) {
  16184. addToSet(ALLOWED_TAGS, svg$1);
  16185. addToSet(ALLOWED_ATTR, svg);
  16186. addToSet(ALLOWED_ATTR, xml);
  16187. }
  16188. if (USE_PROFILES.svgFilters === true) {
  16189. addToSet(ALLOWED_TAGS, svgFilters);
  16190. addToSet(ALLOWED_ATTR, svg);
  16191. addToSet(ALLOWED_ATTR, xml);
  16192. }
  16193. if (USE_PROFILES.mathMl === true) {
  16194. addToSet(ALLOWED_TAGS, mathMl$1);
  16195. addToSet(ALLOWED_ATTR, mathMl);
  16196. addToSet(ALLOWED_ATTR, xml);
  16197. }
  16198. }
  16199. /* Merge configuration parameters */
  16200. if (cfg.ADD_TAGS) {
  16201. if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
  16202. ALLOWED_TAGS = clone(ALLOWED_TAGS);
  16203. }
  16204. addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
  16205. }
  16206. if (cfg.ADD_ATTR) {
  16207. if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
  16208. ALLOWED_ATTR = clone(ALLOWED_ATTR);
  16209. }
  16210. addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
  16211. }
  16212. if (cfg.ADD_URI_SAFE_ATTR) {
  16213. addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
  16214. }
  16215. if (cfg.FORBID_CONTENTS) {
  16216. if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
  16217. FORBID_CONTENTS = clone(FORBID_CONTENTS);
  16218. }
  16219. addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
  16220. }
  16221. /* Add #text in case KEEP_CONTENT is set to true */
  16222. if (KEEP_CONTENT) {
  16223. ALLOWED_TAGS['#text'] = true;
  16224. }
  16225. /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
  16226. if (WHOLE_DOCUMENT) {
  16227. addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
  16228. }
  16229. /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
  16230. if (ALLOWED_TAGS.table) {
  16231. addToSet(ALLOWED_TAGS, ['tbody']);
  16232. delete FORBID_TAGS.tbody;
  16233. }
  16234. if (cfg.TRUSTED_TYPES_POLICY) {
  16235. if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {
  16236. throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');
  16237. }
  16238. if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {
  16239. throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
  16240. }
  16241. // Overwrite existing TrustedTypes policy.
  16242. trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;
  16243. // Sign local variables required by `sanitize`.
  16244. emptyHTML = trustedTypesPolicy.createHTML('');
  16245. } else {
  16246. // Uninitialized policy, attempt to initialize the internal dompurify policy.
  16247. if (trustedTypesPolicy === undefined) {
  16248. trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
  16249. }
  16250. // If creating the internal policy succeeded sign internal variables.
  16251. if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') {
  16252. emptyHTML = trustedTypesPolicy.createHTML('');
  16253. }
  16254. }
  16255. // Prevent further manipulation of configuration.
  16256. // Not available in IE8, Safari 5, etc.
  16257. if (freeze) {
  16258. freeze(cfg);
  16259. }
  16260. CONFIG = cfg;
  16261. };
  16262. /* Keep track of all possible SVG and MathML tags
  16263. * so that we can perform the namespace checks
  16264. * correctly. */
  16265. const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
  16266. const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);
  16267. /**
  16268. * @param element a DOM element whose namespace is being checked
  16269. * @returns Return false if the element has a
  16270. * namespace that a spec-compliant parser would never
  16271. * return. Return true otherwise.
  16272. */
  16273. const _checkValidNamespace = function _checkValidNamespace(element) {
  16274. let parent = getParentNode(element);
  16275. // In JSDOM, if we're inside shadow DOM, then parentNode
  16276. // can be null. We just simulate parent in this case.
  16277. if (!parent || !parent.tagName) {
  16278. parent = {
  16279. namespaceURI: NAMESPACE,
  16280. tagName: 'template'
  16281. };
  16282. }
  16283. const tagName = stringToLowerCase(element.tagName);
  16284. const parentTagName = stringToLowerCase(parent.tagName);
  16285. if (!ALLOWED_NAMESPACES[element.namespaceURI]) {
  16286. return false;
  16287. }
  16288. if (element.namespaceURI === SVG_NAMESPACE) {
  16289. // The only way to switch from HTML namespace to SVG
  16290. // is via <svg>. If it happens via any other tag, then
  16291. // it should be killed.
  16292. if (parent.namespaceURI === HTML_NAMESPACE) {
  16293. return tagName === 'svg';
  16294. }
  16295. // The only way to switch from MathML to SVG is via`
  16296. // svg if parent is either <annotation-xml> or MathML
  16297. // text integration points.
  16298. if (parent.namespaceURI === MATHML_NAMESPACE) {
  16299. return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
  16300. }
  16301. // We only allow elements that are defined in SVG
  16302. // spec. All others are disallowed in SVG namespace.
  16303. return Boolean(ALL_SVG_TAGS[tagName]);
  16304. }
  16305. if (element.namespaceURI === MATHML_NAMESPACE) {
  16306. // The only way to switch from HTML namespace to MathML
  16307. // is via <math>. If it happens via any other tag, then
  16308. // it should be killed.
  16309. if (parent.namespaceURI === HTML_NAMESPACE) {
  16310. return tagName === 'math';
  16311. }
  16312. // The only way to switch from SVG to MathML is via
  16313. // <math> and HTML integration points
  16314. if (parent.namespaceURI === SVG_NAMESPACE) {
  16315. return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
  16316. }
  16317. // We only allow elements that are defined in MathML
  16318. // spec. All others are disallowed in MathML namespace.
  16319. return Boolean(ALL_MATHML_TAGS[tagName]);
  16320. }
  16321. if (element.namespaceURI === HTML_NAMESPACE) {
  16322. // The only way to switch from SVG to HTML is via
  16323. // HTML integration points, and from MathML to HTML
  16324. // is via MathML text integration points
  16325. if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
  16326. return false;
  16327. }
  16328. if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
  16329. return false;
  16330. }
  16331. // We disallow tags that are specific for MathML
  16332. // or SVG and should never appear in HTML namespace
  16333. return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
  16334. }
  16335. // For XHTML and XML documents that support custom namespaces
  16336. if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
  16337. return true;
  16338. }
  16339. // The code should never reach this place (this means
  16340. // that the element somehow got namespace that is not
  16341. // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).
  16342. // Return false just in case.
  16343. return false;
  16344. };
  16345. /**
  16346. * _forceRemove
  16347. *
  16348. * @param node a DOM node
  16349. */
  16350. const _forceRemove = function _forceRemove(node) {
  16351. arrayPush(DOMPurify.removed, {
  16352. element: node
  16353. });
  16354. try {
  16355. // eslint-disable-next-line unicorn/prefer-dom-node-remove
  16356. getParentNode(node).removeChild(node);
  16357. } catch (_) {
  16358. remove(node);
  16359. }
  16360. };
  16361. /**
  16362. * _removeAttribute
  16363. *
  16364. * @param name an Attribute name
  16365. * @param element a DOM node
  16366. */
  16367. const _removeAttribute = function _removeAttribute(name, element) {
  16368. try {
  16369. arrayPush(DOMPurify.removed, {
  16370. attribute: element.getAttributeNode(name),
  16371. from: element
  16372. });
  16373. } catch (_) {
  16374. arrayPush(DOMPurify.removed, {
  16375. attribute: null,
  16376. from: element
  16377. });
  16378. }
  16379. element.removeAttribute(name);
  16380. // We void attribute values for unremovable "is" attributes
  16381. if (name === 'is') {
  16382. if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
  16383. try {
  16384. _forceRemove(element);
  16385. } catch (_) {}
  16386. } else {
  16387. try {
  16388. element.setAttribute(name, '');
  16389. } catch (_) {}
  16390. }
  16391. }
  16392. };
  16393. /**
  16394. * _initDocument
  16395. *
  16396. * @param dirty - a string of dirty markup
  16397. * @return a DOM, filled with the dirty markup
  16398. */
  16399. const _initDocument = function _initDocument(dirty) {
  16400. /* Create a HTML document */
  16401. let doc = null;
  16402. let leadingWhitespace = null;
  16403. if (FORCE_BODY) {
  16404. dirty = '<remove></remove>' + dirty;
  16405. } else {
  16406. /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */
  16407. const matches = stringMatch(dirty, /^[\r\n\t ]+/);
  16408. leadingWhitespace = matches && matches[0];
  16409. }
  16410. if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && NAMESPACE === HTML_NAMESPACE) {
  16411. // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
  16412. dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
  16413. }
  16414. const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
  16415. /*
  16416. * Use the DOMParser API by default, fallback later if needs be
  16417. * DOMParser not work for svg when has multiple root element.
  16418. */
  16419. if (NAMESPACE === HTML_NAMESPACE) {
  16420. try {
  16421. doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
  16422. } catch (_) {}
  16423. }
  16424. /* Use createHTMLDocument in case DOMParser is not available */
  16425. if (!doc || !doc.documentElement) {
  16426. doc = implementation.createDocument(NAMESPACE, 'template', null);
  16427. try {
  16428. doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload;
  16429. } catch (_) {
  16430. // Syntax error if dirtyPayload is invalid xml
  16431. }
  16432. }
  16433. const body = doc.body || doc.documentElement;
  16434. if (dirty && leadingWhitespace) {
  16435. body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);
  16436. }
  16437. /* Work on whole document or just its body */
  16438. if (NAMESPACE === HTML_NAMESPACE) {
  16439. return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
  16440. }
  16441. return WHOLE_DOCUMENT ? doc.documentElement : body;
  16442. };
  16443. /**
  16444. * Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document.
  16445. *
  16446. * @param root The root element or node to start traversing on.
  16447. * @return The created NodeIterator
  16448. */
  16449. const _createNodeIterator = function _createNodeIterator(root) {
  16450. return createNodeIterator.call(root.ownerDocument || root, root,
  16451. // eslint-disable-next-line no-bitwise
  16452. NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
  16453. };
  16454. /**
  16455. * _isClobbered
  16456. *
  16457. * @param element element to check for clobbering attacks
  16458. * @return true if clobbered, false if safe
  16459. */
  16460. const _isClobbered = function _isClobbered(element) {
  16461. return element instanceof HTMLFormElement && (typeof element.nodeName !== 'string' || typeof element.textContent !== 'string' || typeof element.removeChild !== 'function' || !(element.attributes instanceof NamedNodeMap) || typeof element.removeAttribute !== 'function' || typeof element.setAttribute !== 'function' || typeof element.namespaceURI !== 'string' || typeof element.insertBefore !== 'function' || typeof element.hasChildNodes !== 'function');
  16462. };
  16463. /**
  16464. * Checks whether the given object is a DOM node.
  16465. *
  16466. * @param value object to check whether it's a DOM node
  16467. * @return true is object is a DOM node
  16468. */
  16469. const _isNode = function _isNode(value) {
  16470. return typeof Node === 'function' && value instanceof Node;
  16471. };
  16472. function _executeHooks(hooks, currentNode, data) {
  16473. arrayForEach(hooks, hook => {
  16474. hook.call(DOMPurify, currentNode, data, CONFIG);
  16475. });
  16476. }
  16477. /**
  16478. * _sanitizeElements
  16479. *
  16480. * @protect nodeName
  16481. * @protect textContent
  16482. * @protect removeChild
  16483. * @param currentNode to check for permission to exist
  16484. * @return true if node was killed, false if left alive
  16485. */
  16486. const _sanitizeElements = function _sanitizeElements(currentNode) {
  16487. let content = null;
  16488. /* Execute a hook if present */
  16489. _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
  16490. /* Check if element is clobbered or can clobber */
  16491. if (_isClobbered(currentNode)) {
  16492. _forceRemove(currentNode);
  16493. return true;
  16494. }
  16495. /* Now let's check the element's type and name */
  16496. const tagName = transformCaseFunc(currentNode.nodeName);
  16497. /* Execute a hook if present */
  16498. _executeHooks(hooks.uponSanitizeElement, currentNode, {
  16499. tagName,
  16500. allowedTags: ALLOWED_TAGS
  16501. });
  16502. /* Detect mXSS attempts abusing namespace confusion */
  16503. if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w!]/g, currentNode.innerHTML) && regExpTest(/<[/\w!]/g, currentNode.textContent)) {
  16504. _forceRemove(currentNode);
  16505. return true;
  16506. }
  16507. /* Remove any occurrence of processing instructions */
  16508. if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
  16509. _forceRemove(currentNode);
  16510. return true;
  16511. }
  16512. /* Remove any kind of possibly harmful comments */
  16513. if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) {
  16514. _forceRemove(currentNode);
  16515. return true;
  16516. }
  16517. /* Remove element if anything forbids its presence */
  16518. if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
  16519. /* Check if we have a custom element to handle */
  16520. if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
  16521. if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
  16522. return false;
  16523. }
  16524. if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
  16525. return false;
  16526. }
  16527. }
  16528. /* Keep content except for bad-listed elements */
  16529. if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
  16530. const parentNode = getParentNode(currentNode) || currentNode.parentNode;
  16531. const childNodes = getChildNodes(currentNode) || currentNode.childNodes;
  16532. if (childNodes && parentNode) {
  16533. const childCount = childNodes.length;
  16534. for (let i = childCount - 1; i >= 0; --i) {
  16535. const childClone = cloneNode(childNodes[i], true);
  16536. childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
  16537. parentNode.insertBefore(childClone, getNextSibling(currentNode));
  16538. }
  16539. }
  16540. }
  16541. _forceRemove(currentNode);
  16542. return true;
  16543. }
  16544. /* Check whether element has a valid namespace */
  16545. if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
  16546. _forceRemove(currentNode);
  16547. return true;
  16548. }
  16549. /* Make sure that older browsers don't get fallback-tag mXSS */
  16550. if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) {
  16551. _forceRemove(currentNode);
  16552. return true;
  16553. }
  16554. /* Sanitize element content to be template-safe */
  16555. if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
  16556. /* Get the element's text content */
  16557. content = currentNode.textContent;
  16558. arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
  16559. content = stringReplace(content, expr, ' ');
  16560. });
  16561. if (currentNode.textContent !== content) {
  16562. arrayPush(DOMPurify.removed, {
  16563. element: currentNode.cloneNode()
  16564. });
  16565. currentNode.textContent = content;
  16566. }
  16567. }
  16568. /* Execute a hook if present */
  16569. _executeHooks(hooks.afterSanitizeElements, currentNode, null);
  16570. return false;
  16571. };
  16572. /**
  16573. * _isValidAttribute
  16574. *
  16575. * @param lcTag Lowercase tag name of containing element.
  16576. * @param lcName Lowercase attribute name.
  16577. * @param value Attribute value.
  16578. * @return Returns true if `value` is valid, otherwise false.
  16579. */
  16580. // eslint-disable-next-line complexity
  16581. const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
  16582. /* Make sure attribute cannot clobber */
  16583. if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
  16584. return false;
  16585. }
  16586. /* Allow valid data-* attributes: At least one character after "-"
  16587. (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
  16588. XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
  16589. We don't need to check the value; it's always URI safe. */
  16590. if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
  16591. if (
  16592. // First condition does a very basic check if a) it's basically a valid custom element tagname AND
  16593. // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
  16594. // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
  16595. _isBasicCustomElement(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)) ||
  16596. // Alternative, second condition checks if it's an `is`-attribute, AND
  16597. // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
  16598. lcName === 'is' && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))) ; else {
  16599. return false;
  16600. }
  16601. /* Check value is safe. First, is attr inert? If so, is safe */
  16602. } else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if (value) {
  16603. return false;
  16604. } else ;
  16605. return true;
  16606. };
  16607. /**
  16608. * _isBasicCustomElement
  16609. * checks if at least one dash is included in tagName, and it's not the first char
  16610. * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
  16611. *
  16612. * @param tagName name of the tag of the node to sanitize
  16613. * @returns Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
  16614. */
  16615. const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
  16616. return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT);
  16617. };
  16618. /**
  16619. * _sanitizeAttributes
  16620. *
  16621. * @protect attributes
  16622. * @protect nodeName
  16623. * @protect removeAttribute
  16624. * @protect setAttribute
  16625. *
  16626. * @param currentNode to sanitize
  16627. */
  16628. const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
  16629. /* Execute a hook if present */
  16630. _executeHooks(hooks.beforeSanitizeAttributes, currentNode, null);
  16631. const {
  16632. attributes
  16633. } = currentNode;
  16634. /* Check if we have attributes; if not we might have a text node */
  16635. if (!attributes || _isClobbered(currentNode)) {
  16636. return;
  16637. }
  16638. const hookEvent = {
  16639. attrName: '',
  16640. attrValue: '',
  16641. keepAttr: true,
  16642. allowedAttributes: ALLOWED_ATTR,
  16643. forceKeepAttr: undefined
  16644. };
  16645. let l = attributes.length;
  16646. /* Go backwards over all attributes; safely remove bad ones */
  16647. while (l--) {
  16648. const attr = attributes[l];
  16649. const {
  16650. name,
  16651. namespaceURI,
  16652. value: attrValue
  16653. } = attr;
  16654. const lcName = transformCaseFunc(name);
  16655. const initValue = attrValue;
  16656. let value = name === 'value' ? initValue : stringTrim(initValue);
  16657. /* Execute a hook if present */
  16658. hookEvent.attrName = lcName;
  16659. hookEvent.attrValue = value;
  16660. hookEvent.keepAttr = true;
  16661. hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
  16662. _executeHooks(hooks.uponSanitizeAttribute, currentNode, hookEvent);
  16663. value = hookEvent.attrValue;
  16664. /* Full DOM Clobbering protection via namespace isolation,
  16665. * Prefix id and name attributes with `user-content-`
  16666. */
  16667. if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
  16668. // Remove the attribute with this value
  16669. _removeAttribute(name, currentNode);
  16670. // Prefix the value and later re-create the attribute with the sanitized value
  16671. value = SANITIZE_NAMED_PROPS_PREFIX + value;
  16672. }
  16673. /* Work around a security issue with comments inside attributes */
  16674. if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title)/i, value)) {
  16675. _removeAttribute(name, currentNode);
  16676. continue;
  16677. }
  16678. /* Did the hooks approve of the attribute? */
  16679. if (hookEvent.forceKeepAttr) {
  16680. continue;
  16681. }
  16682. /* Did the hooks approve of the attribute? */
  16683. if (!hookEvent.keepAttr) {
  16684. _removeAttribute(name, currentNode);
  16685. continue;
  16686. }
  16687. /* Work around a security issue in jQuery 3.0 */
  16688. if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
  16689. _removeAttribute(name, currentNode);
  16690. continue;
  16691. }
  16692. /* Sanitize attribute content to be template-safe */
  16693. if (SAFE_FOR_TEMPLATES) {
  16694. arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
  16695. value = stringReplace(value, expr, ' ');
  16696. });
  16697. }
  16698. /* Is `value` valid for this attribute? */
  16699. const lcTag = transformCaseFunc(currentNode.nodeName);
  16700. if (!_isValidAttribute(lcTag, lcName, value)) {
  16701. _removeAttribute(name, currentNode);
  16702. continue;
  16703. }
  16704. /* Handle attributes that require Trusted Types */
  16705. if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') {
  16706. if (namespaceURI) ; else {
  16707. switch (trustedTypes.getAttributeType(lcTag, lcName)) {
  16708. case 'TrustedHTML':
  16709. {
  16710. value = trustedTypesPolicy.createHTML(value);
  16711. break;
  16712. }
  16713. case 'TrustedScriptURL':
  16714. {
  16715. value = trustedTypesPolicy.createScriptURL(value);
  16716. break;
  16717. }
  16718. }
  16719. }
  16720. }
  16721. /* Handle invalid data-* attribute set by try-catching it */
  16722. if (value !== initValue) {
  16723. try {
  16724. if (namespaceURI) {
  16725. currentNode.setAttributeNS(namespaceURI, name, value);
  16726. } else {
  16727. /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
  16728. currentNode.setAttribute(name, value);
  16729. }
  16730. if (_isClobbered(currentNode)) {
  16731. _forceRemove(currentNode);
  16732. } else {
  16733. arrayPop(DOMPurify.removed);
  16734. }
  16735. } catch (_) {
  16736. _removeAttribute(name, currentNode);
  16737. }
  16738. }
  16739. }
  16740. /* Execute a hook if present */
  16741. _executeHooks(hooks.afterSanitizeAttributes, currentNode, null);
  16742. };
  16743. /**
  16744. * _sanitizeShadowDOM
  16745. *
  16746. * @param fragment to iterate over recursively
  16747. */
  16748. const _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
  16749. let shadowNode = null;
  16750. const shadowIterator = _createNodeIterator(fragment);
  16751. /* Execute a hook if present */
  16752. _executeHooks(hooks.beforeSanitizeShadowDOM, fragment, null);
  16753. while (shadowNode = shadowIterator.nextNode()) {
  16754. /* Execute a hook if present */
  16755. _executeHooks(hooks.uponSanitizeShadowNode, shadowNode, null);
  16756. /* Sanitize tags and elements */
  16757. _sanitizeElements(shadowNode);
  16758. /* Check attributes next */
  16759. _sanitizeAttributes(shadowNode);
  16760. /* Deep shadow DOM detected */
  16761. if (shadowNode.content instanceof DocumentFragment) {
  16762. _sanitizeShadowDOM(shadowNode.content);
  16763. }
  16764. }
  16765. /* Execute a hook if present */
  16766. _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
  16767. };
  16768. // eslint-disable-next-line complexity
  16769. DOMPurify.sanitize = function (dirty) {
  16770. let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  16771. let body = null;
  16772. let importedNode = null;
  16773. let currentNode = null;
  16774. let returnNode = null;
  16775. /* Make sure we have a string to sanitize.
  16776. DO NOT return early, as this will return the wrong type if
  16777. the user has requested a DOM object rather than a string */
  16778. IS_EMPTY_INPUT = !dirty;
  16779. if (IS_EMPTY_INPUT) {
  16780. dirty = '<!-->';
  16781. }
  16782. /* Stringify, in case dirty is an object */
  16783. if (typeof dirty !== 'string' && !_isNode(dirty)) {
  16784. if (typeof dirty.toString === 'function') {
  16785. dirty = dirty.toString();
  16786. if (typeof dirty !== 'string') {
  16787. throw typeErrorCreate('dirty is not a string, aborting');
  16788. }
  16789. } else {
  16790. throw typeErrorCreate('toString is not a function');
  16791. }
  16792. }
  16793. /* Return dirty HTML if DOMPurify cannot run */
  16794. if (!DOMPurify.isSupported) {
  16795. return dirty;
  16796. }
  16797. /* Assign config vars */
  16798. if (!SET_CONFIG) {
  16799. _parseConfig(cfg);
  16800. }
  16801. /* Clean up removed elements */
  16802. DOMPurify.removed = [];
  16803. /* Check if dirty is correctly typed for IN_PLACE */
  16804. if (typeof dirty === 'string') {
  16805. IN_PLACE = false;
  16806. }
  16807. if (IN_PLACE) {
  16808. /* Do some early pre-sanitization to avoid unsafe root nodes */
  16809. if (dirty.nodeName) {
  16810. const tagName = transformCaseFunc(dirty.nodeName);
  16811. if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
  16812. throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
  16813. }
  16814. }
  16815. } else if (dirty instanceof Node) {
  16816. /* If dirty is a DOM element, append to an empty document to avoid
  16817. elements being stripped by the parser */
  16818. body = _initDocument('<!---->');
  16819. importedNode = body.ownerDocument.importNode(dirty, true);
  16820. if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === 'BODY') {
  16821. /* Node is already a body, use as is */
  16822. body = importedNode;
  16823. } else if (importedNode.nodeName === 'HTML') {
  16824. body = importedNode;
  16825. } else {
  16826. // eslint-disable-next-line unicorn/prefer-dom-node-append
  16827. body.appendChild(importedNode);
  16828. }
  16829. } else {
  16830. /* Exit directly if we have nothing to do */
  16831. if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
  16832. // eslint-disable-next-line unicorn/prefer-includes
  16833. dirty.indexOf('<') === -1) {
  16834. return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
  16835. }
  16836. /* Initialize the document to work on */
  16837. body = _initDocument(dirty);
  16838. /* Check we have a DOM node from the data */
  16839. if (!body) {
  16840. return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';
  16841. }
  16842. }
  16843. /* Remove first element node (ours) if FORCE_BODY is set */
  16844. if (body && FORCE_BODY) {
  16845. _forceRemove(body.firstChild);
  16846. }
  16847. /* Get node iterator */
  16848. const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);
  16849. /* Now start iterating over the created document */
  16850. while (currentNode = nodeIterator.nextNode()) {
  16851. /* Sanitize tags and elements */
  16852. _sanitizeElements(currentNode);
  16853. /* Check attributes next */
  16854. _sanitizeAttributes(currentNode);
  16855. /* Shadow DOM detected, sanitize it */
  16856. if (currentNode.content instanceof DocumentFragment) {
  16857. _sanitizeShadowDOM(currentNode.content);
  16858. }
  16859. }
  16860. /* If we sanitized `dirty` in-place, return it. */
  16861. if (IN_PLACE) {
  16862. return dirty;
  16863. }
  16864. /* Return sanitized string or DOM */
  16865. if (RETURN_DOM) {
  16866. if (RETURN_DOM_FRAGMENT) {
  16867. returnNode = createDocumentFragment.call(body.ownerDocument);
  16868. while (body.firstChild) {
  16869. // eslint-disable-next-line unicorn/prefer-dom-node-append
  16870. returnNode.appendChild(body.firstChild);
  16871. }
  16872. } else {
  16873. returnNode = body;
  16874. }
  16875. if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) {
  16876. /*
  16877. AdoptNode() is not used because internal state is not reset
  16878. (e.g. the past names map of a HTMLFormElement), this is safe
  16879. in theory but we would rather not risk another attack vector.
  16880. The state that is cloned by importNode() is explicitly defined
  16881. by the specs.
  16882. */
  16883. returnNode = importNode.call(originalDocument, returnNode, true);
  16884. }
  16885. return returnNode;
  16886. }
  16887. let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
  16888. /* Serialize doctype if allowed */
  16889. if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {
  16890. serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML;
  16891. }
  16892. /* Sanitize final string template-safe */
  16893. if (SAFE_FOR_TEMPLATES) {
  16894. arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
  16895. serializedHTML = stringReplace(serializedHTML, expr, ' ');
  16896. });
  16897. }
  16898. return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
  16899. };
  16900. DOMPurify.setConfig = function () {
  16901. let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
  16902. _parseConfig(cfg);
  16903. SET_CONFIG = true;
  16904. };
  16905. DOMPurify.clearConfig = function () {
  16906. CONFIG = null;
  16907. SET_CONFIG = false;
  16908. };
  16909. DOMPurify.isValidAttribute = function (tag, attr, value) {
  16910. /* Initialize shared config vars if necessary. */
  16911. if (!CONFIG) {
  16912. _parseConfig({});
  16913. }
  16914. const lcTag = transformCaseFunc(tag);
  16915. const lcName = transformCaseFunc(attr);
  16916. return _isValidAttribute(lcTag, lcName, value);
  16917. };
  16918. DOMPurify.addHook = function (entryPoint, hookFunction) {
  16919. if (typeof hookFunction !== 'function') {
  16920. return;
  16921. }
  16922. arrayPush(hooks[entryPoint], hookFunction);
  16923. };
  16924. DOMPurify.removeHook = function (entryPoint, hookFunction) {
  16925. if (hookFunction !== undefined) {
  16926. const index = arrayLastIndexOf(hooks[entryPoint], hookFunction);
  16927. return index === -1 ? undefined : arraySplice(hooks[entryPoint], index, 1)[0];
  16928. }
  16929. return arrayPop(hooks[entryPoint]);
  16930. };
  16931. DOMPurify.removeHooks = function (entryPoint) {
  16932. hooks[entryPoint] = [];
  16933. };
  16934. DOMPurify.removeAllHooks = function () {
  16935. hooks = _createHooksMap();
  16936. };
  16937. return DOMPurify;
  16938. }
  16939. var purify = createDOMPurify();
  16940. const sanitizeHtmlString = (html) => purify().sanitize(html);
  16941. var global$6 = tinymce.util.Tools.resolve('tinymce.util.I18n');
  16942. // Icons that need to be transformed in RTL
  16943. const rtlTransform = {
  16944. 'indent': true,
  16945. 'outdent': true,
  16946. 'table-insert-column-after': true,
  16947. 'table-insert-column-before': true,
  16948. 'paste-column-after': true,
  16949. 'paste-column-before': true,
  16950. 'unordered-list': true,
  16951. 'list-bull-circle': true,
  16952. 'list-bull-disc': true,
  16953. 'list-bull-default': true,
  16954. 'list-bull-square': true
  16955. };
  16956. const defaultIconName = 'temporary-placeholder';
  16957. const defaultIcon = (icons) => () => get$h(icons, defaultIconName).getOr('!not found!');
  16958. const getIconName = (name, icons) => {
  16959. const lcName = name.toLowerCase();
  16960. // If in rtl mode then try to see if we have a rtl icon to use instead
  16961. if (global$6.isRtl()) {
  16962. const rtlName = ensureTrailing(lcName, '-rtl');
  16963. return has$2(icons, rtlName) ? rtlName : lcName;
  16964. }
  16965. else {
  16966. return lcName;
  16967. }
  16968. };
  16969. const lookupIcon = (name, icons) => get$h(icons, getIconName(name, icons));
  16970. const get = (name, iconProvider) => {
  16971. const icons = iconProvider();
  16972. return lookupIcon(name, icons).getOrThunk(defaultIcon(icons));
  16973. };
  16974. const getOr = (name, iconProvider, fallbackIcon) => {
  16975. const icons = iconProvider();
  16976. return lookupIcon(name, icons).or(fallbackIcon).getOrThunk(defaultIcon(icons));
  16977. };
  16978. const needsRtlTransform = (iconName) => global$6.isRtl() ? has$2(rtlTransform, iconName) : false;
  16979. const addFocusableBehaviour = () => config('add-focusable', [
  16980. runOnAttached((comp) => {
  16981. // set focusable=false on SVGs to prevent focusing the toolbar when tabbing into the editor
  16982. child(comp.element, 'svg').each((svg) => set$9(svg, 'focusable', 'false'));
  16983. })
  16984. ]);
  16985. const renderIcon$3 = (spec, iconName, icons, fallbackIcon) => {
  16986. var _a, _b, _c;
  16987. // If RTL, add the flip icon class if the icon doesn't have a `-rtl` icon available.
  16988. const rtlIconClasses = needsRtlTransform(iconName) ? ['tox-icon--flip'] : [];
  16989. const iconHtml = get$h(icons, getIconName(iconName, icons)).or(fallbackIcon).getOrThunk(defaultIcon(icons));
  16990. return {
  16991. dom: {
  16992. tag: spec.tag,
  16993. attributes: (_a = spec.attributes) !== null && _a !== void 0 ? _a : {},
  16994. classes: spec.classes.concat(rtlIconClasses),
  16995. innerHtml: iconHtml
  16996. },
  16997. behaviours: derive$1([
  16998. ...(_b = spec.behaviours) !== null && _b !== void 0 ? _b : [],
  16999. addFocusableBehaviour()
  17000. ]),
  17001. eventOrder: (_c = spec.eventOrder) !== null && _c !== void 0 ? _c : {}
  17002. };
  17003. };
  17004. const render$4 = (iconName, spec, iconProvider, fallbackIcon = Optional.none()) => renderIcon$3(spec, iconName, iconProvider(), fallbackIcon);
  17005. const renderFirst = (iconNames, spec, iconProvider) => {
  17006. const icons = iconProvider();
  17007. const iconName = find$5(iconNames, (name) => has$2(icons, getIconName(name, icons)));
  17008. return renderIcon$3(spec, iconName.getOr(defaultIconName), icons, Optional.none());
  17009. };
  17010. const notificationIconMap = {
  17011. success: 'checkmark',
  17012. error: 'warning',
  17013. err: 'error',
  17014. warning: 'warning',
  17015. warn: 'warning',
  17016. info: 'info'
  17017. };
  17018. const factory$4 = (detail) => {
  17019. // For using the alert banner as a standalone banner
  17020. const notificationTextId = generate$6('notification-text');
  17021. const memBannerText = record({
  17022. dom: fromHtml(`<p id=${notificationTextId}>${sanitizeHtmlString(detail.backstageProvider.translate(detail.text))}</p>`),
  17023. behaviours: derive$1([
  17024. Replacing.config({})
  17025. ])
  17026. });
  17027. const renderPercentBar = (percent) => ({
  17028. dom: {
  17029. tag: 'div',
  17030. classes: ['tox-bar'],
  17031. styles: {
  17032. width: `${percent}%`
  17033. }
  17034. }
  17035. });
  17036. const renderPercentText = (percent) => ({
  17037. dom: {
  17038. tag: 'div',
  17039. classes: ['tox-text'],
  17040. innerHtml: `${percent}%`
  17041. }
  17042. });
  17043. const memBannerProgress = record({
  17044. dom: {
  17045. tag: 'div',
  17046. classes: detail.progress ? ['tox-progress-bar', 'tox-progress-indicator'] : ['tox-progress-bar']
  17047. },
  17048. components: [
  17049. {
  17050. dom: {
  17051. tag: 'div',
  17052. classes: ['tox-bar-container']
  17053. },
  17054. components: [
  17055. renderPercentBar(0)
  17056. ]
  17057. },
  17058. renderPercentText(0)
  17059. ],
  17060. behaviours: derive$1([
  17061. Replacing.config({})
  17062. ])
  17063. });
  17064. const updateProgress = (comp, percent) => {
  17065. if (comp.getSystem().isConnected()) {
  17066. memBannerProgress.getOpt(comp).each((progress) => {
  17067. Replacing.set(progress, [
  17068. {
  17069. dom: {
  17070. tag: 'div',
  17071. classes: ['tox-bar-container']
  17072. },
  17073. components: [
  17074. renderPercentBar(percent)
  17075. ]
  17076. },
  17077. renderPercentText(percent)
  17078. ]);
  17079. });
  17080. }
  17081. };
  17082. const updateText = (comp, text) => {
  17083. if (comp.getSystem().isConnected()) {
  17084. const banner = memBannerText.get(comp);
  17085. Replacing.set(banner, [
  17086. text$2(text)
  17087. ]);
  17088. }
  17089. };
  17090. const apis = {
  17091. updateProgress,
  17092. updateText
  17093. };
  17094. const iconChoices = flatten([
  17095. detail.icon.toArray(),
  17096. [detail.level],
  17097. Optional.from(notificationIconMap[detail.level]).toArray()
  17098. ]);
  17099. const memButton = record(Button.sketch({
  17100. dom: {
  17101. tag: 'button',
  17102. classes: ['tox-notification__dismiss', 'tox-button', 'tox-button--naked', 'tox-button--icon'],
  17103. attributes: {
  17104. 'aria-label': detail.backstageProvider.translate('Close')
  17105. }
  17106. },
  17107. components: [
  17108. render$4('close', {
  17109. tag: 'span',
  17110. classes: ['tox-icon'],
  17111. }, detail.iconProvider)
  17112. ],
  17113. buttonBehaviours: derive$1([
  17114. Tabstopping.config({}),
  17115. Tooltipping.config({
  17116. ...detail.backstageProvider.tooltips.getConfig({
  17117. tooltipText: detail.backstageProvider.translate('Close')
  17118. })
  17119. })
  17120. ]),
  17121. action: (comp) => {
  17122. detail.onAction(comp);
  17123. }
  17124. }));
  17125. const notificationIconSpec = renderFirst(iconChoices, { tag: 'div', classes: ['tox-notification__icon'] }, detail.iconProvider);
  17126. const notificationBodySpec = {
  17127. dom: {
  17128. tag: 'div',
  17129. classes: ['tox-notification__body']
  17130. },
  17131. components: [
  17132. memBannerText.asSpec()
  17133. ],
  17134. behaviours: derive$1([
  17135. Replacing.config({})
  17136. ])
  17137. };
  17138. const components = [notificationIconSpec, notificationBodySpec];
  17139. return {
  17140. uid: detail.uid,
  17141. dom: {
  17142. tag: 'div',
  17143. attributes: {
  17144. 'role': 'alert',
  17145. 'aria-labelledby': notificationTextId
  17146. },
  17147. classes: ['tox-notification', 'tox-notification--in', `tox-notification--${detail.level}`],
  17148. },
  17149. behaviours: derive$1([
  17150. Tabstopping.config({}),
  17151. Focusing.config({}),
  17152. Keying.config({
  17153. mode: 'special',
  17154. onEscape: (comp) => {
  17155. detail.onAction(comp);
  17156. return Optional.some(true);
  17157. }
  17158. })
  17159. ]),
  17160. components: components
  17161. .concat(detail.progress ? [memBannerProgress.asSpec()] : [])
  17162. .concat([memButton.asSpec()]),
  17163. apis
  17164. };
  17165. };
  17166. const Notification = single({
  17167. name: 'Notification',
  17168. factory: factory$4,
  17169. configFields: [
  17170. defaultedStringEnum('level', 'info', ['success', 'error', 'warning', 'warn', 'info']),
  17171. required$1('progress'),
  17172. option$3('icon'),
  17173. required$1('onAction'),
  17174. required$1('text'),
  17175. required$1('iconProvider'),
  17176. required$1('backstageProvider'),
  17177. ],
  17178. apis: {
  17179. updateProgress: (apis, comp, percent) => {
  17180. apis.updateProgress(comp, percent);
  17181. },
  17182. updateText: (apis, comp, text) => {
  17183. apis.updateText(comp, text);
  17184. }
  17185. }
  17186. });
  17187. var NotificationManagerImpl = (editor, extras, uiMothership, notificationRegion) => {
  17188. const sharedBackstage = extras.backstage.shared;
  17189. const getBoundsContainer = () => SugarElement.fromDom(editor.queryCommandValue('ToggleView') === '' ? editor.getContentAreaContainer() : editor.getContainer());
  17190. const getBounds = () => {
  17191. const contentArea = box$1(getBoundsContainer());
  17192. return Optional.some(contentArea);
  17193. };
  17194. const clampComponentsToBounds = (components) => {
  17195. getBounds().each((bounds) => {
  17196. each$1(components, (comp) => {
  17197. remove$6(comp.element, 'width');
  17198. if (get$c(comp.element) > bounds.width) {
  17199. set$7(comp.element, 'width', bounds.width + 'px');
  17200. }
  17201. });
  17202. });
  17203. };
  17204. const open = (settings, closeCallback, isEditorOrUIFocused) => {
  17205. const close = () => {
  17206. const removeNotificationAndReposition = (region) => {
  17207. Replacing.remove(region, notification);
  17208. reposition();
  17209. };
  17210. const manageRegionVisibility = (region, editorOrUiFocused) => {
  17211. if (children(region.element).length === 0) {
  17212. handleEmptyRegion(region, editorOrUiFocused);
  17213. }
  17214. else {
  17215. handleRegionWithChildren(region, editorOrUiFocused);
  17216. }
  17217. };
  17218. const handleEmptyRegion = (region, editorOrUIFocused) => {
  17219. InlineView.hide(region);
  17220. notificationRegion.clear();
  17221. if (editorOrUIFocused) {
  17222. editor.focus();
  17223. }
  17224. };
  17225. const handleRegionWithChildren = (region, editorOrUIFocused) => {
  17226. if (editorOrUIFocused) {
  17227. Keying.focusIn(region);
  17228. }
  17229. };
  17230. notificationRegion.on((region) => {
  17231. closeCallback();
  17232. const editorOrUIFocused = isEditorOrUIFocused();
  17233. removeNotificationAndReposition(region);
  17234. manageRegionVisibility(region, editorOrUIFocused);
  17235. });
  17236. };
  17237. const shouldApplyDocking = () => !isStickyToolbar(editor) || !sharedBackstage.header.isPositionedAtTop();
  17238. const notification = build$1(Notification.sketch({
  17239. text: settings.text,
  17240. level: contains$2(['success', 'error', 'warning', 'warn', 'info'], settings.type) ? settings.type : undefined,
  17241. progress: settings.progressBar === true,
  17242. icon: settings.icon,
  17243. onAction: close,
  17244. iconProvider: sharedBackstage.providers.icons,
  17245. backstageProvider: sharedBackstage.providers,
  17246. }));
  17247. if (!notificationRegion.isSet()) {
  17248. const notificationWrapper = build$1(InlineView.sketch({
  17249. dom: {
  17250. tag: 'div',
  17251. classes: ['tox-notifications-container'],
  17252. attributes: {
  17253. 'aria-label': 'Notifications',
  17254. 'role': 'region'
  17255. }
  17256. },
  17257. lazySink: sharedBackstage.getSink,
  17258. fireDismissalEventInstead: {},
  17259. ...sharedBackstage.header.isPositionedAtTop() ? {} : { fireRepositionEventInstead: {} },
  17260. inlineBehaviours: derive$1([
  17261. Keying.config({
  17262. mode: 'cyclic',
  17263. selector: '.tox-notification, .tox-notification a, .tox-notification button',
  17264. }),
  17265. Replacing.config({}),
  17266. ...(shouldApplyDocking()
  17267. ? [
  17268. Docking.config({
  17269. contextual: {
  17270. lazyContext: () => Optional.some(box$1(getBoundsContainer())),
  17271. fadeInClass: 'tox-notification-container-dock-fadein',
  17272. fadeOutClass: 'tox-notification-container-dock-fadeout',
  17273. transitionClass: 'tox-notification-container-dock-transition'
  17274. },
  17275. modes: ['top'],
  17276. lazyViewport: (comp) => {
  17277. const optScrollingContext = detectWhenSplitUiMode(editor, comp.element);
  17278. return optScrollingContext
  17279. .map((sc) => {
  17280. const combinedBounds = getBoundsFrom(sc);
  17281. return {
  17282. bounds: combinedBounds,
  17283. optScrollEnv: Optional.some({
  17284. currentScrollTop: sc.element.dom.scrollTop,
  17285. scrollElmTop: absolute$3(sc.element).top
  17286. })
  17287. };
  17288. }).getOrThunk(() => ({
  17289. bounds: win(),
  17290. optScrollEnv: Optional.none()
  17291. }));
  17292. }
  17293. })
  17294. ] : [])
  17295. ])
  17296. }));
  17297. const notificationSpec = premade(notification);
  17298. const anchorOverrides = {
  17299. maxHeightFunction: expandable$1()
  17300. };
  17301. const anchor = {
  17302. ...sharedBackstage.anchors.banner(),
  17303. overrides: anchorOverrides
  17304. };
  17305. notificationRegion.set(notificationWrapper);
  17306. uiMothership.add(notificationWrapper);
  17307. InlineView.showWithinBounds(notificationWrapper, notificationSpec, { anchor }, getBounds);
  17308. }
  17309. else {
  17310. const notificationSpec = premade(notification);
  17311. notificationRegion.on((notificationWrapper) => {
  17312. Replacing.append(notificationWrapper, notificationSpec);
  17313. InlineView.reposition(notificationWrapper);
  17314. if (notification.hasConfigured(Docking)) {
  17315. Docking.refresh(notificationWrapper);
  17316. }
  17317. clampComponentsToBounds(notificationWrapper.components());
  17318. });
  17319. }
  17320. if (isNumber(settings.timeout) && settings.timeout > 0) {
  17321. global$a.setEditorTimeout(editor, () => {
  17322. close();
  17323. }, settings.timeout);
  17324. }
  17325. const reposition = () => {
  17326. notificationRegion.on((region) => {
  17327. InlineView.reposition(region);
  17328. if (region.hasConfigured(Docking)) {
  17329. Docking.refresh(region);
  17330. }
  17331. clampComponentsToBounds(region.components());
  17332. });
  17333. };
  17334. const thisNotification = {
  17335. close,
  17336. reposition,
  17337. text: (nuText) => {
  17338. // check if component is still mounted
  17339. Notification.updateText(notification, nuText);
  17340. },
  17341. settings,
  17342. getEl: () => notification.element.dom,
  17343. progressBar: {
  17344. value: (percent) => {
  17345. Notification.updateProgress(notification, percent);
  17346. }
  17347. }
  17348. };
  17349. return thisNotification;
  17350. };
  17351. const close = (notification) => {
  17352. notification.close();
  17353. };
  17354. const getArgs = (notification) => {
  17355. return notification.settings;
  17356. };
  17357. return {
  17358. open,
  17359. close,
  17360. getArgs
  17361. };
  17362. };
  17363. const setup$c = (api, editor) => {
  17364. const redirectKeyToItem = (item, e) => {
  17365. emitWith(item, keydown(), { raw: e });
  17366. };
  17367. const getItem = () => api.getMenu().bind(Highlighting.getHighlighted);
  17368. editor.on('keydown', (e) => {
  17369. const keyCode = e.which;
  17370. // If the autocompleter isn't activated then do nothing
  17371. if (!api.isActive()) {
  17372. return;
  17373. }
  17374. if (api.isMenuOpen()) {
  17375. // Pressing <enter> executes any item currently selected, or does nothing
  17376. if (keyCode === 13) {
  17377. getItem().each(emitExecute);
  17378. e.preventDefault();
  17379. // Pressing <down> either highlights the first option, or moves down the menu
  17380. }
  17381. else if (keyCode === 40) {
  17382. getItem().fold(
  17383. // No current item, so highlight the first one
  17384. () => {
  17385. api.getMenu().each(Highlighting.highlightFirst);
  17386. },
  17387. // There is a current item, so move down in the menu
  17388. (item) => {
  17389. redirectKeyToItem(item, e);
  17390. });
  17391. e.preventDefault();
  17392. e.stopImmediatePropagation();
  17393. // Pressing <up>, <left>, <right> gets redirected to the selected item
  17394. }
  17395. else if (keyCode === 37 || keyCode === 38 || keyCode === 39) {
  17396. getItem().each((item) => {
  17397. redirectKeyToItem(item, e);
  17398. e.preventDefault();
  17399. e.stopImmediatePropagation();
  17400. });
  17401. }
  17402. }
  17403. else {
  17404. // Pressing <enter>, <down> or <up> closes the autocompleter when it's active but the menu isn't open
  17405. if (keyCode === 13 || keyCode === 38 || keyCode === 40) {
  17406. api.cancelIfNecessary();
  17407. }
  17408. }
  17409. });
  17410. editor.on('NodeChange', () => {
  17411. // Close if active, not in the middle of an onAction callback and we're no longer inside the autocompleter span
  17412. if (api.isActive() && !api.isProcessingAction() && !editor.queryCommandState('mceAutoCompleterInRange')) {
  17413. api.cancelIfNecessary();
  17414. }
  17415. });
  17416. };
  17417. const AutocompleterEditorEvents = {
  17418. setup: setup$c
  17419. };
  17420. var ItemResponse;
  17421. (function (ItemResponse) {
  17422. ItemResponse[ItemResponse["CLOSE_ON_EXECUTE"] = 0] = "CLOSE_ON_EXECUTE";
  17423. ItemResponse[ItemResponse["BUBBLE_TO_SANDBOX"] = 1] = "BUBBLE_TO_SANDBOX";
  17424. })(ItemResponse || (ItemResponse = {}));
  17425. var ItemResponse$1 = ItemResponse;
  17426. const navClass = 'tox-menu-nav__js';
  17427. const selectableClass = 'tox-collection__item';
  17428. const colorClass = 'tox-swatch';
  17429. const presetClasses = {
  17430. normal: navClass,
  17431. color: colorClass
  17432. };
  17433. const tickedClass = 'tox-collection__item--enabled';
  17434. const groupHeadingClass = 'tox-collection__group-heading';
  17435. const iconClass = 'tox-collection__item-icon';
  17436. const imageClass = 'tox-collection__item-image';
  17437. const imageSelectorClasll = 'tox-collection__item-image-selector';
  17438. const textClass = 'tox-collection__item-label';
  17439. const accessoryClass = 'tox-collection__item-accessory';
  17440. const caretClass = 'tox-collection__item-caret';
  17441. const checkmarkClass = 'tox-collection__item-checkmark';
  17442. const activeClass = 'tox-collection__item--active';
  17443. const containerClass = 'tox-collection__item-container';
  17444. const containerColumnClass = 'tox-collection__item-container--column';
  17445. const containerRowClass = 'tox-collection__item-container--row';
  17446. const containerAlignRightClass = 'tox-collection__item-container--align-right';
  17447. const containerAlignLeftClass = 'tox-collection__item-container--align-left';
  17448. const containerValignTopClass = 'tox-collection__item-container--valign-top';
  17449. const containerValignMiddleClass = 'tox-collection__item-container--valign-middle';
  17450. const containerValignBottomClass = 'tox-collection__item-container--valign-bottom';
  17451. const classForPreset = (presets) => get$h(presetClasses, presets).getOr(navClass);
  17452. const forMenu = (presets) => {
  17453. if (presets === 'color') {
  17454. return 'tox-swatches';
  17455. }
  17456. else {
  17457. return 'tox-menu';
  17458. }
  17459. };
  17460. const classes = (presets) => ({
  17461. backgroundMenu: 'tox-background-menu',
  17462. selectedMenu: 'tox-selected-menu',
  17463. selectedItem: 'tox-collection__item--active',
  17464. hasIcons: 'tox-menu--has-icons',
  17465. menu: forMenu(presets),
  17466. tieredMenu: 'tox-tiered-menu'
  17467. });
  17468. const markers = (presets) => {
  17469. const menuClasses = classes(presets);
  17470. return {
  17471. backgroundMenu: menuClasses.backgroundMenu,
  17472. selectedMenu: menuClasses.selectedMenu,
  17473. menu: menuClasses.menu,
  17474. selectedItem: menuClasses.selectedItem,
  17475. item: classForPreset(presets)
  17476. };
  17477. };
  17478. const dom = (hasIcons, columns, presets) => {
  17479. const menuClasses = classes(presets);
  17480. return {
  17481. tag: 'div',
  17482. classes: flatten([
  17483. [menuClasses.menu, `tox-menu-${columns}-column`],
  17484. hasIcons ? [menuClasses.hasIcons] : []
  17485. ])
  17486. };
  17487. };
  17488. const components = [
  17489. Menu.parts.items({})
  17490. ];
  17491. // NOTE: Up to here.
  17492. const part = (hasIcons, columns, presets) => {
  17493. const menuClasses = classes(presets);
  17494. const d = {
  17495. tag: 'div',
  17496. classes: flatten([
  17497. [menuClasses.tieredMenu]
  17498. ])
  17499. };
  17500. return {
  17501. dom: d,
  17502. markers: markers(presets)
  17503. };
  17504. };
  17505. // This event is triggered by a menu item from a dropdown when it wants the
  17506. // dropdown to refetch its contents based on a search string.
  17507. const refetchTriggerEvent = generate$6('refetch-trigger-event');
  17508. // This event is triggerd by a menu item from a dropdown, when it wants to
  17509. // redispatch that event to the currently active item of that dropdown menu. It will
  17510. // be used in situations where the event should be firing on the item with fake focus,
  17511. // but instead it is firing on the item with real focus (e.g of real focus:
  17512. // menu search field)
  17513. const redirectMenuItemInteractionEvent = generate$6('redirect-menu-item-interaction');
  17514. // This is not stored in ItemClasses, because the searcher is not actually
  17515. // contained within items. It isn't part of their navigation, and it
  17516. // isn't maintained by menus. It is just part of the first menu, but
  17517. // not its items.
  17518. const menuSearcherClass = 'tox-menu__searcher';
  17519. // Ideally, we'd be using mementos to find it again, but we'd need to pass
  17520. // that memento onto the dropdown, which isn't going to have it. Especially,
  17521. // because the dropdown isn't responsible for putting this searcher component
  17522. // into the menu, NestedMenus is.
  17523. const findWithinSandbox = (sandboxComp) => {
  17524. return descendant(sandboxComp.element, `.${menuSearcherClass}`).bind((inputElem) => sandboxComp.getSystem().getByDom(inputElem).toOptional());
  17525. };
  17526. // There is nothing sandbox-specific about this code. It just needs to be
  17527. // a container that wraps the search field.
  17528. const findWithinMenu = findWithinSandbox;
  17529. const restoreState = (inputComp, searcherState) => {
  17530. Representing.setValue(inputComp, searcherState.fetchPattern);
  17531. inputComp.element.dom.selectionStart = searcherState.selectionStart;
  17532. inputComp.element.dom.selectionEnd = searcherState.selectionEnd;
  17533. };
  17534. const saveState = (inputComp) => {
  17535. const fetchPattern = Representing.getValue(inputComp);
  17536. const selectionStart = inputComp.element.dom.selectionStart;
  17537. const selectionEnd = inputComp.element.dom.selectionEnd;
  17538. return {
  17539. fetchPattern,
  17540. selectionStart,
  17541. selectionEnd
  17542. };
  17543. };
  17544. // Make sure there is ARIA communicating the currently active item in the results.
  17545. const setActiveDescendant = (inputComp, active) => {
  17546. getOpt(active.element, 'id')
  17547. .each((id) => set$9(inputComp.element, 'aria-activedescendant', id));
  17548. };
  17549. const renderMenuSearcher = (spec) => {
  17550. const handleByBrowser = (comp, se) => {
  17551. // We "cut" this event, so that the browser still handles it, but it is not processed
  17552. // by any of the above alloy components. We could also do this by stopping propagation,
  17553. // but not preventing default, but it's probably good to allow some overarching thing
  17554. // in the DOM (outside of alloy) to stop it if they want to.
  17555. se.cut();
  17556. // Returning a Some here (regardless of boolean value) is going to call `stop` on the
  17557. // simulated event, which is going to call: preventDefault and stopPropagation. We want
  17558. // neither of these things to happen, so we return None here to say that it hasn't been
  17559. // handled. But because we've cut it, it will not propagate to any other alloy components
  17560. return Optional.none();
  17561. };
  17562. const handleByHighlightedItem = (comp, se) => {
  17563. // Because we need to redispatch based on highlighted items that we don't know about here,
  17564. // we are going to emit an event, that the sandbox listens to, and the sandbox will
  17565. // redispatch the event.
  17566. const eventData = {
  17567. interactionEvent: se.event,
  17568. eventType: se.event.raw.type
  17569. };
  17570. emitWith(comp, redirectMenuItemInteractionEvent, eventData);
  17571. return Optional.some(true);
  17572. };
  17573. const customSearcherEventsName = 'searcher-events';
  17574. return {
  17575. dom: {
  17576. tag: 'div',
  17577. // NOTE: This is very intentionally NOT the navigation class, because
  17578. // we don't want the searcher to be part of the navigation. This class
  17579. // is just for styling consistency. Perhaps it should be its own class.
  17580. classes: [selectableClass]
  17581. },
  17582. components: [
  17583. Input.sketch({
  17584. inputClasses: [menuSearcherClass, 'tox-textfield'],
  17585. inputAttributes: {
  17586. ...(spec.placeholder.map((placeholder) => ({ placeholder: spec.i18n(placeholder) })).getOr({})),
  17587. // This ARIA is based on the algolia example documented in TINY-8952
  17588. 'type': 'search',
  17589. 'aria-autocomplete': 'list'
  17590. },
  17591. inputBehaviours: derive$1([
  17592. config(customSearcherEventsName, [
  17593. // When the user types into the search field, we want to retrigger
  17594. // a fetch on the dropdown. This will be fired from within the
  17595. // dropdown's sandbox, so the dropdown is going to have to listen
  17596. // for it there. See CommonDropdown.ts.
  17597. run$1(
  17598. // Use "input" to handle keydown, paste etc.
  17599. input(), (inputComp) => {
  17600. emit(inputComp, refetchTriggerEvent);
  17601. }),
  17602. run$1(keydown(), (inputComp, se) => {
  17603. // The Special Keying config type since TINY-7005 processes the Escape
  17604. // key on keyup, not keydown. We need to stop the keydown event for this
  17605. // input, because some browsers (e.g. Chrome) will process a keydown
  17606. // for Escape inside an input[type=search] by clearing the input value,
  17607. // and then triggering an "input" event. This "input" event will trigger
  17608. // a refetch, which if it completes before the keyup is fired for Escape,
  17609. // will go back to only showing one level of menu. Then, when the escape
  17610. // keyup is processed by Keying, it will close the single remaining menu.
  17611. // This has the effect of closing *all* menus that are open when Escape is
  17612. // pressed instead of the last one. So, instead, we are going to kill the
  17613. // keydown event, so that it doesn't have the default browser behaviour, and
  17614. // won't trigger an input (and then Refetch). Then the keyup will still fire
  17615. // so just one level of the menu will close. This is all based on the underlying
  17616. // assumption that preventDefault and/or stop on a keydown does not suppress
  17617. // the related keyup. All of the documentation found so far, suggests it should
  17618. // only suppress the keypress, not the keyup, but that might not be across all
  17619. // browsers, or implemented consistently.
  17620. if (se.event.raw.key === 'Escape') {
  17621. se.stop();
  17622. }
  17623. })
  17624. ]),
  17625. // In addition to input handling, we want special handling for
  17626. // Up/Down/Left/Right/Enter/Escape/Space. We can divide these into two categories
  17627. // - events that we don't want to allow the overall menu system to process (left and right and space)
  17628. // - events that we want to redispatch on the "highlighted item" based on the
  17629. // current fake focus.
  17630. Keying.config({
  17631. mode: 'special',
  17632. onLeft: handleByBrowser,
  17633. onRight: handleByBrowser,
  17634. onSpace: handleByBrowser,
  17635. onEnter: handleByHighlightedItem,
  17636. onEscape: handleByHighlightedItem,
  17637. onUp: handleByHighlightedItem,
  17638. onDown: handleByHighlightedItem
  17639. })
  17640. ]),
  17641. // Because we have customised handling for keydown, and we are configuring
  17642. // Keying, we need to specify which "behaviour" (custom events or keying) gets to
  17643. // process the keydown event first. In this situation, we want to stop escape before
  17644. // anything happens (although it really isn't necessary)
  17645. eventOrder: {
  17646. keydown: [customSearcherEventsName, Keying.name()]
  17647. }
  17648. })
  17649. ]
  17650. };
  17651. };
  17652. const searchResultsClass = 'tox-collection--results__js';
  17653. // NOTE: this is operating on the the final AlloySpec
  17654. const augmentWithAria = (item) => {
  17655. var _a;
  17656. if (item.dom) {
  17657. return {
  17658. ...item,
  17659. dom: {
  17660. ...item.dom,
  17661. attributes: {
  17662. ...(_a = item.dom.attributes) !== null && _a !== void 0 ? _a : {},
  17663. 'id': generate$6('aria-item-search-result-id'),
  17664. 'aria-selected': 'false'
  17665. }
  17666. }
  17667. };
  17668. }
  17669. else {
  17670. return item;
  17671. }
  17672. };
  17673. const widgetAriaLabel = 'Use arrow keys to navigate.';
  17674. const chunk = (rowDom, numColumns) => (items) => {
  17675. const chunks = chunk$1(items, numColumns);
  17676. return map$2(chunks, (c) => ({
  17677. dom: rowDom,
  17678. components: c
  17679. }));
  17680. };
  17681. const forSwatch = (columns) => ({
  17682. dom: {
  17683. tag: 'div',
  17684. classes: ['tox-menu', 'tox-swatches-menu'],
  17685. attributes: {
  17686. 'aria-label': global$6.translate(widgetAriaLabel)
  17687. }
  17688. },
  17689. components: [
  17690. {
  17691. dom: {
  17692. tag: 'div',
  17693. classes: ['tox-swatches']
  17694. },
  17695. components: [
  17696. Menu.parts.items({
  17697. preprocess: columns !== 'auto' ? chunk({
  17698. tag: 'div',
  17699. classes: ['tox-swatches__row']
  17700. }, columns) : identity
  17701. })
  17702. ]
  17703. }
  17704. ]
  17705. });
  17706. const forImageSelector = (columns) => ({
  17707. dom: {
  17708. tag: 'div',
  17709. classes: ['tox-menu', 'tox-image-selector-menu']
  17710. },
  17711. components: [
  17712. {
  17713. dom: {
  17714. tag: 'div',
  17715. classes: ['tox-image-selector']
  17716. },
  17717. components: [
  17718. Menu.parts.items({
  17719. preprocess: columns !== 'auto' ? chunk({
  17720. tag: 'div',
  17721. classes: ['tox-image-selector__row']
  17722. }, columns) : identity
  17723. })
  17724. ]
  17725. }
  17726. ]
  17727. });
  17728. const forToolbar = (columns) => ({
  17729. dom: {
  17730. tag: 'div',
  17731. // TODO: Configurable lg setting?
  17732. classes: ['tox-menu', 'tox-collection', 'tox-collection--toolbar', 'tox-collection--toolbar-lg']
  17733. },
  17734. components: [
  17735. Menu.parts.items({
  17736. preprocess: chunk({
  17737. tag: 'div',
  17738. classes: ['tox-collection__group']
  17739. }, columns)
  17740. })
  17741. ]
  17742. });
  17743. // NOTE: That type signature isn't quite true.
  17744. const preprocessCollection = (items, isSeparator) => {
  17745. const allSplits = [];
  17746. let currentSplit = [];
  17747. each$1(items, (item, i) => {
  17748. if (isSeparator(item, i)) {
  17749. if (currentSplit.length > 0) {
  17750. allSplits.push(currentSplit);
  17751. }
  17752. currentSplit = [];
  17753. if (has$2(item.dom, 'innerHtml') || item.components && item.components.length > 0) {
  17754. currentSplit.push(item);
  17755. }
  17756. }
  17757. else {
  17758. currentSplit.push(item);
  17759. }
  17760. });
  17761. if (currentSplit.length > 0) {
  17762. allSplits.push(currentSplit);
  17763. }
  17764. return map$2(allSplits, (s) => ({
  17765. dom: {
  17766. tag: 'div',
  17767. classes: ['tox-collection__group']
  17768. },
  17769. components: s
  17770. }));
  17771. };
  17772. const insertItemsPlaceholder = (columns, initItems, onItem) => {
  17773. return Menu.parts.items({
  17774. preprocess: (rawItems) => {
  17775. // Add any information to the items that is required. For example
  17776. // when the items are results in a searchable menu, we need them to have
  17777. // an ID that can be referenced by aria-activedescendant
  17778. const enrichedItems = map$2(rawItems, onItem);
  17779. if (columns !== 'auto' && columns > 1) {
  17780. return chunk({
  17781. tag: 'div',
  17782. classes: ['tox-collection__group']
  17783. }, columns)(enrichedItems);
  17784. }
  17785. else {
  17786. return preprocessCollection(enrichedItems, (_item, i) => initItems[i].type === 'separator');
  17787. }
  17788. }
  17789. });
  17790. };
  17791. const hasWidget = (items) => exists(items, (item) => item.type === 'widget');
  17792. const forCollection = (columns, initItems, _hasIcons = true) => ({
  17793. dom: {
  17794. tag: 'div',
  17795. classes: ['tox-menu', 'tox-collection'].concat(columns === 1 ? ['tox-collection--list'] : ['tox-collection--grid']),
  17796. attributes: {
  17797. // widget item can be inserttable, colorswatch or imageselect - all of them are navigated with arrow keys
  17798. ...hasWidget(initItems) ? { 'aria-label': global$6.translate(widgetAriaLabel) } : {}
  17799. },
  17800. },
  17801. components: [
  17802. // We don't need to add IDs for each item because there are no
  17803. // aria relationships we need to maintain
  17804. insertItemsPlaceholder(columns, initItems, identity)
  17805. ]
  17806. });
  17807. const forCollectionWithSearchResults = (columns, initItems, _hasIcons = true) => {
  17808. // A collection with results is exactly like a collection, except it also has
  17809. // an ID and class on its outer div to allow for aria-controls relationships, and ids
  17810. // on its items.
  17811. // This connects the search bar with the list box.
  17812. const ariaControlsSearchResults = generate$6('aria-controls-search-results');
  17813. return {
  17814. dom: {
  17815. tag: 'div',
  17816. classes: ['tox-menu', 'tox-collection', searchResultsClass].concat(columns === 1 ? ['tox-collection--list'] : ['tox-collection--grid']),
  17817. attributes: {
  17818. id: ariaControlsSearchResults
  17819. }
  17820. },
  17821. components: [
  17822. // For each item, it needs to have an ID, so that we can refer to it
  17823. // by the aria-activedescendant attribute
  17824. insertItemsPlaceholder(columns, initItems, augmentWithAria)
  17825. ]
  17826. };
  17827. };
  17828. // Does a searchable menu *really* support columns !== 1 ?
  17829. const forCollectionWithSearchField = (columns, initItems, searchField) => {
  17830. // This connects the search bar with the list box.
  17831. const ariaControlsSearchResults = generate$6('aria-controls-search-results');
  17832. return {
  17833. dom: {
  17834. tag: 'div',
  17835. classes: ['tox-menu', 'tox-collection'].concat(columns === 1 ? ['tox-collection--list'] : ['tox-collection--grid'])
  17836. },
  17837. components: [
  17838. // Importantly, the search bar is not in the "items" part, which means that it is
  17839. // not given any of the item decorations by default. In order to ensure that is
  17840. // not part of the navigation, however, we need to prevent it from getting the nav
  17841. // class. For general collection menu items, it is navClass, which is:
  17842. // tox-menu-nav__js. So simply, do not add this class when creating
  17843. // the search, so that it isn't in the navigation. Ideally, it would only ever look
  17844. // inside its items section, but the items aren't guaranteed to have a separate
  17845. // container, and navigation candidates are found anywhere inside the menu
  17846. // container. We could add configuration to alloy's Menu movement, where there was
  17847. // a 'navigation container' that all items would be in. That could be another
  17848. // way to solve the problem. For now, we'll just manually avoid adding the navClass
  17849. renderMenuSearcher({
  17850. i18n: global$6.translate,
  17851. placeholder: searchField.placeholder
  17852. }),
  17853. {
  17854. // We need a separate container for the items, because this is the container
  17855. // that multiple tox-collection__groups might go into, and will be the container
  17856. // that the search bar controls.
  17857. dom: {
  17858. tag: 'div',
  17859. classes: [
  17860. ...(columns === 1 ? ['tox-collection--list'] : ['tox-collection--grid']),
  17861. searchResultsClass
  17862. ],
  17863. attributes: {
  17864. id: ariaControlsSearchResults
  17865. }
  17866. },
  17867. components: [
  17868. // For each item, it needs to have an ID, so that we can refer to it
  17869. // by the aria-activedescendant attribute
  17870. insertItemsPlaceholder(columns, initItems, augmentWithAria)
  17871. ]
  17872. }
  17873. ]
  17874. };
  17875. };
  17876. const forHorizontalCollection = (initItems, _hasIcons = true) => ({
  17877. dom: {
  17878. tag: 'div',
  17879. classes: ['tox-collection', 'tox-collection--horizontal']
  17880. },
  17881. components: [
  17882. Menu.parts.items({
  17883. preprocess: (items) => preprocessCollection(items, (_item, i) => initItems[i].type === 'separator')
  17884. })
  17885. ]
  17886. });
  17887. const menuHasIcons = (xs) => exists(xs, (item) => 'icon' in item && item.icon !== undefined);
  17888. const handleError = (error) => {
  17889. // eslint-disable-next-line no-console
  17890. console.error(formatError(error));
  17891. // eslint-disable-next-line no-console
  17892. console.log(error);
  17893. return Optional.none();
  17894. };
  17895. const createHorizontalPartialMenuWithAlloyItems = (value, _hasIcons, items, _columns, _menuLayout) => {
  17896. // Horizontal collections do not support different menu layout structures currently.
  17897. const structure = forHorizontalCollection(items);
  17898. return {
  17899. value,
  17900. dom: structure.dom,
  17901. components: structure.components,
  17902. items
  17903. };
  17904. };
  17905. const createPartialMenuWithAlloyItems = (value, hasIcons, items, columns, menuLayout) => {
  17906. const getNormalStructure = () => {
  17907. if (menuLayout.menuType !== 'searchable') {
  17908. return forCollection(columns, items);
  17909. }
  17910. else {
  17911. return menuLayout.searchMode.searchMode === 'search-with-field'
  17912. ? forCollectionWithSearchField(columns, items, menuLayout.searchMode)
  17913. : forCollectionWithSearchResults(columns, items);
  17914. }
  17915. };
  17916. if (menuLayout.menuType === 'color') {
  17917. const structure = forSwatch(columns);
  17918. return {
  17919. value,
  17920. dom: structure.dom,
  17921. components: structure.components,
  17922. items
  17923. };
  17924. }
  17925. else if (menuLayout.menuType === 'imageselector' && columns !== 'auto') {
  17926. const structure = forImageSelector(columns);
  17927. return {
  17928. value,
  17929. dom: structure.dom,
  17930. components: structure.components,
  17931. items
  17932. };
  17933. }
  17934. else if (menuLayout.menuType === 'normal' && columns === 'auto') {
  17935. const structure = forCollection(columns, items);
  17936. return {
  17937. value,
  17938. dom: structure.dom,
  17939. components: structure.components,
  17940. items
  17941. };
  17942. }
  17943. else if (menuLayout.menuType === 'normal' || menuLayout.menuType === 'searchable') {
  17944. const structure = getNormalStructure();
  17945. return {
  17946. value,
  17947. dom: structure.dom,
  17948. components: structure.components,
  17949. items
  17950. };
  17951. }
  17952. else if (menuLayout.menuType === 'listpreview' && columns !== 'auto') {
  17953. const structure = forToolbar(columns);
  17954. return {
  17955. value,
  17956. dom: structure.dom,
  17957. components: structure.components,
  17958. items
  17959. };
  17960. }
  17961. else {
  17962. return {
  17963. value,
  17964. dom: dom(hasIcons, columns, menuLayout.menuType),
  17965. components: components,
  17966. items
  17967. };
  17968. }
  17969. };
  17970. const type = requiredString('type');
  17971. const name = requiredString('name');
  17972. const label = requiredString('label');
  17973. const text = requiredString('text');
  17974. const title = requiredString('title');
  17975. const icon = requiredString('icon');
  17976. const url = requiredString('url');
  17977. const value = requiredString('value');
  17978. const fetch = requiredFunction('fetch');
  17979. const getSubmenuItems = requiredFunction('getSubmenuItems');
  17980. const onAction = requiredFunction('onAction');
  17981. const onItemAction = requiredFunction('onItemAction');
  17982. const onSetup = defaultedFunction('onSetup', () => noop);
  17983. const optionalName = optionString('name');
  17984. const optionalText = optionString('text');
  17985. const optionalRole = optionString('role');
  17986. const optionalIcon = optionString('icon');
  17987. const optionalTooltip = optionString('tooltip');
  17988. const optionalChevronTooltip = optionString('chevronTooltip');
  17989. const optionalLabel = optionString('label');
  17990. const optionalShortcut = optionString('shortcut');
  17991. const optionalSelect = optionFunction('select');
  17992. const active = defaultedBoolean('active', false);
  17993. const borderless = defaultedBoolean('borderless', false);
  17994. const enabled = defaultedBoolean('enabled', true);
  17995. const primary = defaultedBoolean('primary', false);
  17996. const defaultedColumns = (num) => defaulted('columns', num);
  17997. const defaultedMeta = defaulted('meta', {});
  17998. const defaultedOnAction = defaultedFunction('onAction', noop);
  17999. const defaultedType = (type) => defaultedString('type', type);
  18000. const generatedName = (namePrefix) => field$1('name', 'name', defaultedThunk(() => generate$6(`${namePrefix}-name`)), string);
  18001. const generatedValue = (valuePrefix) => field$1('value', 'value', defaultedThunk(() => generate$6(`${valuePrefix}-value`)), anyValue());
  18002. const alertBannerFields = [
  18003. type,
  18004. text,
  18005. requiredStringEnum('level', ['info', 'warn', 'error', 'success']),
  18006. icon,
  18007. defaulted('url', '')
  18008. ];
  18009. const alertBannerSchema = objOf(alertBannerFields);
  18010. const createBarFields = (itemsField) => [
  18011. type,
  18012. itemsField
  18013. ];
  18014. const buttonFields = [
  18015. type,
  18016. text,
  18017. enabled,
  18018. generatedName('button'),
  18019. optionalIcon,
  18020. borderless,
  18021. // this should be defaulted to `secondary` but the implementation needs to manage the deprecation
  18022. optionStringEnum('buttonType', ['primary', 'secondary', 'toolbar']),
  18023. // this should be removed, but must live here because FieldSchema doesn't have a way to manage deprecated fields
  18024. primary,
  18025. defaultedString('context', 'mode:design')
  18026. ];
  18027. const buttonSchema = objOf(buttonFields);
  18028. const formComponentFields = [
  18029. type,
  18030. name
  18031. ];
  18032. const formComponentWithLabelFields = formComponentFields.concat([
  18033. optionalLabel
  18034. ]);
  18035. const checkboxFields = formComponentFields.concat([
  18036. label,
  18037. enabled,
  18038. defaultedString('context', 'mode:design')
  18039. ]);
  18040. const checkboxSchema = objOf(checkboxFields);
  18041. const checkboxDataProcessor = boolean;
  18042. const collectionFields = formComponentWithLabelFields.concat([
  18043. defaultedColumns('auto'),
  18044. defaultedString('context', 'mode:design')
  18045. ]);
  18046. const collectionSchema = objOf(collectionFields);
  18047. // TODO: Make type for CollectionItem
  18048. const collectionDataProcessor = arrOfObj([
  18049. value,
  18050. text,
  18051. icon
  18052. ]);
  18053. const colorInputFields = formComponentWithLabelFields.concat([
  18054. defaultedString('storageKey', 'default'),
  18055. defaultedString('context', 'mode:design'),
  18056. ]);
  18057. const colorInputSchema = objOf(colorInputFields);
  18058. const colorInputDataProcessor = string;
  18059. const colorPickerFields = formComponentWithLabelFields;
  18060. const colorPickerSchema = objOf(colorPickerFields);
  18061. const colorPickerDataProcessor = string;
  18062. const customEditorFields = formComponentFields.concat([
  18063. defaultedString('tag', 'textarea'),
  18064. requiredString('scriptId'),
  18065. requiredString('scriptUrl'),
  18066. optionFunction('onFocus'),
  18067. defaultedPostMsg('settings', undefined)
  18068. ]);
  18069. const customEditorFieldsOld = formComponentFields.concat([
  18070. defaultedString('tag', 'textarea'),
  18071. requiredFunction('init')
  18072. ]);
  18073. const customEditorSchema = valueOf((v) => asRaw('customeditor.old', objOfOnly(customEditorFieldsOld), v).orThunk(() => asRaw('customeditor.new', objOfOnly(customEditorFields), v)));
  18074. const customEditorDataProcessor = string;
  18075. const commonMenuItemFields = [
  18076. enabled,
  18077. optionalText,
  18078. optionalRole,
  18079. optionalShortcut,
  18080. generatedValue('menuitem'),
  18081. defaultedMeta,
  18082. defaultedString('context', 'mode:design')
  18083. ];
  18084. const dialogToggleMenuItemSchema = objOf([
  18085. type,
  18086. name
  18087. ].concat(commonMenuItemFields));
  18088. const dialogToggleMenuItemDataProcessor = boolean;
  18089. const baseFooterButtonFields = [
  18090. generatedName('button'),
  18091. optionalIcon,
  18092. defaultedStringEnum('align', 'end', ['start', 'end']),
  18093. // this should be removed, but must live here because FieldSchema doesn't have a way to manage deprecated fields
  18094. primary,
  18095. enabled,
  18096. // this should be defaulted to `secondary` but the implementation needs to manage the deprecation
  18097. optionStringEnum('buttonType', ['primary', 'secondary']),
  18098. defaultedString('context', 'mode:design')
  18099. ];
  18100. const dialogFooterButtonFields = [
  18101. ...baseFooterButtonFields,
  18102. text
  18103. ];
  18104. const normalFooterButtonFields = [
  18105. requiredStringEnum('type', ['submit', 'cancel', 'custom']),
  18106. ...dialogFooterButtonFields
  18107. ];
  18108. const menuFooterButtonFields = [
  18109. requiredStringEnum('type', ['menu']),
  18110. optionalText,
  18111. optionalTooltip,
  18112. optionalIcon,
  18113. requiredArrayOf('items', dialogToggleMenuItemSchema),
  18114. ...baseFooterButtonFields
  18115. ];
  18116. const toggleButtonSpecFields = [
  18117. ...baseFooterButtonFields,
  18118. requiredStringEnum('type', ['togglebutton']),
  18119. optionalTooltip,
  18120. optionalIcon,
  18121. optionalText,
  18122. defaultedBoolean('active', false)
  18123. ];
  18124. const dialogFooterButtonSchema = choose$1('type', {
  18125. submit: normalFooterButtonFields,
  18126. cancel: normalFooterButtonFields,
  18127. custom: normalFooterButtonFields,
  18128. menu: menuFooterButtonFields,
  18129. togglebutton: toggleButtonSpecFields
  18130. });
  18131. const dropZoneFields = formComponentWithLabelFields.concat([
  18132. defaultedString('context', 'mode:design'),
  18133. ]);
  18134. const dropZoneSchema = objOf(dropZoneFields);
  18135. const dropZoneDataProcessor = arrOfVal();
  18136. const createGridFields = (itemsField) => [
  18137. type,
  18138. requiredNumber('columns'),
  18139. itemsField
  18140. ];
  18141. const htmlPanelFields = [
  18142. type,
  18143. requiredString('html'),
  18144. defaultedStringEnum('presets', 'presentation', ['presentation', 'document']),
  18145. defaultedFunction('onInit', noop),
  18146. defaultedBoolean('stretched', false),
  18147. ];
  18148. const htmlPanelSchema = objOf(htmlPanelFields);
  18149. const iframeFields = formComponentWithLabelFields.concat([
  18150. defaultedBoolean('border', false),
  18151. defaultedBoolean('sandboxed', true),
  18152. defaultedBoolean('streamContent', false),
  18153. defaultedBoolean('transparent', true)
  18154. ]);
  18155. const iframeSchema = objOf(iframeFields);
  18156. const iframeDataProcessor = string;
  18157. const imagePreviewSchema = objOf(formComponentFields.concat([
  18158. optionString('height'),
  18159. ]));
  18160. const imagePreviewDataProcessor = objOf([
  18161. requiredString('url'),
  18162. optionNumber('zoom'),
  18163. optionNumber('cachedWidth'),
  18164. optionNumber('cachedHeight'),
  18165. ]);
  18166. const inputFields = formComponentWithLabelFields.concat([
  18167. optionString('inputMode'),
  18168. optionString('placeholder'),
  18169. defaultedBoolean('maximized', false),
  18170. enabled,
  18171. defaultedString('context', 'mode:design'),
  18172. ]);
  18173. const inputSchema = objOf(inputFields);
  18174. const inputDataProcessor = string;
  18175. const createLabelFields = (itemsField) => [
  18176. type,
  18177. label,
  18178. itemsField,
  18179. defaultedStringEnum('align', 'start', ['start', 'center', 'end']),
  18180. optionString('for')
  18181. ];
  18182. const listBoxSingleItemFields = [
  18183. text,
  18184. value
  18185. ];
  18186. const listBoxNestedItemFields = [
  18187. text,
  18188. requiredArrayOf('items', thunkOf('items', () => listBoxItemSchema))
  18189. ];
  18190. const listBoxItemSchema = oneOf([
  18191. objOf(listBoxSingleItemFields),
  18192. objOf(listBoxNestedItemFields)
  18193. ]);
  18194. const listBoxFields = formComponentWithLabelFields.concat([
  18195. requiredArrayOf('items', listBoxItemSchema),
  18196. enabled,
  18197. defaultedString('context', 'mode:design')
  18198. ]);
  18199. const listBoxSchema = objOf(listBoxFields);
  18200. const listBoxDataProcessor = string;
  18201. const selectBoxFields = formComponentWithLabelFields.concat([
  18202. requiredArrayOfObj('items', [
  18203. text,
  18204. value
  18205. ]),
  18206. defaultedNumber('size', 1),
  18207. enabled,
  18208. defaultedString('context', 'mode:design')
  18209. ]);
  18210. const selectBoxSchema = objOf(selectBoxFields);
  18211. const selectBoxDataProcessor = string;
  18212. const sizeInputFields = formComponentWithLabelFields.concat([
  18213. defaultedBoolean('constrain', true),
  18214. enabled,
  18215. defaultedString('context', 'mode:design')
  18216. ]);
  18217. const sizeInputSchema = objOf(sizeInputFields);
  18218. const sizeInputDataProcessor = objOf([
  18219. requiredString('width'),
  18220. requiredString('height')
  18221. ]);
  18222. const sliderFields = formComponentFields.concat([
  18223. label,
  18224. defaultedNumber('min', 0),
  18225. defaultedNumber('max', 0),
  18226. ]);
  18227. const sliderSchema = objOf(sliderFields);
  18228. const sliderInputDataProcessor = number;
  18229. const tableFields = [
  18230. type,
  18231. requiredArrayOf('header', string),
  18232. requiredArrayOf('cells', arrOf(string))
  18233. ];
  18234. const tableSchema = objOf(tableFields);
  18235. const textAreaFields = formComponentWithLabelFields.concat([
  18236. optionString('placeholder'),
  18237. defaultedBoolean('maximized', false),
  18238. enabled,
  18239. defaultedString('context', 'mode:design'),
  18240. optionBoolean('spellcheck'),
  18241. ]);
  18242. const textAreaSchema = objOf(textAreaFields);
  18243. const textAreaDataProcessor = string;
  18244. const baseMenuButtonFields = [
  18245. defaultedString('buttonType', 'default'),
  18246. optionString('text'),
  18247. optionString('tooltip'),
  18248. optionString('icon'),
  18249. defaultedOf('search', false,
  18250. // So our boulder validation are:
  18251. // a) boolean -> we need to map it into an Option
  18252. // b) object -> we need to map it into a Some
  18253. oneOf([
  18254. // Unfortunately, due to objOf not checking to see that the
  18255. // input is an object, the boolean check MUST be first
  18256. boolean,
  18257. objOf([
  18258. optionString('placeholder')
  18259. ])
  18260. ],
  18261. // This function allows you to standardise the output.
  18262. (x) => {
  18263. if (isBoolean(x)) {
  18264. return x ? Optional.some({ placeholder: Optional.none() }) : Optional.none();
  18265. }
  18266. else {
  18267. return Optional.some(x);
  18268. }
  18269. })),
  18270. requiredFunction('fetch'),
  18271. defaultedFunction('onSetup', () => noop),
  18272. defaultedString('context', 'mode:design')
  18273. ];
  18274. const MenuButtonSchema = objOf([
  18275. type,
  18276. ...baseMenuButtonFields
  18277. ]);
  18278. const createMenuButton = (spec) => asRaw('menubutton', MenuButtonSchema, spec);
  18279. const baseTreeItemFields = [
  18280. requiredStringEnum('type', ['directory', 'leaf']),
  18281. title,
  18282. requiredString('id'),
  18283. optionOf('menu', MenuButtonSchema),
  18284. optionString('customStateIcon'),
  18285. optionString('customStateIconTooltip'),
  18286. ];
  18287. const treeItemLeafFields = baseTreeItemFields;
  18288. const treeItemLeafSchema = objOf(treeItemLeafFields);
  18289. const treeItemDirectoryFields = baseTreeItemFields.concat([
  18290. requiredArrayOf('children', thunkOf('children', () => {
  18291. return choose$2('type', {
  18292. directory: treeItemDirectorySchema,
  18293. leaf: treeItemLeafSchema,
  18294. });
  18295. })),
  18296. ]);
  18297. const treeItemDirectorySchema = objOf(treeItemDirectoryFields);
  18298. const treeItemSchema = choose$2('type', {
  18299. directory: treeItemDirectorySchema,
  18300. leaf: treeItemLeafSchema,
  18301. });
  18302. const treeFields = [
  18303. type,
  18304. requiredArrayOf('items', treeItemSchema),
  18305. optionFunction('onLeafAction'),
  18306. optionFunction('onToggleExpand'),
  18307. defaultedArrayOf('defaultExpandedIds', [], string),
  18308. optionString('defaultSelectedId'),
  18309. ];
  18310. const treeSchema = objOf(treeFields);
  18311. const urlInputFields = formComponentWithLabelFields.concat([
  18312. defaultedStringEnum('filetype', 'file', ['image', 'media', 'file']),
  18313. enabled,
  18314. optionString('picker_text'),
  18315. defaultedString('context', 'mode:design')
  18316. ]);
  18317. const urlInputSchema = objOf(urlInputFields);
  18318. const urlInputDataProcessor = objOf([
  18319. value,
  18320. defaultedMeta
  18321. ]);
  18322. const createItemsField = (name) => field$1('items', 'items', required$2(), arrOf(valueOf((v) => asRaw(`Checking item of ${name}`, itemSchema$1, v).fold((sErr) => Result.error(formatError(sErr)), (passValue) => Result.value(passValue)))));
  18323. // We're using a thunk here so we can refer to panel fields
  18324. const itemSchema$1 = valueThunk(() => choose$2('type', {
  18325. alertbanner: alertBannerSchema,
  18326. bar: objOf(createBarFields(createItemsField('bar'))),
  18327. button: buttonSchema,
  18328. checkbox: checkboxSchema,
  18329. colorinput: colorInputSchema,
  18330. colorpicker: colorPickerSchema,
  18331. dropzone: dropZoneSchema,
  18332. grid: objOf(createGridFields(createItemsField('grid'))),
  18333. iframe: iframeSchema,
  18334. input: inputSchema,
  18335. listbox: listBoxSchema,
  18336. selectbox: selectBoxSchema,
  18337. sizeinput: sizeInputSchema,
  18338. slider: sliderSchema,
  18339. textarea: textAreaSchema,
  18340. urlinput: urlInputSchema,
  18341. customeditor: customEditorSchema,
  18342. htmlpanel: htmlPanelSchema,
  18343. imagepreview: imagePreviewSchema,
  18344. collection: collectionSchema,
  18345. label: objOf(createLabelFields(createItemsField('label'))),
  18346. table: tableSchema,
  18347. tree: treeSchema,
  18348. panel: panelSchema
  18349. }));
  18350. const panelFields = [
  18351. type,
  18352. defaulted('classes', []),
  18353. requiredArrayOf('items', itemSchema$1)
  18354. ];
  18355. const panelSchema = objOf(panelFields);
  18356. const tabFields = [
  18357. generatedName('tab'),
  18358. title,
  18359. requiredArrayOf('items', itemSchema$1)
  18360. ];
  18361. const tabPanelFields = [
  18362. type,
  18363. requiredArrayOfObj('tabs', tabFields)
  18364. ];
  18365. const tabPanelSchema = objOf(tabPanelFields);
  18366. const dialogButtonFields = dialogFooterButtonFields;
  18367. const dialogButtonSchema = dialogFooterButtonSchema;
  18368. const dialogSchema = objOf([
  18369. requiredString('title'),
  18370. requiredOf('body', choose$2('type', {
  18371. panel: panelSchema,
  18372. tabpanel: tabPanelSchema
  18373. })),
  18374. defaultedString('size', 'normal'),
  18375. defaultedArrayOf('buttons', [], dialogButtonSchema),
  18376. defaulted('initialData', {}),
  18377. defaultedFunction('onAction', noop),
  18378. defaultedFunction('onChange', noop),
  18379. defaultedFunction('onSubmit', noop),
  18380. defaultedFunction('onClose', noop),
  18381. defaultedFunction('onCancel', noop),
  18382. defaultedFunction('onTabChange', noop)
  18383. ]);
  18384. const createDialog = (spec) => asRaw('dialog', dialogSchema, spec);
  18385. const urlDialogButtonSchema = objOf([
  18386. requiredStringEnum('type', ['cancel', 'custom']),
  18387. ...dialogButtonFields
  18388. ]);
  18389. const urlDialogSchema = objOf([
  18390. requiredString('title'),
  18391. requiredString('url'),
  18392. optionNumber('height'),
  18393. optionNumber('width'),
  18394. optionArrayOf('buttons', urlDialogButtonSchema),
  18395. defaultedFunction('onAction', noop),
  18396. defaultedFunction('onCancel', noop),
  18397. defaultedFunction('onClose', noop),
  18398. defaultedFunction('onMessage', noop)
  18399. ]);
  18400. const createUrlDialog = (spec) => asRaw('dialog', urlDialogSchema, spec);
  18401. // eslint-disable-next-line @typescript-eslint/no-wrapper-object-types
  18402. const getAllObjects = (obj) => {
  18403. if (isObject(obj)) {
  18404. return [obj].concat(bind$3(values(obj), getAllObjects));
  18405. }
  18406. else if (isArray(obj)) {
  18407. return bind$3(obj, getAllObjects);
  18408. }
  18409. else {
  18410. return [];
  18411. }
  18412. };
  18413. const isNamedItem = (obj) => isString(obj.type) && isString(obj.name);
  18414. const dataProcessors = {
  18415. checkbox: checkboxDataProcessor,
  18416. colorinput: colorInputDataProcessor,
  18417. colorpicker: colorPickerDataProcessor,
  18418. dropzone: dropZoneDataProcessor,
  18419. input: inputDataProcessor,
  18420. iframe: iframeDataProcessor,
  18421. imagepreview: imagePreviewDataProcessor,
  18422. selectbox: selectBoxDataProcessor,
  18423. sizeinput: sizeInputDataProcessor,
  18424. slider: sliderInputDataProcessor,
  18425. listbox: listBoxDataProcessor,
  18426. size: sizeInputDataProcessor,
  18427. textarea: textAreaDataProcessor,
  18428. urlinput: urlInputDataProcessor,
  18429. customeditor: customEditorDataProcessor,
  18430. collection: collectionDataProcessor,
  18431. togglemenuitem: dialogToggleMenuItemDataProcessor
  18432. };
  18433. const getDataProcessor = (item) => Optional.from(dataProcessors[item.type]);
  18434. const getNamedItems = (structure) => filter$2(getAllObjects(structure), isNamedItem);
  18435. const createDataValidator = (structure) => {
  18436. const namedItems = getNamedItems(structure);
  18437. const fields = bind$3(namedItems, (item) => getDataProcessor(item).fold(() => [], (schema) => [requiredOf(item.name, schema)]));
  18438. return objOf(fields);
  18439. };
  18440. const extract = (structure) => {
  18441. var _a;
  18442. const internalDialog = getOrDie(createDialog(structure));
  18443. const dataValidator = createDataValidator(structure);
  18444. // We used to validate data here, but it's done when loading the dialog in tinymce
  18445. const initialData = (_a = structure.initialData) !== null && _a !== void 0 ? _a : {};
  18446. return {
  18447. internalDialog,
  18448. dataValidator,
  18449. initialData
  18450. };
  18451. };
  18452. const DialogManager = {
  18453. open: (factory, structure) => {
  18454. const extraction = extract(structure);
  18455. return factory(extraction.internalDialog, extraction.initialData, extraction.dataValidator);
  18456. },
  18457. openUrl: (factory, structure) => {
  18458. const internalDialog = getOrDie(createUrlDialog(structure));
  18459. return factory(internalDialog);
  18460. },
  18461. redial: (structure) => extract(structure)
  18462. };
  18463. const separatorMenuItemSchema = objOf([
  18464. type,
  18465. optionalText
  18466. ]);
  18467. const createSeparatorMenuItem = (spec) => asRaw('separatormenuitem', separatorMenuItemSchema, spec);
  18468. const autocompleterItemSchema = objOf([
  18469. // Currently, autocomplete items don't support configuring type, active, disabled, meta
  18470. defaultedType('autocompleteitem'),
  18471. active,
  18472. enabled,
  18473. defaultedMeta,
  18474. value,
  18475. optionalText,
  18476. optionalIcon
  18477. ]);
  18478. objOf([
  18479. type,
  18480. requiredString('trigger'),
  18481. defaultedNumber('minChars', 1),
  18482. defaultedColumns(1),
  18483. defaultedNumber('maxResults', 10),
  18484. optionFunction('matches'),
  18485. fetch,
  18486. onAction,
  18487. defaultedArrayOf('highlightOn', [], string)
  18488. ]);
  18489. const createSeparatorItem = (spec) => asRaw('Autocompleter.Separator', separatorMenuItemSchema, spec);
  18490. const createAutocompleterItem = (spec) => asRaw('Autocompleter.Item', autocompleterItemSchema, spec);
  18491. const baseToolbarButtonFields = [
  18492. enabled,
  18493. optionalTooltip,
  18494. optionalIcon,
  18495. optionalText,
  18496. onSetup,
  18497. defaultedString('context', 'mode:design')
  18498. ];
  18499. const toolbarButtonSchema = objOf([
  18500. type,
  18501. onAction,
  18502. optionalShortcut
  18503. ].concat(baseToolbarButtonFields));
  18504. const createToolbarButton = (spec) => asRaw('toolbarbutton', toolbarButtonSchema, spec);
  18505. const baseToolbarToggleButtonFields = [
  18506. active
  18507. ].concat(baseToolbarButtonFields);
  18508. const toggleButtonSchema = objOf(baseToolbarToggleButtonFields.concat([
  18509. type,
  18510. onAction,
  18511. optionalShortcut
  18512. ]));
  18513. const createToggleButton = (spec) => asRaw('ToggleButton', toggleButtonSchema, spec);
  18514. const contextBarFields = [
  18515. defaultedFunction('predicate', never),
  18516. defaultedStringEnum('scope', 'node', ['node', 'editor']),
  18517. defaultedStringEnum('position', 'selection', ['node', 'selection', 'line'])
  18518. ];
  18519. const contextButtonFields = baseToolbarButtonFields.concat([
  18520. defaultedType('contextformbutton'),
  18521. defaultedString('align', 'end'),
  18522. primary,
  18523. onAction,
  18524. customField('original', identity)
  18525. ]);
  18526. const contextToggleButtonFields = baseToolbarToggleButtonFields.concat([
  18527. defaultedType('contextformbutton'),
  18528. defaultedString('align', 'end'),
  18529. primary,
  18530. onAction,
  18531. customField('original', identity)
  18532. ]);
  18533. const launchButtonFields$1 = baseToolbarButtonFields.concat([
  18534. defaultedType('contextformbutton')
  18535. ]);
  18536. const launchToggleButtonFields = baseToolbarToggleButtonFields.concat([
  18537. defaultedType('contextformtogglebutton')
  18538. ]);
  18539. const toggleOrNormal = choose$1('type', {
  18540. contextformbutton: contextButtonFields,
  18541. contextformtogglebutton: contextToggleButtonFields
  18542. });
  18543. const baseContextFormFields = [
  18544. optionalLabel,
  18545. requiredArrayOf('commands', toggleOrNormal),
  18546. optionOf('launch', choose$1('type', {
  18547. contextformbutton: launchButtonFields$1,
  18548. contextformtogglebutton: launchToggleButtonFields
  18549. })),
  18550. defaultedFunction('onInput', noop),
  18551. defaultedFunction('onSetup', noop)
  18552. ];
  18553. const contextFormFields = [
  18554. ...contextBarFields,
  18555. ...baseContextFormFields,
  18556. requiredStringEnum('type', ['contextform']),
  18557. defaultedFunction('initValue', constant$1('')),
  18558. optionString('placeholder'),
  18559. ];
  18560. const contextSliderFormFields = [
  18561. ...contextBarFields,
  18562. ...baseContextFormFields,
  18563. requiredStringEnum('type', ['contextsliderform']),
  18564. defaultedFunction('initValue', constant$1(0)),
  18565. defaultedFunction('min', constant$1(0)),
  18566. defaultedFunction('max', constant$1(100))
  18567. ];
  18568. const contextSizeInputFormFields = [
  18569. ...contextBarFields,
  18570. ...baseContextFormFields,
  18571. requiredStringEnum('type', ['contextsizeinputform']),
  18572. defaultedFunction('initValue', constant$1({ width: '', height: '' }))
  18573. ];
  18574. const contextFormSchema = choose$1('type', {
  18575. contextform: contextFormFields,
  18576. contextsliderform: contextSliderFormFields,
  18577. contextsizeinputform: contextSizeInputFormFields
  18578. });
  18579. const createContextForm = (spec) => asRaw('ContextForm', contextFormSchema, spec);
  18580. const launchButtonFields = baseToolbarButtonFields.concat([
  18581. defaultedType('contexttoolbarbutton')
  18582. ]);
  18583. const contextToolbarSchema = objOf([
  18584. defaultedType('contexttoolbar'),
  18585. optionObjOf('launch', launchButtonFields),
  18586. requiredOf('items', oneOf([
  18587. string,
  18588. arrOfObj([
  18589. optionString('name'),
  18590. optionString('label'),
  18591. requiredArrayOf('items', string)
  18592. ])
  18593. ])),
  18594. ].concat(contextBarFields));
  18595. const toolbarGroupBackToSpec = (toolbarGroup) => ({
  18596. name: toolbarGroup.name.getOrUndefined(),
  18597. label: toolbarGroup.label.getOrUndefined(),
  18598. items: toolbarGroup.items
  18599. });
  18600. const contextToolbarToSpec = (contextToolbar) => ({
  18601. ...contextToolbar,
  18602. launch: contextToolbar.launch.getOrUndefined(),
  18603. items: isString(contextToolbar.items) ? contextToolbar.items : map$2(contextToolbar.items, toolbarGroupBackToSpec)
  18604. });
  18605. const createContextToolbar = (spec) => asRaw('ContextToolbar', contextToolbarSchema, spec);
  18606. const cardImageFields = [
  18607. type,
  18608. requiredString('src'),
  18609. optionString('alt'),
  18610. defaultedArrayOf('classes', [], string)
  18611. ];
  18612. const cardImageSchema = objOf(cardImageFields);
  18613. const cardTextFields = [
  18614. type,
  18615. text,
  18616. optionalName,
  18617. defaultedArrayOf('classes', ['tox-collection__item-label'], string)
  18618. ];
  18619. const cardTextSchema = objOf(cardTextFields);
  18620. const itemSchema = valueThunk(() => choose$2('type', {
  18621. cardimage: cardImageSchema,
  18622. cardtext: cardTextSchema,
  18623. cardcontainer: cardContainerSchema
  18624. }));
  18625. const cardContainerSchema = objOf([
  18626. type,
  18627. defaultedString('direction', 'horizontal'),
  18628. defaultedString('align', 'left'),
  18629. defaultedString('valign', 'middle'),
  18630. requiredArrayOf('items', itemSchema)
  18631. ]);
  18632. const cardMenuItemSchema = objOf([
  18633. type,
  18634. optionalLabel,
  18635. requiredArrayOf('items', itemSchema),
  18636. onSetup,
  18637. defaultedOnAction
  18638. ].concat(commonMenuItemFields));
  18639. const createCardMenuItem = (spec) => asRaw('cardmenuitem', cardMenuItemSchema, spec);
  18640. const choiceMenuItemSchema = objOf([
  18641. type,
  18642. active,
  18643. optionalIcon,
  18644. optionalLabel
  18645. ].concat(commonMenuItemFields));
  18646. const createChoiceMenuItem = (spec) => asRaw('choicemenuitem', choiceMenuItemSchema, spec);
  18647. const baseFields = [
  18648. type,
  18649. requiredString('fancytype'),
  18650. defaultedOnAction
  18651. ];
  18652. const insertTableFields = [
  18653. defaulted('initData', {})
  18654. ].concat(baseFields);
  18655. const colorSwatchFields = [
  18656. optionFunction('select'),
  18657. defaultedObjOf('initData', {}, [
  18658. defaultedBoolean('allowCustomColors', true),
  18659. defaultedString('storageKey', 'default'),
  18660. // Note: We don't validate the colors as they are instead validated by choiceschema when rendering
  18661. optionArrayOf('colors', anyValue())
  18662. ])
  18663. ].concat(baseFields);
  18664. const imageSelectFields = [
  18665. optionFunction('select'),
  18666. requiredObjOf('initData', [
  18667. requiredNumber('columns'),
  18668. // Note: We don't validate the items as they are instead validated by imageMenuItemSchema when rendering
  18669. defaultedArrayOf('items', [], anyValue())
  18670. ])
  18671. ].concat(baseFields);
  18672. const fancyMenuItemSchema = choose$1('fancytype', {
  18673. inserttable: insertTableFields,
  18674. colorswatch: colorSwatchFields,
  18675. imageselect: imageSelectFields
  18676. });
  18677. const createFancyMenuItem = (spec) => asRaw('fancymenuitem', fancyMenuItemSchema, spec);
  18678. const imageMenuItemSchema = objOf([
  18679. type,
  18680. active,
  18681. url,
  18682. optionalLabel,
  18683. optionalTooltip
  18684. ].concat(commonMenuItemFields));
  18685. const resetImageItemSchema = objOf([
  18686. type,
  18687. active,
  18688. icon,
  18689. label,
  18690. optionalTooltip,
  18691. value
  18692. ].concat(commonMenuItemFields));
  18693. const createImageMenuItem = (spec) => asRaw('imagemenuitem', imageMenuItemSchema, spec);
  18694. const createResetImageItem = (spec) => asRaw('resetimageitem', resetImageItemSchema, spec);
  18695. const menuItemSchema = objOf([
  18696. type,
  18697. onSetup,
  18698. defaultedOnAction,
  18699. optionalIcon
  18700. ].concat(commonMenuItemFields));
  18701. const createMenuItem = (spec) => asRaw('menuitem', menuItemSchema, spec);
  18702. const nestedMenuItemSchema = objOf([
  18703. type,
  18704. getSubmenuItems,
  18705. onSetup,
  18706. optionalIcon
  18707. ].concat(commonMenuItemFields));
  18708. const createNestedMenuItem = (spec) => asRaw('nestedmenuitem', nestedMenuItemSchema, spec);
  18709. const toggleMenuItemSchema = objOf([
  18710. type,
  18711. optionalIcon,
  18712. active,
  18713. onSetup,
  18714. onAction
  18715. ].concat(commonMenuItemFields));
  18716. const createToggleMenuItem = (spec) => asRaw('togglemenuitem', toggleMenuItemSchema, spec);
  18717. const sidebarSchema = objOf([
  18718. optionalIcon,
  18719. optionalTooltip,
  18720. defaultedFunction('onShow', noop),
  18721. defaultedFunction('onHide', noop),
  18722. onSetup
  18723. ]);
  18724. const createSidebar = (spec) => asRaw('sidebar', sidebarSchema, spec);
  18725. const groupToolbarButtonSchema = objOf([
  18726. type,
  18727. requiredOf('items', oneOf([
  18728. arrOfObj([
  18729. name,
  18730. requiredArrayOf('items', string)
  18731. ]),
  18732. string
  18733. ]))
  18734. ].concat(baseToolbarButtonFields));
  18735. const createGroupToolbarButton = (spec) => asRaw('GroupToolbarButton', groupToolbarButtonSchema, spec);
  18736. const splitButtonSchema = objOf([
  18737. type,
  18738. optionalTooltip,
  18739. optionalChevronTooltip,
  18740. optionalIcon,
  18741. optionalText,
  18742. optionalSelect,
  18743. fetch,
  18744. onSetup,
  18745. // TODO: Validate the allowed presets
  18746. defaultedStringEnum('presets', 'normal', ['normal', 'color', 'listpreview']),
  18747. defaultedColumns(1),
  18748. onAction,
  18749. onItemAction,
  18750. defaultedString('context', 'mode:design')
  18751. ]);
  18752. const createSplitButton = (spec) => asRaw('SplitButton', splitButtonSchema, spec);
  18753. const baseButtonFields = [
  18754. optionalText,
  18755. optionalIcon,
  18756. optionString('tooltip'),
  18757. defaultedStringEnum('buttonType', 'secondary', ['primary', 'secondary']),
  18758. defaultedBoolean('borderless', false),
  18759. requiredFunction('onAction'),
  18760. defaultedString('context', 'mode:design')
  18761. ];
  18762. const normalButtonFields = [
  18763. ...baseButtonFields,
  18764. text,
  18765. requiredStringEnum('type', ['button']),
  18766. ];
  18767. const toggleButtonFields = [
  18768. ...baseButtonFields,
  18769. defaultedBoolean('active', false),
  18770. requiredStringEnum('type', ['togglebutton'])
  18771. ];
  18772. const schemaWithoutGroupButton = {
  18773. button: normalButtonFields,
  18774. togglebutton: toggleButtonFields,
  18775. };
  18776. const groupFields = [
  18777. requiredStringEnum('type', ['group']),
  18778. defaultedArrayOf('buttons', [], choose$1('type', schemaWithoutGroupButton))
  18779. ];
  18780. const viewButtonSchema = choose$1('type', {
  18781. ...schemaWithoutGroupButton,
  18782. group: groupFields
  18783. });
  18784. const viewSchema = objOf([
  18785. defaultedArrayOf('buttons', [], viewButtonSchema),
  18786. requiredFunction('onShow'),
  18787. requiredFunction('onHide')
  18788. ]);
  18789. const createView = (spec) => asRaw('view', viewSchema, spec);
  18790. const detectSize = (comp, margin, selectorClass) => {
  18791. const descendants$1 = descendants(comp.element, '.' + selectorClass);
  18792. // TODO: This seems to cause performance issues in the emoji dialog
  18793. if (descendants$1.length > 0) {
  18794. const columnLength = findIndex$1(descendants$1, (c) => {
  18795. const thisTop = c.dom.getBoundingClientRect().top;
  18796. const cTop = descendants$1[0].dom.getBoundingClientRect().top;
  18797. return Math.abs(thisTop - cTop) > margin;
  18798. }).getOr(descendants$1.length);
  18799. return Optional.some({
  18800. numColumns: columnLength,
  18801. numRows: Math.ceil(descendants$1.length / columnLength)
  18802. });
  18803. }
  18804. else {
  18805. return Optional.none();
  18806. }
  18807. };
  18808. // Consider moving to alloy once it takes shape.
  18809. const namedEvents = (name, handlers) => derive$1([
  18810. config(name, handlers)
  18811. ]);
  18812. const unnamedEvents = (handlers) => namedEvents(generate$6('unnamed-events'), handlers);
  18813. const SimpleBehaviours = {
  18814. namedEvents,
  18815. unnamedEvents
  18816. };
  18817. const item = (disabled) => Disabling.config({
  18818. disabled,
  18819. disableClass: 'tox-collection__item--state-disabled'
  18820. });
  18821. const button = (disabled) => Disabling.config({
  18822. disabled
  18823. });
  18824. const splitButton = (disabled) => Disabling.config({
  18825. disabled,
  18826. disableClass: 'tox-tbtn--disabled'
  18827. });
  18828. const toolbarButton = (disabled) => Disabling.config({
  18829. disabled,
  18830. disableClass: 'tox-tbtn--disabled',
  18831. useNative: false
  18832. });
  18833. const DisablingConfigs = {
  18834. item,
  18835. button,
  18836. splitButton,
  18837. toolbarButton
  18838. };
  18839. const runWithApi = (info, comp) => {
  18840. const api = info.getApi(comp);
  18841. return (f) => {
  18842. f(api);
  18843. };
  18844. };
  18845. // These handlers are used for providing common onAttached and onDetached handlers.
  18846. // Essentially, the `editorOffCell` is used store the onDestroy function returned
  18847. // by onSetup. The reason onControlAttached doesn't create the cell itself, is because
  18848. // it also has to be passed into onControlDetached. We could make this function return
  18849. // the cell and the onAttachedHandler, but that would provide too much complexity.
  18850. const onControlAttached = (info, editorOffCell) => runOnAttached((comp) => {
  18851. if (isFunction(info.onBeforeSetup)) {
  18852. info.onBeforeSetup(comp);
  18853. }
  18854. const run = runWithApi(info, comp);
  18855. run((api) => {
  18856. const onDestroy = info.onSetup(api);
  18857. if (isFunction(onDestroy)) {
  18858. editorOffCell.set(onDestroy);
  18859. }
  18860. });
  18861. });
  18862. const onControlDetached = (getApi, editorOffCell) => runOnDetached((comp) => runWithApi(getApi, comp)(editorOffCell.get()));
  18863. const onContextFormControlDetached = (getApi, editorOffCell, valueState) => runOnDetached((comp) => {
  18864. valueState.set(Representing.getValue(comp));
  18865. return runWithApi(getApi, comp)(editorOffCell.get());
  18866. });
  18867. const UiStateChannel = 'silver.uistate';
  18868. const messageSetDisabled = 'setDisabled';
  18869. const messageSetEnabled = 'setEnabled';
  18870. const messageInit = 'init';
  18871. const messageSwitchMode = 'switchmode';
  18872. const modeContextMessages = [messageSwitchMode, messageInit];
  18873. const broadcastEvents = (uiRefs, messageType) => {
  18874. const outerContainer = uiRefs.mainUi.outerContainer;
  18875. const motherships = [uiRefs.mainUi.mothership, ...uiRefs.uiMotherships];
  18876. if (messageType === messageSetDisabled) {
  18877. each$1(motherships, (m) => {
  18878. m.broadcastOn([dismissPopups()], { target: outerContainer.element });
  18879. });
  18880. }
  18881. each$1(motherships, (m) => {
  18882. m.broadcastOn([UiStateChannel], messageType);
  18883. });
  18884. };
  18885. const setupEventsForUi = (editor, uiRefs) => {
  18886. editor.on('init SwitchMode', (event) => {
  18887. broadcastEvents(uiRefs, event.type);
  18888. });
  18889. editor.on('DisabledStateChange', (event) => {
  18890. if (!event.isDefaultPrevented()) {
  18891. // When the event state indicates the editor is **enabled** (`event.state` is false),
  18892. // we send an 'init' message instead of 'setEnabled' because the editor might be in read-only mode.
  18893. // Sending 'setEnabled' would enable all the toolbar buttons, which is undesirable if the editor is read-only.
  18894. const messageType = event.state ? messageSetDisabled : messageInit;
  18895. broadcastEvents(uiRefs, messageType);
  18896. // After refreshing the state of the buttons, trigger a NodeChange event.
  18897. if (!event.state) {
  18898. editor.nodeChanged();
  18899. }
  18900. }
  18901. });
  18902. editor.on('NodeChange', (e) => {
  18903. const messageType = editor.ui.isEnabled() ? e.type : messageSetDisabled;
  18904. broadcastEvents(uiRefs, messageType);
  18905. });
  18906. if (isReadOnly(editor)) {
  18907. editor.mode.set('readonly');
  18908. }
  18909. };
  18910. const toggleOnReceive = (getContext) => Receiving.config({
  18911. channels: {
  18912. [UiStateChannel]: {
  18913. onReceive: (comp, messageType) => {
  18914. if (messageType === messageSetDisabled || messageType === messageSetEnabled) {
  18915. Disabling.set(comp, messageType === messageSetDisabled);
  18916. return;
  18917. }
  18918. const { contextType, shouldDisable } = getContext();
  18919. if (contextType === 'mode' && !contains$2(modeContextMessages, messageType)) {
  18920. return;
  18921. }
  18922. Disabling.set(comp, shouldDisable);
  18923. }
  18924. }
  18925. }
  18926. });
  18927. // Perform `action` when an item is clicked on, close menus, and stop event
  18928. const onMenuItemExecute = (info, itemResponse) => runOnExecute$1((comp, simulatedEvent) => {
  18929. // If there is an action, run the action
  18930. runWithApi(info, comp)(info.onAction);
  18931. if (!info.triggersSubmenu && itemResponse === ItemResponse$1.CLOSE_ON_EXECUTE) {
  18932. if (comp.getSystem().isConnected()) {
  18933. emit(comp, sandboxClose());
  18934. }
  18935. simulatedEvent.stop();
  18936. }
  18937. });
  18938. const menuItemEventOrder = {
  18939. // TODO: use the constants provided by behaviours.
  18940. [execute$5()]: ['disabling', 'alloy.base.behaviour', 'toggling', 'item-events']
  18941. };
  18942. const componentRenderPipeline = cat;
  18943. const renderCommonItem = (spec, structure, itemResponse, providersBackstage) => {
  18944. const editorOffCell = Cell(noop);
  18945. return {
  18946. type: 'item',
  18947. dom: structure.dom,
  18948. components: componentRenderPipeline(structure.optComponents),
  18949. data: spec.data,
  18950. eventOrder: menuItemEventOrder,
  18951. hasSubmenu: spec.triggersSubmenu,
  18952. itemBehaviours: derive$1([
  18953. config('item-events', [
  18954. onMenuItemExecute(spec, itemResponse),
  18955. onControlAttached(spec, editorOffCell),
  18956. onControlDetached(spec, editorOffCell)
  18957. ]),
  18958. DisablingConfigs.item(() => !spec.enabled || providersBackstage.checkUiComponentContext(spec.context).shouldDisable),
  18959. toggleOnReceive(() => providersBackstage.checkUiComponentContext(spec.context)),
  18960. Replacing.config({})
  18961. ].concat(spec.itemBehaviours))
  18962. };
  18963. };
  18964. const buildData = (source) => ({
  18965. value: source.value,
  18966. meta: {
  18967. text: source.text.getOr(''),
  18968. ...source.meta
  18969. }
  18970. });
  18971. const renderImage$1 = (spec, imageUrl) => {
  18972. var _a, _b;
  18973. const spinnerElement = SugarElement.fromTag('div');
  18974. add$2(spinnerElement, 'tox-image-selector-loading-spinner');
  18975. const addSpinnerElement = (loadingElement) => {
  18976. add$2(loadingElement, 'tox-image-selector-loading-spinner-wrapper');
  18977. append$2(loadingElement, spinnerElement);
  18978. };
  18979. const removeSpinnerElement = (loadingElement) => {
  18980. remove$3(loadingElement, 'tox-image-selector-loading-spinner-wrapper');
  18981. remove$7(spinnerElement);
  18982. };
  18983. return {
  18984. dom: {
  18985. tag: spec.tag,
  18986. attributes: (_a = spec.attributes) !== null && _a !== void 0 ? _a : {},
  18987. classes: spec.classes,
  18988. },
  18989. components: [
  18990. {
  18991. dom: {
  18992. tag: 'div',
  18993. classes: ['tox-image-selector-image-wrapper']
  18994. },
  18995. components: [
  18996. {
  18997. dom: {
  18998. tag: 'img',
  18999. attributes: { src: imageUrl },
  19000. classes: ['tox-image-selector-image-img']
  19001. }
  19002. },
  19003. ]
  19004. },
  19005. ...spec.checkMark.toArray()
  19006. ],
  19007. behaviours: derive$1([
  19008. ...(_b = spec.behaviours) !== null && _b !== void 0 ? _b : [],
  19009. config('render-image-events', [
  19010. runOnAttached((component) => {
  19011. addSpinnerElement(component.element);
  19012. descendant(component.element, 'img').each((image$1) => {
  19013. image(image$1).catch((e) => {
  19014. // eslint-disable-next-line no-console
  19015. console.error(e);
  19016. }).finally(() => {
  19017. removeSpinnerElement(component.element);
  19018. });
  19019. });
  19020. })
  19021. ]),
  19022. ])
  19023. };
  19024. };
  19025. const render$3 = (imageUrl, spec) => renderImage$1(spec, imageUrl);
  19026. // Converts shortcut format to Mac/PC variants
  19027. // Note: This is different to the help shortcut converter, as it doesn't padd the + symbol with spaces
  19028. // so as to not take up large amounts of space in the menus
  19029. const convertText = (source) => {
  19030. const isMac = global$7.os.isMacOS() || global$7.os.isiOS();
  19031. const mac = {
  19032. alt: '\u2325',
  19033. ctrl: '\u2303',
  19034. shift: '\u21E7',
  19035. meta: '\u2318',
  19036. access: '\u2303\u2325'
  19037. };
  19038. const other = {
  19039. meta: 'Ctrl',
  19040. access: 'Shift+Alt'
  19041. };
  19042. const replace = isMac ? mac : other;
  19043. const shortcut = source.split('+');
  19044. const updated = map$2(shortcut, (segment) => {
  19045. // search lowercase, but if not found use the original
  19046. const search = segment.toLowerCase().trim();
  19047. return has$2(replace, search) ? replace[search] : segment;
  19048. });
  19049. return isMac ? updated.join('') : updated.join('+');
  19050. };
  19051. const renderIcon$2 = (name, icons, classes = [iconClass]) => render$4(name, { tag: 'div', classes }, icons);
  19052. const renderText = (text) => ({
  19053. dom: {
  19054. tag: 'div',
  19055. classes: [textClass]
  19056. },
  19057. components: [text$2(global$6.translate(text))]
  19058. });
  19059. const renderHtml = (html, classes) => ({
  19060. dom: {
  19061. tag: 'div',
  19062. classes,
  19063. innerHtml: html
  19064. }
  19065. });
  19066. const renderStyledText = (style, text) => ({
  19067. dom: {
  19068. tag: 'div',
  19069. classes: [textClass]
  19070. },
  19071. components: [
  19072. {
  19073. dom: {
  19074. tag: style.tag,
  19075. styles: style.styles
  19076. },
  19077. components: [text$2(global$6.translate(text))]
  19078. }
  19079. ]
  19080. });
  19081. const renderShortcut = (shortcut) => ({
  19082. dom: {
  19083. tag: 'div',
  19084. classes: [accessoryClass]
  19085. },
  19086. components: [
  19087. text$2(convertText(shortcut))
  19088. ]
  19089. });
  19090. const renderCheckmark = (icons) => renderIcon$2('checkmark', icons, [checkmarkClass]);
  19091. const renderSubmenuCaret = (icons) => renderIcon$2('chevron-right', icons, [caretClass]);
  19092. const renderDownwardsCaret = (icons) => renderIcon$2('chevron-down', icons, [caretClass]);
  19093. const renderContainer = (container, components) => {
  19094. const directionClass = container.direction === 'vertical' ? containerColumnClass : containerRowClass;
  19095. const alignClass = container.align === 'left' ? containerAlignLeftClass : containerAlignRightClass;
  19096. const getValignClass = () => {
  19097. switch (container.valign) {
  19098. case 'top':
  19099. return containerValignTopClass;
  19100. case 'middle':
  19101. return containerValignMiddleClass;
  19102. case 'bottom':
  19103. return containerValignBottomClass;
  19104. }
  19105. };
  19106. return {
  19107. dom: {
  19108. tag: 'div',
  19109. classes: [
  19110. containerClass,
  19111. directionClass,
  19112. alignClass,
  19113. getValignClass()
  19114. ]
  19115. },
  19116. components
  19117. };
  19118. };
  19119. const renderImage = (src, classes, alt) => ({
  19120. dom: {
  19121. tag: 'img',
  19122. classes,
  19123. attributes: {
  19124. src,
  19125. alt: alt.getOr('')
  19126. }
  19127. }
  19128. });
  19129. const renderColorStructure = (item, providerBackstage, fallbackIcon) => {
  19130. const colorPickerCommand = 'custom';
  19131. const removeColorCommand = 'remove';
  19132. const itemValue = item.value;
  19133. const iconSvg = item.iconContent.map((name) => getOr(name, providerBackstage.icons, fallbackIcon));
  19134. const attributes = item.ariaLabel.map((al) => ({
  19135. 'aria-label': providerBackstage.translate(al),
  19136. 'data-mce-name': al
  19137. })).getOr({});
  19138. const getDom = () => {
  19139. const common = colorClass;
  19140. const icon = iconSvg.getOr('');
  19141. const baseDom = {
  19142. tag: 'div',
  19143. attributes,
  19144. classes: [common]
  19145. };
  19146. if (itemValue === colorPickerCommand) {
  19147. return {
  19148. ...baseDom,
  19149. tag: 'button',
  19150. classes: [...baseDom.classes, 'tox-swatches__picker-btn'],
  19151. innerHtml: icon
  19152. };
  19153. }
  19154. else if (itemValue === removeColorCommand) {
  19155. return {
  19156. ...baseDom,
  19157. classes: [...baseDom.classes, 'tox-swatch--remove'],
  19158. innerHtml: icon
  19159. };
  19160. }
  19161. else if (isNonNullable(itemValue)) {
  19162. return {
  19163. ...baseDom,
  19164. attributes: {
  19165. ...baseDom.attributes,
  19166. 'data-mce-color': itemValue
  19167. },
  19168. styles: {
  19169. 'background-color': itemValue
  19170. },
  19171. innerHtml: icon
  19172. };
  19173. }
  19174. else {
  19175. return baseDom;
  19176. }
  19177. };
  19178. return {
  19179. dom: getDom(),
  19180. optComponents: []
  19181. };
  19182. };
  19183. const renderItemDomStructure = (ariaLabel, classes) => {
  19184. const domTitle = ariaLabel.map((label) => ({
  19185. attributes: {
  19186. 'id': generate$6('menu-item'),
  19187. 'aria-label': global$6.translate(label)
  19188. }
  19189. })).getOr({});
  19190. return {
  19191. tag: 'div',
  19192. classes: [navClass, selectableClass].concat(classes),
  19193. ...domTitle
  19194. };
  19195. };
  19196. const createLabel = (label) => {
  19197. return {
  19198. dom: {
  19199. tag: 'label'
  19200. },
  19201. components: [
  19202. text$2(label)
  19203. ]
  19204. };
  19205. };
  19206. const renderNormalItemStructure = (info, providersBackstage, renderIcons, fallbackIcon) => {
  19207. // TODO: TINY-3036 Work out a better way of dealing with custom icons
  19208. const iconSpec = { tag: 'div', classes: [iconClass] };
  19209. const renderIcon = (iconName) => render$4(iconName, iconSpec, providersBackstage.icons, fallbackIcon);
  19210. const renderEmptyIcon = () => Optional.some({ dom: iconSpec });
  19211. // Note: renderIcons indicates if any icons are present in the menu - if false then the icon column will not be present for the whole menu
  19212. const leftIcon = renderIcons ? info.iconContent.map(renderIcon).orThunk(renderEmptyIcon) : Optional.none();
  19213. // TINY-3345: Dedicated columns for icon and checkmark if applicable
  19214. const checkmark = info.checkMark;
  19215. // Style items and autocompleter both have meta. Need to branch on style
  19216. // This could probably be more stable...
  19217. const textRender = Optional.from(info.meta).fold(() => renderText, (meta) => has$2(meta, 'style') ? curry(renderStyledText, meta.style) : renderText);
  19218. const content = info.htmlContent.fold(() => info.textContent.map(textRender), (html) => Optional.some(renderHtml(html, [textClass])));
  19219. const menuItem = {
  19220. dom: renderItemDomStructure(info.ariaLabel, []),
  19221. optComponents: [
  19222. leftIcon,
  19223. content,
  19224. info.shortcutContent.map(renderShortcut),
  19225. checkmark,
  19226. info.caret,
  19227. info.labelContent.map(createLabel)
  19228. ]
  19229. };
  19230. return menuItem;
  19231. };
  19232. const renderImgItemStructure = (info) => {
  19233. const menuItem = {
  19234. dom: renderItemDomStructure(info.ariaLabel, [imageSelectorClasll]),
  19235. optComponents: [
  19236. Optional.some(render$3(info.iconContent.getOrDie(), { tag: 'div', classes: [imageClass], checkMark: info.checkMark })),
  19237. info.labelContent.map(createLabel)
  19238. ]
  19239. };
  19240. return menuItem;
  19241. };
  19242. // TODO: Maybe need aria-label
  19243. const renderItemStructure = (info, providersBackstage, renderIcons, fallbackIcon = Optional.none()) => {
  19244. if (info.presets === 'color') {
  19245. return renderColorStructure(info, providersBackstage, fallbackIcon);
  19246. }
  19247. else if (info.presets === 'img') {
  19248. return renderImgItemStructure(info);
  19249. }
  19250. else {
  19251. return renderNormalItemStructure(info, providersBackstage, renderIcons, fallbackIcon);
  19252. }
  19253. };
  19254. // Use meta to pass through special information about the tooltip
  19255. // (yes this is horrible but it is not yet public API)
  19256. const tooltipBehaviour = (meta, sharedBackstage, tooltipText) => get$h(meta, 'tooltipWorker')
  19257. .map((tooltipWorker) => [
  19258. Tooltipping.config({
  19259. lazySink: sharedBackstage.getSink,
  19260. tooltipDom: {
  19261. tag: 'div',
  19262. classes: ['tox-tooltip-worker-container']
  19263. },
  19264. tooltipComponents: [],
  19265. anchor: (comp) => ({
  19266. type: 'submenu',
  19267. item: comp,
  19268. overrides: {
  19269. // NOTE: this avoids it setting overflow and max-height.
  19270. maxHeightFunction: expandable$1
  19271. }
  19272. }),
  19273. mode: 'follow-highlight',
  19274. onShow: (component, _tooltip) => {
  19275. tooltipWorker((elm) => {
  19276. Tooltipping.setComponents(component, [
  19277. external({ element: SugarElement.fromDom(elm) })
  19278. ]);
  19279. });
  19280. }
  19281. })
  19282. ])
  19283. .getOrThunk(() => {
  19284. return tooltipText.map((text) => [
  19285. Tooltipping.config({
  19286. ...sharedBackstage.providers.tooltips.getConfig({
  19287. tooltipText: text
  19288. }),
  19289. mode: 'follow-highlight'
  19290. })
  19291. ]).getOr([]);
  19292. });
  19293. const encodeText = (text) => global$9.DOM.encode(text);
  19294. const replaceText = (text, matchText) => {
  19295. const translated = global$6.translate(text);
  19296. const encoded = encodeText(translated);
  19297. if (matchText.length > 0) {
  19298. const escapedMatchRegex = new RegExp(escape(matchText), 'gi');
  19299. return encoded.replace(escapedMatchRegex, (match) => `<span class="tox-autocompleter-highlight">${match}</span>`);
  19300. }
  19301. else {
  19302. return encoded;
  19303. }
  19304. };
  19305. const renderAutocompleteItem = (spec, matchText, useText, presets, onItemValueHandler, itemResponse, sharedBackstage, renderIcons = true) => {
  19306. const structure = renderItemStructure({
  19307. presets,
  19308. textContent: Optional.none(),
  19309. htmlContent: useText ? spec.text.map((text) => replaceText(text, matchText)) : Optional.none(),
  19310. ariaLabel: spec.text,
  19311. labelContent: Optional.none(),
  19312. iconContent: spec.icon,
  19313. shortcutContent: Optional.none(),
  19314. checkMark: Optional.none(),
  19315. caret: Optional.none(),
  19316. value: spec.value
  19317. }, sharedBackstage.providers, renderIcons, spec.icon);
  19318. const tooltipString = spec.text.filter((text) => !useText && text !== '');
  19319. return renderCommonItem({
  19320. context: 'mode:design',
  19321. data: buildData(spec),
  19322. enabled: spec.enabled,
  19323. getApi: constant$1({}),
  19324. onAction: (_api) => onItemValueHandler(spec.value, spec.meta),
  19325. onSetup: constant$1(noop),
  19326. triggersSubmenu: false,
  19327. itemBehaviours: tooltipBehaviour(spec, sharedBackstage, tooltipString)
  19328. }, structure, itemResponse, sharedBackstage.providers);
  19329. };
  19330. const render$2 = (items, extras) => map$2(items, (item) => {
  19331. switch (item.type) {
  19332. case 'cardcontainer':
  19333. return renderContainer(item, render$2(item.items, extras));
  19334. case 'cardimage':
  19335. return renderImage(item.src, item.classes, item.alt);
  19336. case 'cardtext':
  19337. // Only highlight targeted text components
  19338. const shouldHighlight = item.name.exists((name) => contains$2(extras.cardText.highlightOn, name));
  19339. const matchText = shouldHighlight ? Optional.from(extras.cardText.matchText).getOr('') : '';
  19340. return renderHtml(replaceText(item.text, matchText), item.classes);
  19341. }
  19342. });
  19343. const renderCardMenuItem = (spec, itemResponse, sharedBackstage, extras) => {
  19344. const getApi = (component) => ({
  19345. isEnabled: () => !Disabling.isDisabled(component),
  19346. setEnabled: (state) => {
  19347. Disabling.set(component, !state);
  19348. // Disable sub components
  19349. each$1(descendants(component.element, '*'), (elm) => {
  19350. component.getSystem().getByDom(elm).each((comp) => {
  19351. if (comp.hasConfigured(Disabling)) {
  19352. Disabling.set(comp, !state);
  19353. }
  19354. });
  19355. });
  19356. }
  19357. });
  19358. const structure = {
  19359. dom: renderItemDomStructure(spec.label, []),
  19360. optComponents: [
  19361. Optional.some({
  19362. dom: {
  19363. tag: 'div',
  19364. classes: [containerClass, containerRowClass]
  19365. },
  19366. components: render$2(spec.items, extras)
  19367. })
  19368. ]
  19369. };
  19370. return renderCommonItem({
  19371. context: 'mode:design',
  19372. data: buildData({ text: Optional.none(), ...spec }),
  19373. enabled: spec.enabled,
  19374. getApi,
  19375. onAction: spec.onAction,
  19376. onSetup: spec.onSetup,
  19377. triggersSubmenu: false,
  19378. itemBehaviours: Optional.from(extras.itemBehaviours).getOr([])
  19379. }, structure, itemResponse, sharedBackstage.providers);
  19380. };
  19381. const renderChoiceItem = (spec, useText, presets, onItemValueHandler, isSelected, itemResponse, providersBackstage, renderIcons = true) => {
  19382. const getApi = (component) => ({
  19383. setActive: (state) => {
  19384. Toggling.set(component, state);
  19385. },
  19386. isActive: () => Toggling.isOn(component),
  19387. isEnabled: () => !Disabling.isDisabled(component),
  19388. setEnabled: (state) => Disabling.set(component, !state)
  19389. });
  19390. const structure = renderItemStructure({
  19391. presets,
  19392. textContent: useText ? spec.text : Optional.none(),
  19393. htmlContent: Optional.none(),
  19394. labelContent: spec.label,
  19395. ariaLabel: spec.text,
  19396. iconContent: spec.icon,
  19397. shortcutContent: useText ? spec.shortcut : Optional.none(),
  19398. // useText essentially says that we have one column. In one column lists, we should show a tick
  19399. // The tick is controlled by the tickedClass (via css). It is always present
  19400. // but is hidden unless the tickedClass is present.
  19401. checkMark: useText ? Optional.some(renderCheckmark(providersBackstage.icons)) : Optional.none(),
  19402. caret: Optional.none(),
  19403. value: spec.value
  19404. }, providersBackstage, renderIcons);
  19405. const optTooltipping = spec.text
  19406. .filter(constant$1(!useText))
  19407. .map((t) => Tooltipping.config(providersBackstage.tooltips.getConfig({
  19408. tooltipText: providersBackstage.translate(t)
  19409. })));
  19410. return deepMerge(renderCommonItem({
  19411. context: spec.context,
  19412. data: buildData(spec),
  19413. enabled: spec.enabled,
  19414. getApi,
  19415. onAction: (_api) => onItemValueHandler(spec.value),
  19416. onSetup: (api) => {
  19417. api.setActive(isSelected);
  19418. return noop;
  19419. },
  19420. triggersSubmenu: false,
  19421. itemBehaviours: [
  19422. ...optTooltipping.toArray()
  19423. ]
  19424. }, structure, itemResponse, providersBackstage), {
  19425. toggling: {
  19426. toggleClass: tickedClass,
  19427. toggleOnExecute: false,
  19428. selected: spec.active,
  19429. exclusive: true
  19430. }
  19431. });
  19432. };
  19433. const hexColour = (value) => ({
  19434. value: normalizeHex(value)
  19435. });
  19436. const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
  19437. const longformRegex = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i;
  19438. const isHexString = (hex) => shorthandRegex.test(hex) || longformRegex.test(hex);
  19439. const normalizeHex = (hex) => removeLeading(hex, '#').toUpperCase();
  19440. const fromString$1 = (hex) => isHexString(hex) ? Optional.some({ value: normalizeHex(hex) }) : Optional.none();
  19441. // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
  19442. const getLongForm = (hex) => {
  19443. const hexString = hex.value.replace(shorthandRegex, (m, r, g, b) => r + r + g + g + b + b);
  19444. return { value: hexString };
  19445. };
  19446. const extractValues = (hex) => {
  19447. const longForm = getLongForm(hex);
  19448. const splitForm = longformRegex.exec(longForm.value);
  19449. return splitForm === null ? ['FFFFFF', 'FF', 'FF', 'FF'] : splitForm;
  19450. };
  19451. const toHex = (component) => {
  19452. const hex = component.toString(16);
  19453. return (hex.length === 1 ? '0' + hex : hex).toUpperCase();
  19454. };
  19455. const fromRgba = (rgbaColour) => {
  19456. const value = toHex(rgbaColour.red) + toHex(rgbaColour.green) + toHex(rgbaColour.blue);
  19457. return hexColour(value);
  19458. };
  19459. const hsvColour = (hue, saturation, value) => ({
  19460. hue,
  19461. saturation,
  19462. value
  19463. });
  19464. const fromRgb = (rgbaColour) => {
  19465. let h = 0;
  19466. let s = 0;
  19467. let v = 0;
  19468. const r = rgbaColour.red / 255;
  19469. const g = rgbaColour.green / 255;
  19470. const b = rgbaColour.blue / 255;
  19471. const minRGB = Math.min(r, Math.min(g, b));
  19472. const maxRGB = Math.max(r, Math.max(g, b));
  19473. if (minRGB === maxRGB) {
  19474. v = minRGB;
  19475. return hsvColour(0, 0, v * 100);
  19476. }
  19477. /* eslint no-nested-ternary:0 */
  19478. const d = (r === minRGB) ? g - b : ((b === minRGB) ? r - g : b - r);
  19479. h = (r === minRGB) ? 3 : ((b === minRGB) ? 1 : 5);
  19480. h = 60 * (h - d / (maxRGB - minRGB));
  19481. s = (maxRGB - minRGB) / maxRGB;
  19482. v = maxRGB;
  19483. return hsvColour(Math.round(h), Math.round(s * 100), Math.round(v * 100));
  19484. };
  19485. const min = Math.min;
  19486. const max = Math.max;
  19487. const round$1 = Math.round;
  19488. const rgbRegex = /^\s*rgb\s*\(\s*(\d+)\s*[,\s]\s*(\d+)\s*[,\s]\s*(\d+)\s*\)\s*$/i;
  19489. // This regex will match rgba(0, 0, 0, 0.5) or rgba(0, 0, 0, 50%) , or without commas
  19490. const rgbaRegex = /^\s*rgba\s*\(\s*(\d+)\s*[,\s]\s*(\d+)\s*[,\s]\s*(\d+)\s*[,\s]\s*((?:\d?\.\d+|\d+)%?)\s*\)\s*$/i;
  19491. const rgbaColour = (red, green, blue, alpha) => ({
  19492. red,
  19493. green,
  19494. blue,
  19495. alpha
  19496. });
  19497. const isRgbaComponent = (value) => {
  19498. const num = parseInt(value, 10);
  19499. return num.toString() === value && num >= 0 && num <= 255;
  19500. };
  19501. const fromHsv = (hsv) => {
  19502. let r;
  19503. let g;
  19504. let b;
  19505. const hue = (hsv.hue || 0) % 360;
  19506. let saturation = hsv.saturation / 100;
  19507. let brightness = hsv.value / 100;
  19508. saturation = max(0, min(saturation, 1));
  19509. brightness = max(0, min(brightness, 1));
  19510. if (saturation === 0) {
  19511. r = g = b = round$1(255 * brightness);
  19512. return rgbaColour(r, g, b, 1);
  19513. }
  19514. const side = hue / 60;
  19515. const chroma = brightness * saturation;
  19516. const x = chroma * (1 - Math.abs(side % 2 - 1));
  19517. const match = brightness - chroma;
  19518. switch (Math.floor(side)) {
  19519. case 0:
  19520. r = chroma;
  19521. g = x;
  19522. b = 0;
  19523. break;
  19524. case 1:
  19525. r = x;
  19526. g = chroma;
  19527. b = 0;
  19528. break;
  19529. case 2:
  19530. r = 0;
  19531. g = chroma;
  19532. b = x;
  19533. break;
  19534. case 3:
  19535. r = 0;
  19536. g = x;
  19537. b = chroma;
  19538. break;
  19539. case 4:
  19540. r = x;
  19541. g = 0;
  19542. b = chroma;
  19543. break;
  19544. case 5:
  19545. r = chroma;
  19546. g = 0;
  19547. b = x;
  19548. break;
  19549. default:
  19550. r = g = b = 0;
  19551. }
  19552. r = round$1(255 * (r + match));
  19553. g = round$1(255 * (g + match));
  19554. b = round$1(255 * (b + match));
  19555. return rgbaColour(r, g, b, 1);
  19556. };
  19557. // Temporarily using: https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
  19558. const fromHex = (hexColour) => {
  19559. const result = extractValues(hexColour);
  19560. const red = parseInt(result[1], 16);
  19561. const green = parseInt(result[2], 16);
  19562. const blue = parseInt(result[3], 16);
  19563. return rgbaColour(red, green, blue, 1);
  19564. };
  19565. const fromStringValues = (red, green, blue, alpha) => {
  19566. const r = parseInt(red, 10);
  19567. const g = parseInt(green, 10);
  19568. const b = parseInt(blue, 10);
  19569. const a = parseFloat(alpha);
  19570. return rgbaColour(r, g, b, a);
  19571. };
  19572. const fromString = (rgbaString) => {
  19573. const rgbMatch = rgbRegex.exec(rgbaString);
  19574. if (rgbMatch !== null) {
  19575. return Optional.some(fromStringValues(rgbMatch[1], rgbMatch[2], rgbMatch[3], '1'));
  19576. }
  19577. const rgbaMatch = rgbaRegex.exec(rgbaString);
  19578. if (rgbaMatch !== null) {
  19579. return Optional.some(fromStringValues(rgbaMatch[1], rgbaMatch[2], rgbaMatch[3], rgbaMatch[4]));
  19580. }
  19581. return Optional.none();
  19582. };
  19583. const toString = (rgba) => `rgba(${rgba.red},${rgba.green},${rgba.blue},${rgba.alpha})`;
  19584. const red = rgbaColour(255, 0, 0, 1);
  19585. const hexToHsv = (hex) => fromRgb(fromHex(hex));
  19586. const hsvToHex = (hsv) => fromRgba(fromHsv(hsv));
  19587. const anyToHex = (color) => fromString$1(color)
  19588. .orThunk(() => fromString(color).map(fromRgba))
  19589. .getOrThunk(() => {
  19590. // Not dealing with Hex or RGBA so use a canvas to parse the color
  19591. const canvas = document.createElement('canvas');
  19592. canvas.height = 1;
  19593. canvas.width = 1;
  19594. const canvasContext = canvas.getContext('2d');
  19595. // all valid colors after this point
  19596. canvasContext.clearRect(0, 0, canvas.width, canvas.height);
  19597. // invalid colors will be shown as white - the first assignment will pass and the second may be ignored
  19598. canvasContext.fillStyle = '#FFFFFF';
  19599. canvasContext.fillStyle = color;
  19600. canvasContext.fillRect(0, 0, 1, 1);
  19601. const rgba = canvasContext.getImageData(0, 0, 1, 1).data;
  19602. const r = rgba[0];
  19603. const g = rgba[1];
  19604. const b = rgba[2];
  19605. const a = rgba[3];
  19606. return fromRgba(rgbaColour(r, g, b, a));
  19607. });
  19608. const fireSkinLoaded$1 = (editor) => {
  19609. editor.dispatch('SkinLoaded');
  19610. };
  19611. const fireSkinLoadError$1 = (editor, error) => {
  19612. editor.dispatch('SkinLoadError', error);
  19613. };
  19614. const fireResizeEditor = (editor) => {
  19615. editor.dispatch('ResizeEditor');
  19616. };
  19617. const fireResizeContent = (editor, e) => {
  19618. editor.dispatch('ResizeContent', e);
  19619. };
  19620. const fireScrollContent = (editor, e) => {
  19621. editor.dispatch('ScrollContent', e);
  19622. };
  19623. const fireTextColorChange = (editor, data) => {
  19624. editor.dispatch('TextColorChange', data);
  19625. };
  19626. const fireAfterProgressState = (editor, state) => {
  19627. editor.dispatch('AfterProgressState', { state });
  19628. };
  19629. const fireResolveName = (editor, node) => editor.dispatch('ResolveName', {
  19630. name: node.nodeName.toLowerCase(),
  19631. target: node
  19632. });
  19633. const fireToggleToolbarDrawer = (editor, state) => {
  19634. editor.dispatch('ToggleToolbarDrawer', { state });
  19635. };
  19636. const fireStylesTextUpdate = (editor, data) => {
  19637. editor.dispatch('StylesTextUpdate', data);
  19638. };
  19639. const fireAlignTextUpdate = (editor, data) => {
  19640. editor.dispatch('AlignTextUpdate', data);
  19641. };
  19642. const fireFontSizeTextUpdate = (editor, data) => {
  19643. editor.dispatch('FontSizeTextUpdate', data);
  19644. };
  19645. const fireFontSizeInputTextUpdate = (editor, data) => {
  19646. editor.dispatch('FontSizeInputTextUpdate', data);
  19647. };
  19648. const fireBlocksTextUpdate = (editor, data) => {
  19649. editor.dispatch('BlocksTextUpdate', data);
  19650. };
  19651. const fireFontFamilyTextUpdate = (editor, data) => {
  19652. editor.dispatch('FontFamilyTextUpdate', data);
  19653. };
  19654. const fireToggleSidebar = (editor) => {
  19655. editor.dispatch('ToggleSidebar');
  19656. };
  19657. const fireToggleView = (editor) => {
  19658. editor.dispatch('ToggleView');
  19659. };
  19660. const fireContextToolbarClose = (editor) => {
  19661. editor.dispatch('ContextToolbarClose');
  19662. };
  19663. const fireContextFormSlideBack = (editor) => {
  19664. editor.dispatch('ContextFormSlideBack');
  19665. };
  19666. const composeUnbinders = (f, g) => () => {
  19667. f();
  19668. g();
  19669. };
  19670. const onSetupEditableToggle = (editor, enabledPredicate = always) => onSetupEvent(editor, 'NodeChange', (api) => {
  19671. api.setEnabled(editor.selection.isEditable() && enabledPredicate());
  19672. });
  19673. const onSetupFormatToggle = (editor, name) => (api) => {
  19674. const boundFormatChangeCallback = unbindable();
  19675. const init = () => {
  19676. api.setActive(editor.formatter.match(name));
  19677. const binding = editor.formatter.formatChanged(name, api.setActive);
  19678. boundFormatChangeCallback.set(binding);
  19679. };
  19680. // The editor may or may not have been setup yet, so check for that
  19681. editor.initialized ? init() : editor.once('init', init);
  19682. return () => {
  19683. editor.off('init', init);
  19684. boundFormatChangeCallback.clear();
  19685. };
  19686. };
  19687. const onSetupStateToggle = (editor, name) => (api) => {
  19688. const unbindEditableToogle = onSetupEditableToggle(editor)(api);
  19689. const unbindFormatToggle = onSetupFormatToggle(editor, name)(api);
  19690. return () => {
  19691. unbindEditableToogle();
  19692. unbindFormatToggle();
  19693. };
  19694. };
  19695. const onSetupEvent = (editor, event, f) => (api) => {
  19696. const handleEvent = () => f(api);
  19697. const init = () => {
  19698. f(api);
  19699. editor.on(event, handleEvent);
  19700. };
  19701. // The editor may or may not have been setup yet, so check for that
  19702. editor.initialized ? init() : editor.once('init', init);
  19703. return () => {
  19704. editor.off('init', init);
  19705. editor.off(event, handleEvent);
  19706. };
  19707. };
  19708. const onActionToggleFormat$1 = (editor) => (rawItem) => () => {
  19709. editor.undoManager.transact(() => {
  19710. editor.focus();
  19711. editor.execCommand('mceToggleFormat', false, rawItem.format);
  19712. });
  19713. };
  19714. const onActionExecCommand = (editor, command) => () => editor.execCommand(command);
  19715. var global$5 = tinymce.util.Tools.resolve('tinymce.util.LocalStorage');
  19716. const cacheStorage = {};
  19717. const ColorCache = (storageId, max = 10) => {
  19718. const storageString = global$5.getItem(storageId);
  19719. const localstorage = isString(storageString) ? JSON.parse(storageString) : [];
  19720. const prune = (list) => {
  19721. // When the localStorage cache is too big,
  19722. // remove the difference from the tail (head is fresh, tail is stale!)
  19723. const diff = max - list.length;
  19724. return (diff < 0) ? list.slice(0, max) : list;
  19725. };
  19726. const cache = prune(localstorage);
  19727. const add = (key) => {
  19728. // Remove duplicates first.
  19729. indexOf(cache, key).each(remove);
  19730. cache.unshift(key);
  19731. // When max size is exceeded, the oldest colors will be removed
  19732. if (cache.length > max) {
  19733. cache.pop();
  19734. }
  19735. global$5.setItem(storageId, JSON.stringify(cache));
  19736. };
  19737. const remove = (idx) => {
  19738. cache.splice(idx, 1);
  19739. };
  19740. const state = () => cache.slice(0);
  19741. return {
  19742. add,
  19743. state
  19744. };
  19745. };
  19746. const getCacheForId = (id) => get$h(cacheStorage, id).getOrThunk(() => {
  19747. const storageId = `tinymce-custom-colors-${id}`;
  19748. const currentData = global$5.getItem(storageId);
  19749. if (isNullable(currentData)) {
  19750. const legacyDefault = global$5.getItem('tinymce-custom-colors');
  19751. global$5.setItem(storageId, isNonNullable(legacyDefault) ? legacyDefault : '[]');
  19752. }
  19753. const storage = ColorCache(storageId, 10);
  19754. cacheStorage[id] = storage;
  19755. return storage;
  19756. });
  19757. const getCurrentColors = (id) => map$2(getCacheForId(id).state(), (color) => ({
  19758. type: 'choiceitem',
  19759. text: color,
  19760. icon: 'checkmark',
  19761. value: color
  19762. }));
  19763. const addColor = (id, color) => {
  19764. getCacheForId(id).add(color);
  19765. };
  19766. const foregroundId = 'forecolor';
  19767. const backgroundId = 'hilitecolor';
  19768. const fallbackCols = 5;
  19769. const mapColors = (colorMap) => mapColorsRaw(colorMap.map((color, index) => {
  19770. if (index % 2 === 0) {
  19771. return '#' + anyToHex(color).value;
  19772. }
  19773. return color;
  19774. }));
  19775. const mapColorsRaw = (colorMap) => {
  19776. const colors = [];
  19777. for (let i = 0; i < colorMap.length; i += 2) {
  19778. colors.push({
  19779. text: colorMap[i + 1],
  19780. value: colorMap[i],
  19781. icon: 'checkmark',
  19782. type: 'choiceitem'
  19783. });
  19784. }
  19785. return colors;
  19786. };
  19787. const option$1 = (name) => (editor) => editor.options.get(name);
  19788. const fallbackColor = '#000000';
  19789. const register$e = (editor) => {
  19790. const registerOption = editor.options.register;
  19791. const colorProcessor = (value) => {
  19792. if (isArrayOf(value, isString)) {
  19793. return { value: mapColors(value), valid: true };
  19794. }
  19795. else {
  19796. return { valid: false, message: 'Must be an array of strings.' };
  19797. }
  19798. };
  19799. const colorProcessorRaw = (value) => {
  19800. if (isArrayOf(value, isString)) {
  19801. return { value: mapColorsRaw(value), valid: true };
  19802. }
  19803. else {
  19804. return { valid: false, message: 'Must be an array of strings.' };
  19805. }
  19806. };
  19807. const colorColsProcessor = (value) => {
  19808. if (isNumber(value) && value > 0) {
  19809. return { value, valid: true };
  19810. }
  19811. else {
  19812. return { valid: false, message: 'Must be a positive number.' };
  19813. }
  19814. };
  19815. registerOption('color_map', {
  19816. processor: colorProcessor,
  19817. default: [
  19818. '#BFEDD2', 'Light Green',
  19819. '#FBEEB8', 'Light Yellow',
  19820. '#F8CAC6', 'Light Red',
  19821. '#ECCAFA', 'Light Purple',
  19822. '#C2E0F4', 'Light Blue',
  19823. '#2DC26B', 'Green',
  19824. '#F1C40F', 'Yellow',
  19825. '#E03E2D', 'Red',
  19826. '#B96AD9', 'Purple',
  19827. '#3598DB', 'Blue',
  19828. '#169179', 'Dark Turquoise',
  19829. '#E67E23', 'Orange',
  19830. '#BA372A', 'Dark Red',
  19831. '#843FA1', 'Dark Purple',
  19832. '#236FA1', 'Dark Blue',
  19833. '#ECF0F1', 'Light Gray',
  19834. '#CED4D9', 'Medium Gray',
  19835. '#95A5A6', 'Gray',
  19836. '#7E8C8D', 'Dark Gray',
  19837. '#34495E', 'Navy Blue',
  19838. '#000000', 'Black',
  19839. '#ffffff', 'White'
  19840. ]
  19841. });
  19842. registerOption('color_map_raw', {
  19843. processor: colorProcessorRaw,
  19844. });
  19845. registerOption('color_map_background', {
  19846. processor: colorProcessor
  19847. });
  19848. registerOption('color_map_foreground', {
  19849. processor: colorProcessor
  19850. });
  19851. registerOption('color_cols', {
  19852. processor: colorColsProcessor,
  19853. default: calcCols(editor)
  19854. });
  19855. registerOption('color_cols_foreground', {
  19856. processor: colorColsProcessor,
  19857. default: defaultCols(editor, foregroundId)
  19858. });
  19859. registerOption('color_cols_background', {
  19860. processor: colorColsProcessor,
  19861. default: defaultCols(editor, backgroundId)
  19862. });
  19863. registerOption('custom_colors', {
  19864. processor: 'boolean',
  19865. default: true
  19866. });
  19867. registerOption('color_default_foreground', {
  19868. processor: 'string',
  19869. default: fallbackColor
  19870. });
  19871. registerOption('color_default_background', {
  19872. processor: 'string',
  19873. default: fallbackColor
  19874. });
  19875. };
  19876. const getColors$2 = (editor, id) => {
  19877. if (id === foregroundId && editor.options.isSet('color_map_foreground')) {
  19878. return option$1('color_map_foreground')(editor);
  19879. }
  19880. else if (id === backgroundId && editor.options.isSet('color_map_background')) {
  19881. return option$1('color_map_background')(editor);
  19882. }
  19883. else if (editor.options.isSet('color_map_raw')) {
  19884. return option$1('color_map_raw')(editor);
  19885. }
  19886. else {
  19887. return option$1('color_map')(editor);
  19888. }
  19889. };
  19890. const calcCols = (editor, id = 'default') => Math.max(fallbackCols, Math.ceil(Math.sqrt(getColors$2(editor, id).length)));
  19891. const defaultCols = (editor, id) => {
  19892. const defaultCols = option$1('color_cols')(editor);
  19893. const calculatedCols = calcCols(editor, id);
  19894. if (defaultCols === calcCols(editor)) {
  19895. return calculatedCols;
  19896. }
  19897. else {
  19898. return defaultCols;
  19899. }
  19900. };
  19901. const getColorCols$1 = (editor, id = 'default') => {
  19902. const getCols = () => {
  19903. if (id === foregroundId) {
  19904. return option$1('color_cols_foreground')(editor);
  19905. }
  19906. else if (id === backgroundId) {
  19907. return option$1('color_cols_background')(editor);
  19908. }
  19909. else {
  19910. return option$1('color_cols')(editor);
  19911. }
  19912. };
  19913. return Math.round(getCols());
  19914. };
  19915. const hasCustomColors$1 = option$1('custom_colors');
  19916. const getDefaultForegroundColor = option$1('color_default_foreground');
  19917. const getDefaultBackgroundColor = option$1('color_default_background');
  19918. const defaultBackgroundColor = 'rgba(0, 0, 0, 0)';
  19919. const isValidBackgroundColor = (value) => fromString(value).exists((c) => c.alpha !== 0);
  19920. // Climb up the tree to find the value of the background until finding a non-transparent value or defaulting.
  19921. const getClosestCssBackgroundColorValue = (scope) => {
  19922. return closest(scope, (node) => {
  19923. if (isElement$1(node)) {
  19924. const color = get$e(node, 'background-color');
  19925. return someIf(isValidBackgroundColor(color), color);
  19926. }
  19927. else {
  19928. return Optional.none();
  19929. }
  19930. }).getOr(defaultBackgroundColor);
  19931. };
  19932. const getCurrentColor = (editor, format) => {
  19933. const node = SugarElement.fromDom(editor.selection.getStart());
  19934. const cssRgbValue = format === 'hilitecolor'
  19935. ? getClosestCssBackgroundColorValue(node)
  19936. : get$e(node, 'color');
  19937. return fromString(cssRgbValue).map((rgba) => '#' + fromRgba(rgba).value);
  19938. };
  19939. const applyFormat = (editor, format, value) => {
  19940. editor.undoManager.transact(() => {
  19941. editor.focus();
  19942. editor.formatter.apply(format, { value });
  19943. editor.nodeChanged();
  19944. });
  19945. };
  19946. const removeFormat = (editor, format) => {
  19947. editor.undoManager.transact(() => {
  19948. editor.focus();
  19949. editor.formatter.remove(format, { value: null }, undefined, true);
  19950. editor.nodeChanged();
  19951. });
  19952. };
  19953. const registerCommands = (editor) => {
  19954. editor.addCommand('mceApplyTextcolor', (format, value) => {
  19955. applyFormat(editor, format, value);
  19956. });
  19957. editor.addCommand('mceRemoveTextcolor', (format) => {
  19958. removeFormat(editor, format);
  19959. });
  19960. };
  19961. const getAdditionalColors = (hasCustom) => {
  19962. const type = 'choiceitem';
  19963. const remove = {
  19964. type,
  19965. text: 'Remove color',
  19966. icon: 'color-swatch-remove-color',
  19967. value: 'remove'
  19968. };
  19969. const custom = {
  19970. type,
  19971. text: 'Custom color',
  19972. icon: 'color-picker',
  19973. value: 'custom'
  19974. };
  19975. return hasCustom ? [
  19976. remove,
  19977. custom
  19978. ] : [remove];
  19979. };
  19980. const applyColor = (editor, format, value, onChoice) => {
  19981. if (value === 'custom') {
  19982. const dialog = colorPickerDialog(editor);
  19983. dialog((colorOpt) => {
  19984. colorOpt.each((color) => {
  19985. addColor(format, color);
  19986. editor.execCommand('mceApplyTextcolor', format, color);
  19987. onChoice(color);
  19988. });
  19989. }, getCurrentColor(editor, format).getOr(fallbackColor));
  19990. }
  19991. else if (value === 'remove') {
  19992. onChoice('');
  19993. editor.execCommand('mceRemoveTextcolor', format);
  19994. }
  19995. else {
  19996. onChoice(value);
  19997. editor.execCommand('mceApplyTextcolor', format, value);
  19998. }
  19999. };
  20000. const getColors$1 = (colors, id, hasCustom) => colors.concat(getCurrentColors(id).concat(getAdditionalColors(hasCustom)));
  20001. const getFetch$1 = (colors, id, hasCustom) => (callback) => {
  20002. callback(getColors$1(colors, id, hasCustom));
  20003. };
  20004. const setIconColor = (splitButtonApi, name, newColor) => {
  20005. const id = name === 'forecolor' ? 'tox-icon-text-color__color' : 'tox-icon-highlight-bg-color__color';
  20006. splitButtonApi.setIconFill(id, newColor);
  20007. };
  20008. const setTooltip = (buttonApi, tooltip) => {
  20009. buttonApi.setTooltip(tooltip);
  20010. };
  20011. const select$1 = (editor, format) => (value) => {
  20012. const optCurrentHex = getCurrentColor(editor, format);
  20013. return is$1(optCurrentHex, value.toUpperCase());
  20014. };
  20015. // Selecting `Remove Color` would set the lastColor to ''
  20016. const getToolTipText = (editor, format, lastColor) => {
  20017. if (isEmpty(lastColor)) {
  20018. return format === 'forecolor' ? 'Text color' : 'Background color';
  20019. }
  20020. const tooltipPrefix = format === 'forecolor' ? 'Text color {0}' : 'Background color {0}';
  20021. const colors = getColors$1(getColors$2(editor, format), format, false);
  20022. const colorText = find$5(colors, (c) => c.value === lastColor).getOr({ text: '' }).text;
  20023. return editor.translate([tooltipPrefix, editor.translate(colorText)]);
  20024. };
  20025. const registerTextColorButton = (editor, name, format, lastColor) => {
  20026. editor.ui.registry.addSplitButton(name, {
  20027. tooltip: getToolTipText(editor, format, lastColor.get()),
  20028. chevronTooltip: name === 'forecolor' ? 'Text color menu' : 'Background color menu',
  20029. presets: 'color',
  20030. icon: name === 'forecolor' ? 'text-color' : 'highlight-bg-color',
  20031. select: select$1(editor, format),
  20032. columns: getColorCols$1(editor, format),
  20033. fetch: getFetch$1(getColors$2(editor, format), format, hasCustomColors$1(editor)),
  20034. onAction: (_splitButtonApi) => {
  20035. applyColor(editor, format, lastColor.get(), noop);
  20036. },
  20037. onItemAction: (_splitButtonApi, value) => {
  20038. applyColor(editor, format, value, (newColor) => {
  20039. lastColor.set(newColor);
  20040. fireTextColorChange(editor, {
  20041. name,
  20042. color: newColor
  20043. });
  20044. });
  20045. },
  20046. onSetup: (splitButtonApi) => {
  20047. setIconColor(splitButtonApi, name, lastColor.get());
  20048. const handler = (e) => {
  20049. if (e.name === name) {
  20050. setIconColor(splitButtonApi, e.name, e.color);
  20051. setTooltip(splitButtonApi, getToolTipText(editor, format, e.color));
  20052. }
  20053. };
  20054. editor.on('TextColorChange', handler);
  20055. return composeUnbinders(onSetupEditableToggle(editor)(splitButtonApi), () => {
  20056. editor.off('TextColorChange', handler);
  20057. });
  20058. }
  20059. });
  20060. };
  20061. const registerTextColorMenuItem = (editor, name, format, text, lastColor) => {
  20062. editor.ui.registry.addNestedMenuItem(name, {
  20063. text,
  20064. icon: name === 'forecolor' ? 'text-color' : 'highlight-bg-color',
  20065. onSetup: (api) => {
  20066. setTooltip(api, getToolTipText(editor, format, lastColor.get()));
  20067. setIconColor(api, name, lastColor.get());
  20068. return onSetupEditableToggle(editor)(api);
  20069. },
  20070. getSubmenuItems: () => [
  20071. {
  20072. type: 'fancymenuitem',
  20073. fancytype: 'colorswatch',
  20074. select: select$1(editor, format),
  20075. initData: {
  20076. storageKey: format,
  20077. },
  20078. onAction: (data) => {
  20079. applyColor(editor, format, data.value, (newColor) => {
  20080. lastColor.set(newColor);
  20081. fireTextColorChange(editor, {
  20082. name,
  20083. color: newColor
  20084. });
  20085. });
  20086. },
  20087. }
  20088. ]
  20089. });
  20090. };
  20091. const colorPickerDialog = (editor) => (callback, value) => {
  20092. let isValid = false;
  20093. const onSubmit = (api) => {
  20094. const data = api.getData();
  20095. const hex = data.colorpicker;
  20096. if (isValid) {
  20097. callback(Optional.from(hex));
  20098. api.close();
  20099. }
  20100. else {
  20101. editor.windowManager.alert(editor.translate(['Invalid hex color code: {0}', hex]));
  20102. }
  20103. };
  20104. const onAction = (_api, details) => {
  20105. if (details.name === 'hex-valid') {
  20106. isValid = details.value;
  20107. }
  20108. };
  20109. const initialData = {
  20110. colorpicker: value
  20111. };
  20112. editor.windowManager.open({
  20113. title: 'Color Picker',
  20114. size: 'normal',
  20115. body: {
  20116. type: 'panel',
  20117. items: [
  20118. {
  20119. type: 'colorpicker',
  20120. name: 'colorpicker',
  20121. label: 'Color'
  20122. }
  20123. ]
  20124. },
  20125. buttons: [
  20126. {
  20127. type: 'cancel',
  20128. name: 'cancel',
  20129. text: 'Cancel'
  20130. },
  20131. {
  20132. type: 'submit',
  20133. name: 'save',
  20134. text: 'Save',
  20135. primary: true
  20136. }
  20137. ],
  20138. initialData,
  20139. onAction,
  20140. onSubmit,
  20141. onClose: noop,
  20142. onCancel: () => {
  20143. callback(Optional.none());
  20144. }
  20145. });
  20146. };
  20147. const register$d = (editor) => {
  20148. registerCommands(editor);
  20149. const fallbackColorForeground = getDefaultForegroundColor(editor);
  20150. const fallbackColorBackground = getDefaultBackgroundColor(editor);
  20151. const lastForeColor = Cell(fallbackColorForeground);
  20152. const lastBackColor = Cell(fallbackColorBackground);
  20153. registerTextColorButton(editor, 'forecolor', 'forecolor', lastForeColor);
  20154. registerTextColorButton(editor, 'backcolor', 'hilitecolor', lastBackColor);
  20155. registerTextColorMenuItem(editor, 'forecolor', 'forecolor', 'Text color', lastForeColor);
  20156. registerTextColorMenuItem(editor, 'backcolor', 'hilitecolor', 'Background color', lastBackColor);
  20157. };
  20158. const renderImgItem = (spec, onItemValueHandler, isSelected, itemResponse, providersBackstage) => {
  20159. const getApi = (component) => ({
  20160. setActive: (state) => {
  20161. Toggling.set(component, state);
  20162. },
  20163. isActive: () => Toggling.isOn(component),
  20164. isEnabled: () => !Disabling.isDisabled(component),
  20165. setEnabled: (state) => Disabling.set(component, !state)
  20166. });
  20167. const structure = renderItemStructure({
  20168. presets: 'img',
  20169. textContent: Optional.none(),
  20170. htmlContent: Optional.none(),
  20171. ariaLabel: spec.tooltip,
  20172. iconContent: Optional.some(spec.url),
  20173. labelContent: spec.label,
  20174. shortcutContent: Optional.none(),
  20175. checkMark: Optional.some(renderCheckmark(providersBackstage.icons)),
  20176. caret: Optional.none(),
  20177. value: spec.value
  20178. }, providersBackstage, true);
  20179. const optTooltipping = spec.tooltip
  20180. .map((t) => Tooltipping.config(providersBackstage.tooltips.getConfig({
  20181. tooltipText: providersBackstage.translate(t)
  20182. })));
  20183. return deepMerge(renderCommonItem({
  20184. context: spec.context,
  20185. data: buildData(spec),
  20186. enabled: spec.enabled,
  20187. getApi,
  20188. onAction: (api) => {
  20189. onItemValueHandler(spec.value);
  20190. api.setActive(true);
  20191. },
  20192. onSetup: (api) => {
  20193. api.setActive(isSelected);
  20194. return noop;
  20195. },
  20196. triggersSubmenu: false,
  20197. itemBehaviours: [
  20198. ...optTooltipping.toArray()
  20199. ]
  20200. }, structure, itemResponse, providersBackstage), {
  20201. toggling: {
  20202. toggleClass: tickedClass,
  20203. toggleOnExecute: false,
  20204. selected: spec.active,
  20205. exclusive: true
  20206. }
  20207. });
  20208. };
  20209. const createPartialChoiceMenu = (value, items, onItemValueHandler, columns, presets, itemResponse, select, providersBackstage) => {
  20210. const hasIcons = menuHasIcons(items);
  20211. const presetItemTypes = presets !== 'color' ? 'normal' : 'color';
  20212. const alloyItems = createChoiceItems(items, onItemValueHandler, columns, presetItemTypes, itemResponse, select, providersBackstage);
  20213. const menuLayout = {
  20214. menuType: presets
  20215. };
  20216. return createPartialMenuWithAlloyItems(value, hasIcons, alloyItems, columns, menuLayout);
  20217. };
  20218. const createChoiceItems = (items, onItemValueHandler, columns, itemPresets, itemResponse, select, providersBackstage) => cat(map$2(items, (item) => {
  20219. if (item.type === 'choiceitem') {
  20220. return createChoiceMenuItem(item).fold(handleError, (d) => Optional.some(renderChoiceItem(d, columns === 1, itemPresets, onItemValueHandler, select(d.value), itemResponse, providersBackstage, menuHasIcons(items))));
  20221. }
  20222. else if (item.type === 'imageitem') {
  20223. return createImageMenuItem(item).fold(handleError, (d) => Optional.some(renderImgItem(d, onItemValueHandler, select(d.value), itemResponse, providersBackstage)));
  20224. }
  20225. else if (item.type === 'resetimage') {
  20226. return createResetImageItem(item).fold(handleError, (d) => Optional.some(renderChoiceItem(({
  20227. ...d,
  20228. type: 'choiceitem',
  20229. text: d.tooltip,
  20230. icon: Optional.some(d.icon),
  20231. label: Optional.some(d.label),
  20232. }), columns === 1, itemPresets, onItemValueHandler, select(d.value), itemResponse, providersBackstage, menuHasIcons(items))));
  20233. }
  20234. else {
  20235. return Optional.none();
  20236. }
  20237. }));
  20238. const deriveMenuMovement = (columns, presets) => {
  20239. const menuMarkers = markers(presets);
  20240. if (columns === 1) {
  20241. return { mode: 'menu', moveOnTab: true };
  20242. }
  20243. else if (columns === 'auto') {
  20244. return {
  20245. mode: 'grid',
  20246. selector: '.' + menuMarkers.item,
  20247. initSize: {
  20248. numColumns: 1,
  20249. numRows: 1
  20250. }
  20251. };
  20252. }
  20253. else {
  20254. const rowClass = {
  20255. color: 'tox-swatches__row',
  20256. imageselector: 'tox-image-selector__row',
  20257. listpreview: 'tox-collection__group',
  20258. normal: 'tox-collection__group'
  20259. }[presets];
  20260. return {
  20261. mode: 'matrix',
  20262. rowSelector: '.' + rowClass,
  20263. previousSelector: (menu) => {
  20264. // We only want the navigation to start on the selected item if we are in color-mode (The colorswatch)
  20265. return presets === 'color'
  20266. ? descendant(menu.element, '[aria-checked=true]')
  20267. : Optional.none();
  20268. }
  20269. };
  20270. }
  20271. };
  20272. const deriveCollectionMovement = (columns, presets) => {
  20273. if (columns === 1) {
  20274. return {
  20275. mode: 'menu',
  20276. moveOnTab: false,
  20277. selector: '.tox-collection__item'
  20278. };
  20279. }
  20280. else if (columns === 'auto') {
  20281. return {
  20282. mode: 'flatgrid',
  20283. selector: '.' + 'tox-collection__item',
  20284. initSize: {
  20285. numColumns: 1,
  20286. numRows: 1
  20287. }
  20288. };
  20289. }
  20290. else {
  20291. return {
  20292. mode: 'matrix',
  20293. selectors: {
  20294. row: presets === 'color' ? '.tox-swatches__row' : '.tox-collection__group',
  20295. cell: presets === 'color' ? `.${colorClass}` : `.${selectableClass}`
  20296. }
  20297. };
  20298. }
  20299. };
  20300. const renderColorSwatchItem = (spec, backstage) => {
  20301. const items = getColorItems(spec, backstage);
  20302. const columns = backstage.colorinput.getColorCols(spec.initData.storageKey);
  20303. const presets = 'color';
  20304. const menuSpec = createPartialChoiceMenu(generate$6('menu-value'), items, (value) => {
  20305. spec.onAction({ value });
  20306. }, columns, presets, ItemResponse$1.CLOSE_ON_EXECUTE, spec.select.getOr(never), backstage.shared.providers);
  20307. const widgetSpec = {
  20308. ...menuSpec,
  20309. markers: markers(presets),
  20310. movement: deriveMenuMovement(columns, presets),
  20311. // TINY-10806: Avoid duplication of ARIA role="menu" in the accessibility tree for Color Swatch menu item.
  20312. showMenuRole: false
  20313. };
  20314. return {
  20315. type: 'widget',
  20316. data: { value: generate$6('widget-id') },
  20317. dom: {
  20318. tag: 'div',
  20319. classes: ['tox-fancymenuitem']
  20320. },
  20321. autofocus: true,
  20322. components: [
  20323. parts$7.widget(Menu.sketch(widgetSpec))
  20324. ]
  20325. };
  20326. };
  20327. const getColorItems = (spec, backstage) => {
  20328. const useCustomColors = spec.initData.allowCustomColors && backstage.colorinput.hasCustomColors();
  20329. return spec.initData.colors.fold(() => getColors$1(backstage.colorinput.getColors(spec.initData.storageKey), spec.initData.storageKey, useCustomColors), (colors) => colors.concat(getAdditionalColors(useCustomColors)));
  20330. };
  20331. const renderImageSelector = (spec, backstage) => {
  20332. const presets = 'imageselector';
  20333. const columns = spec.initData.columns;
  20334. const menuSpec = createPartialChoiceMenu(generate$6('menu-value'), spec.initData.items, (value) => {
  20335. spec.onAction({ value });
  20336. }, columns, presets, ItemResponse$1.CLOSE_ON_EXECUTE, spec.select.getOr(never), backstage.shared.providers);
  20337. const widgetSpec = {
  20338. ...menuSpec,
  20339. markers: markers(presets),
  20340. movement: deriveMenuMovement(columns, presets),
  20341. // TINY-10806: Avoid duplication of ARIA role="menu" in the accessibility tree for Image Selector menu item.
  20342. showMenuRole: false
  20343. };
  20344. return {
  20345. type: 'widget',
  20346. data: { value: generate$6('widget-id') },
  20347. dom: {
  20348. tag: 'div',
  20349. classes: ['tox-fancymenuitem', 'tox-collection--toolbar']
  20350. },
  20351. autofocus: true,
  20352. components: [
  20353. parts$7.widget(Menu.sketch(widgetSpec))
  20354. ]
  20355. };
  20356. };
  20357. const cellOverEvent = generate$6('cell-over');
  20358. const cellExecuteEvent = generate$6('cell-execute');
  20359. const makeAnnouncementText = (backstage) => (row, col) => backstage.shared.providers.translate(['{0} columns, {1} rows', col, row]);
  20360. const makeCell = (row, col, label) => {
  20361. const emitCellOver = (c) => emitWith(c, cellOverEvent, { row, col });
  20362. const emitExecute = (c) => emitWith(c, cellExecuteEvent, { row, col });
  20363. const onClick = (c, se) => {
  20364. se.stop();
  20365. emitExecute(c);
  20366. };
  20367. return build$1({
  20368. dom: {
  20369. tag: 'div',
  20370. attributes: {
  20371. role: 'button',
  20372. ['aria-label']: label
  20373. }
  20374. },
  20375. behaviours: derive$1([
  20376. config('insert-table-picker-cell', [
  20377. run$1(mouseover(), Focusing.focus),
  20378. run$1(execute$5(), emitExecute),
  20379. run$1(click(), onClick),
  20380. run$1(tap(), onClick)
  20381. ]),
  20382. Toggling.config({
  20383. toggleClass: 'tox-insert-table-picker__selected',
  20384. toggleOnExecute: false
  20385. }),
  20386. Focusing.config({ onFocus: emitCellOver })
  20387. ])
  20388. });
  20389. };
  20390. const makeCells = (getCellLabel, numRows, numCols) => {
  20391. const cells = [];
  20392. for (let i = 0; i < numRows; i++) {
  20393. const row = [];
  20394. for (let j = 0; j < numCols; j++) {
  20395. const label = getCellLabel(i + 1, j + 1);
  20396. row.push(makeCell(i, j, label));
  20397. }
  20398. cells.push(row);
  20399. }
  20400. return cells;
  20401. };
  20402. const selectCells = (cells, selectedRow, selectedColumn, numRows, numColumns) => {
  20403. for (let i = 0; i < numRows; i++) {
  20404. for (let j = 0; j < numColumns; j++) {
  20405. Toggling.set(cells[i][j], i <= selectedRow && j <= selectedColumn);
  20406. }
  20407. }
  20408. };
  20409. const makeComponents = (cells) => bind$3(cells, (cellRow) => map$2(cellRow, premade));
  20410. const makeLabelText = (row, col) => text$2(`${col}x${row}`);
  20411. const renderInsertTableMenuItem = (spec, backstage) => {
  20412. const numRows = 10;
  20413. const numColumns = 10;
  20414. const getCellLabel = makeAnnouncementText(backstage);
  20415. const cells = makeCells(getCellLabel, numRows, numColumns);
  20416. const emptyLabelText = makeLabelText(0, 0);
  20417. const memLabel = record({
  20418. dom: {
  20419. tag: 'span',
  20420. classes: ['tox-insert-table-picker__label'],
  20421. },
  20422. components: [emptyLabelText],
  20423. behaviours: derive$1([
  20424. Replacing.config({})
  20425. ])
  20426. });
  20427. return {
  20428. type: 'widget',
  20429. data: { value: generate$6('widget-id') },
  20430. dom: {
  20431. tag: 'div',
  20432. classes: ['tox-fancymenuitem']
  20433. },
  20434. autofocus: true,
  20435. components: [parts$7.widget({
  20436. dom: {
  20437. tag: 'div',
  20438. classes: ['tox-insert-table-picker']
  20439. },
  20440. components: makeComponents(cells).concat(memLabel.asSpec()),
  20441. behaviours: derive$1([
  20442. config('insert-table-picker', [
  20443. runOnAttached((c) => {
  20444. // Restore the empty label when opened, otherwise it may still be using an old label from last time it was opened
  20445. Replacing.set(memLabel.get(c), [emptyLabelText]);
  20446. }),
  20447. runWithTarget(cellOverEvent, (c, t, e) => {
  20448. const { row, col } = e.event;
  20449. selectCells(cells, row, col, numRows, numColumns);
  20450. Replacing.set(memLabel.get(c), [makeLabelText(row + 1, col + 1)]);
  20451. }),
  20452. runWithTarget(cellExecuteEvent, (c, _, e) => {
  20453. const { row, col } = e.event;
  20454. // Close the sandbox before triggering the action
  20455. emit(c, sandboxClose());
  20456. spec.onAction({ numRows: row + 1, numColumns: col + 1 });
  20457. })
  20458. ]),
  20459. Keying.config({
  20460. initSize: {
  20461. numRows,
  20462. numColumns
  20463. },
  20464. mode: 'flatgrid',
  20465. selector: '[role="button"]'
  20466. })
  20467. ])
  20468. })]
  20469. };
  20470. };
  20471. const fancyMenuItems = {
  20472. inserttable: renderInsertTableMenuItem,
  20473. colorswatch: renderColorSwatchItem,
  20474. imageselect: renderImageSelector
  20475. };
  20476. const renderFancyMenuItem = (spec, backstage) => get$h(fancyMenuItems, spec.fancytype).map((render) => render(spec, backstage));
  20477. // Note, this does not create a valid SketchSpec.
  20478. const renderNestedItem = (spec, itemResponse, providersBackstage, renderIcons = true, downwardsCaret = false) => {
  20479. const caret = downwardsCaret ? renderDownwardsCaret(providersBackstage.icons) : renderSubmenuCaret(providersBackstage.icons);
  20480. const getApi = (component) => ({
  20481. isEnabled: () => !Disabling.isDisabled(component),
  20482. setEnabled: (state) => Disabling.set(component, !state),
  20483. setIconFill: (id, value) => {
  20484. descendant(component.element, `svg path[class="${id}"], rect[class="${id}"]`).each((underlinePath) => {
  20485. set$9(underlinePath, 'fill', value);
  20486. });
  20487. },
  20488. setTooltip: (tooltip) => {
  20489. const translatedTooltip = providersBackstage.translate(tooltip);
  20490. set$9(component.element, 'aria-label', translatedTooltip);
  20491. }
  20492. });
  20493. const structure = renderItemStructure({
  20494. presets: 'normal',
  20495. iconContent: spec.icon,
  20496. textContent: spec.text,
  20497. htmlContent: Optional.none(),
  20498. ariaLabel: spec.text,
  20499. labelContent: Optional.none(),
  20500. caret: Optional.some(caret),
  20501. checkMark: Optional.none(),
  20502. shortcutContent: spec.shortcut
  20503. }, providersBackstage, renderIcons);
  20504. return renderCommonItem({
  20505. context: spec.context,
  20506. data: buildData(spec),
  20507. getApi,
  20508. enabled: spec.enabled,
  20509. onAction: noop,
  20510. onSetup: spec.onSetup,
  20511. triggersSubmenu: true,
  20512. itemBehaviours: []
  20513. }, structure, itemResponse, providersBackstage);
  20514. };
  20515. // Note, this does not create a valid SketchSpec.
  20516. const renderNormalItem = (spec, itemResponse, providersBackstage, renderIcons = true) => {
  20517. const getApi = (component) => ({
  20518. isEnabled: () => !Disabling.isDisabled(component),
  20519. setEnabled: (state) => Disabling.set(component, !state)
  20520. });
  20521. const structure = renderItemStructure({
  20522. presets: 'normal',
  20523. iconContent: spec.icon,
  20524. textContent: spec.text,
  20525. htmlContent: Optional.none(),
  20526. labelContent: Optional.none(),
  20527. ariaLabel: spec.text,
  20528. caret: Optional.none(),
  20529. checkMark: Optional.none(),
  20530. shortcutContent: spec.shortcut
  20531. }, providersBackstage, renderIcons);
  20532. return renderCommonItem({
  20533. context: spec.context,
  20534. data: buildData(spec),
  20535. getApi,
  20536. enabled: spec.enabled,
  20537. onAction: spec.onAction,
  20538. onSetup: spec.onSetup,
  20539. triggersSubmenu: false,
  20540. itemBehaviours: []
  20541. }, structure, itemResponse, providersBackstage);
  20542. };
  20543. const renderSeparatorItem = (spec) => ({
  20544. type: 'separator',
  20545. dom: {
  20546. tag: 'div',
  20547. classes: [selectableClass, groupHeadingClass]
  20548. },
  20549. components: spec.text.map(text$2).toArray()
  20550. });
  20551. const renderToggleMenuItem = (spec, itemResponse, providersBackstage, renderIcons = true) => {
  20552. const getApi = (component) => ({
  20553. setActive: (state) => {
  20554. Toggling.set(component, state);
  20555. },
  20556. isActive: () => Toggling.isOn(component),
  20557. isEnabled: () => !Disabling.isDisabled(component),
  20558. setEnabled: (state) => Disabling.set(component, !state)
  20559. });
  20560. // BespokeSelects use meta to pass through styling information. Bespokes should only
  20561. // be togglemenuitems hence meta is only passed through in this MenuItem.
  20562. const structure = renderItemStructure({
  20563. iconContent: spec.icon,
  20564. textContent: spec.text,
  20565. htmlContent: Optional.none(),
  20566. labelContent: Optional.none(),
  20567. ariaLabel: spec.text,
  20568. checkMark: Optional.some(renderCheckmark(providersBackstage.icons)),
  20569. caret: Optional.none(),
  20570. shortcutContent: spec.shortcut,
  20571. presets: 'normal',
  20572. meta: spec.meta
  20573. }, providersBackstage, renderIcons);
  20574. return deepMerge(renderCommonItem({
  20575. context: spec.context,
  20576. data: buildData(spec),
  20577. enabled: spec.enabled,
  20578. getApi,
  20579. onAction: spec.onAction,
  20580. onSetup: spec.onSetup,
  20581. triggersSubmenu: false,
  20582. itemBehaviours: []
  20583. }, structure, itemResponse, providersBackstage), {
  20584. toggling: {
  20585. toggleClass: tickedClass,
  20586. toggleOnExecute: false,
  20587. selected: spec.active
  20588. },
  20589. role: spec.role.getOrUndefined()
  20590. });
  20591. };
  20592. const autocomplete = renderAutocompleteItem;
  20593. const separator$3 = renderSeparatorItem;
  20594. const normal = renderNormalItem;
  20595. const nested = renderNestedItem;
  20596. const toggle = renderToggleMenuItem;
  20597. const fancy = renderFancyMenuItem;
  20598. const card = renderCardMenuItem;
  20599. const identifyMenuLayout = (searchMode) => {
  20600. switch (searchMode.searchMode) {
  20601. case 'no-search': {
  20602. return {
  20603. menuType: 'normal'
  20604. };
  20605. }
  20606. default: {
  20607. return {
  20608. menuType: 'searchable',
  20609. searchMode
  20610. };
  20611. }
  20612. }
  20613. };
  20614. const handleRefetchTrigger = (originalSandboxComp) => {
  20615. // At the moment, a Sandbox is "Represented" by its triggering Dropdown.
  20616. // We'll want to make this an official API, in case we change it later.
  20617. const dropdown = Representing.getValue(originalSandboxComp);
  20618. // Because refetch will replace the entire menu, we need to store the
  20619. // original version of the searcher state, so that we can reinstate it
  20620. // after the fetch completes (which is async)
  20621. const optSearcherState = findWithinSandbox(originalSandboxComp).map(saveState);
  20622. Dropdown.refetch(dropdown).get(() => {
  20623. // It has completed, so now find the searcher and set its value
  20624. // again. We can't just use the originalSandbox, because that will
  20625. // have been thrown away and recreated by now.
  20626. const newSandboxComp = Coupling.getCoupled(dropdown, 'sandbox');
  20627. optSearcherState.each((searcherState) => findWithinSandbox(newSandboxComp).each((inputComp) => restoreState(inputComp, searcherState)));
  20628. });
  20629. };
  20630. // This event is triggered by the searcher for key events
  20631. // that should be handled by the currently selected item
  20632. // (that is, the one with *fake* focus, not real focus). So we
  20633. // need to redispatch them to the selected item in the sandbox.
  20634. const handleRedirectToMenuItem = (sandboxComp, se) => {
  20635. getActiveMenuItemFrom(sandboxComp).each((activeItem) => {
  20636. retargetAndDispatchWith(sandboxComp, activeItem.element, se.event.eventType, se.event.interactionEvent);
  20637. });
  20638. };
  20639. // This function is useful when you have fakeFocus, so you can't just find the
  20640. // currently focused item (or the item that triggered a key event). It relies on
  20641. // the following relationships between components
  20642. // The Sandbox creates a tieredmenu, so Sandboxing.getState returns the TieredMenu
  20643. // The TieredMenu uses Highlighting for managing which menus are active, so
  20644. // Highlighting.getHighlighted(tmenu) is the current active menu
  20645. // The Menu uses highlighting to manage the active item, so use
  20646. // Highlighting.getHighlighted(menu) to get the current item.
  20647. const getActiveMenuItemFrom = (sandboxComp) => {
  20648. // Consider moving some of these things into shared APIs. For example, make an extra API
  20649. // for TieredMenu to get the highlighted item.
  20650. return Sandboxing.getState(sandboxComp)
  20651. .bind(Highlighting.getHighlighted)
  20652. .bind(Highlighting.getHighlighted);
  20653. };
  20654. const getSearchResults = (activeMenuComp) => {
  20655. // Depending on the menu layout, the search results will either be the entire
  20656. // menu, or something within the menu.
  20657. return has(activeMenuComp.element, searchResultsClass)
  20658. ? Optional.some(activeMenuComp.element)
  20659. : descendant(activeMenuComp.element, '.' + searchResultsClass);
  20660. };
  20661. // Model the interaction with ARIA
  20662. const updateAriaOnHighlight = (tmenuComp, menuComp, itemComp) => {
  20663. // This ARIA behaviour is based on the algolia example documented in TINY-8952
  20664. findWithinMenu(tmenuComp).each((inputComp) => {
  20665. setActiveDescendant(inputComp, itemComp);
  20666. const optActiveResults = getSearchResults(menuComp);
  20667. optActiveResults.each((resultsElem) => {
  20668. // Link aria-controls of the input to the id of the results container.
  20669. getOpt(resultsElem, 'id')
  20670. .each((controlledId) => set$9(inputComp.element, 'aria-controls', controlledId));
  20671. });
  20672. });
  20673. // Update the aria-selected on the item. The removal is handled by onDehighlight
  20674. set$9(itemComp.element, 'aria-selected', 'true');
  20675. };
  20676. const updateAriaOnDehighlight = (tmenuComp, menuComp, itemComp) => {
  20677. // This ARIA behaviour is based on the algolia example documented in TINY-8952
  20678. set$9(itemComp.element, 'aria-selected', 'false');
  20679. };
  20680. const focusSearchField = (tmenuComp) => {
  20681. findWithinMenu(tmenuComp).each((searcherComp) => Focusing.focus(searcherComp));
  20682. };
  20683. const getSearchPattern = (dropdownComp) => {
  20684. // Dropdowns are "coupled" with their sandbox and generally, create them on demand.
  20685. // When using "getExistingCoupled" of Coupling, it only returns the coupled
  20686. // component (here: the sandbox) if it already exists ... it won't do any creation.
  20687. // So here, we are trying to get possible fetchContext information for our fetch
  20688. // callback. If there is no sandbox, then there is no open menu, and we
  20689. // don't have any search context, so use an empty string. Otherwise, dive into
  20690. // the sandbox, and find the search field's current pattern.
  20691. const optSandboxComp = Coupling.getExistingCoupled(dropdownComp, 'sandbox');
  20692. return optSandboxComp
  20693. .bind(findWithinSandbox)
  20694. .map(saveState)
  20695. .map((state) => state.fetchPattern)
  20696. .getOr('');
  20697. };
  20698. var FocusMode;
  20699. (function (FocusMode) {
  20700. FocusMode[FocusMode["ContentFocus"] = 0] = "ContentFocus";
  20701. FocusMode[FocusMode["UiFocus"] = 1] = "UiFocus";
  20702. })(FocusMode || (FocusMode = {}));
  20703. const createMenuItemFromBridge = (item, itemResponse, backstage, menuHasIcons, isHorizontalMenu) => {
  20704. const providersBackstage = backstage.shared.providers;
  20705. // If we're making a horizontal menu (mobile context menu) we want text OR icons
  20706. // to simplify the UI. We also don't want shortcut text.
  20707. const parseForHorizontalMenu = (menuitem) => !isHorizontalMenu ? menuitem : ({
  20708. ...menuitem,
  20709. shortcut: Optional.none(),
  20710. icon: menuitem.text.isSome() ? Optional.none() : menuitem.icon
  20711. });
  20712. switch (item.type) {
  20713. case 'menuitem':
  20714. return createMenuItem(item).fold(handleError, (d) => Optional.some(normal(parseForHorizontalMenu(d), itemResponse, providersBackstage, menuHasIcons)));
  20715. case 'nestedmenuitem':
  20716. return createNestedMenuItem(item).fold(handleError, (d) => Optional.some(nested(parseForHorizontalMenu(d), itemResponse, providersBackstage, menuHasIcons, isHorizontalMenu)));
  20717. case 'togglemenuitem':
  20718. return createToggleMenuItem(item).fold(handleError, (d) => Optional.some(toggle(parseForHorizontalMenu(d), itemResponse, providersBackstage, menuHasIcons)));
  20719. case 'separator':
  20720. return createSeparatorMenuItem(item).fold(handleError, (d) => Optional.some(separator$3(d)));
  20721. case 'fancymenuitem':
  20722. return createFancyMenuItem(item).fold(handleError,
  20723. // Fancy menu items don't have shortcuts or icons
  20724. (d) => fancy(d, backstage));
  20725. default: {
  20726. // eslint-disable-next-line no-console
  20727. console.error('Unknown item in general menu', item);
  20728. return Optional.none();
  20729. }
  20730. }
  20731. };
  20732. const createAutocompleteItems = (items, matchText, onItemValueHandler, columns, itemResponse, sharedBackstage, highlightOn) => {
  20733. // Render text and icons if we're using a single column, otherwise only render icons
  20734. const renderText = columns === 1;
  20735. const renderIcons = !renderText || menuHasIcons(items);
  20736. return cat(map$2(items, (item) => {
  20737. switch (item.type) {
  20738. case 'separator':
  20739. return createSeparatorItem(item).fold(handleError, (d) => Optional.some(separator$3(d)));
  20740. case 'cardmenuitem':
  20741. return createCardMenuItem(item).fold(handleError, (d) => Optional.some(card({
  20742. ...d,
  20743. // Intercept action
  20744. onAction: (api) => {
  20745. d.onAction(api);
  20746. onItemValueHandler(d.value, d.meta);
  20747. }
  20748. }, itemResponse, sharedBackstage, {
  20749. itemBehaviours: tooltipBehaviour(d.meta, sharedBackstage, Optional.none()),
  20750. cardText: {
  20751. matchText,
  20752. highlightOn
  20753. }
  20754. })));
  20755. case 'autocompleteitem':
  20756. default:
  20757. return createAutocompleterItem(item).fold(handleError, (d) => Optional.some(autocomplete(d, matchText, renderText, 'normal', onItemValueHandler, itemResponse, sharedBackstage, renderIcons)));
  20758. }
  20759. }));
  20760. };
  20761. const createPartialMenu = (value, items, itemResponse, backstage, isHorizontalMenu, searchMode) => {
  20762. const hasIcons = menuHasIcons(items);
  20763. const alloyItems = cat(map$2(items, (item) => {
  20764. // Have to check each item for an icon, instead of as part of hasIcons above,
  20765. // else in horizontal menus, items with an icon but without text will display
  20766. // with neither
  20767. const itemHasIcon = (i) => isHorizontalMenu ? !has$2(i, 'text') : hasIcons;
  20768. const createItem = (i) => createMenuItemFromBridge(i, itemResponse, backstage, itemHasIcon(i), isHorizontalMenu);
  20769. if (item.type === 'nestedmenuitem' && item.getSubmenuItems().length <= 0) {
  20770. return createItem({ ...item, enabled: false });
  20771. }
  20772. else {
  20773. return createItem(item);
  20774. }
  20775. }));
  20776. // The menu layout is dependent upon our search mode.
  20777. const menuLayout = identifyMenuLayout(searchMode);
  20778. const createPartial = isHorizontalMenu ?
  20779. createHorizontalPartialMenuWithAlloyItems :
  20780. createPartialMenuWithAlloyItems;
  20781. return createPartial(value, hasIcons, alloyItems, 1, menuLayout);
  20782. };
  20783. const createTieredDataFrom = (partialMenu) => tieredMenu.singleData(partialMenu.value, partialMenu);
  20784. const createInlineMenuFrom = (partialMenu, columns, focusMode, presets) => {
  20785. const movement = deriveMenuMovement(columns, presets);
  20786. const menuMarkers = markers(presets);
  20787. return {
  20788. data: createTieredDataFrom({
  20789. ...partialMenu,
  20790. movement,
  20791. menuBehaviours: SimpleBehaviours.unnamedEvents(columns !== 'auto' ? [] : [
  20792. runOnAttached((comp, _se) => {
  20793. detectSize(comp, 4, menuMarkers.item).each(({ numColumns, numRows }) => {
  20794. Keying.setGridSize(comp, numRows, numColumns);
  20795. });
  20796. })
  20797. ])
  20798. }),
  20799. menu: {
  20800. markers: markers(presets),
  20801. fakeFocus: focusMode === FocusMode.ContentFocus
  20802. }
  20803. };
  20804. };
  20805. const rangeToSimRange = (r) => SimRange.create(SugarElement.fromDom(r.startContainer), r.startOffset, SugarElement.fromDom(r.endContainer), r.endOffset);
  20806. const register$c = (editor, sharedBackstage) => {
  20807. const autocompleterId = generate$6('autocompleter');
  20808. const processingAction = Cell(false);
  20809. const activeState = Cell(false);
  20810. const activeRange = value$2();
  20811. const autocompleter = build$1(InlineView.sketch({
  20812. dom: {
  20813. tag: 'div',
  20814. classes: ['tox-autocompleter'],
  20815. attributes: {
  20816. id: autocompleterId
  20817. }
  20818. },
  20819. components: [],
  20820. fireDismissalEventInstead: {},
  20821. inlineBehaviours: derive$1([
  20822. config('dismissAutocompleter', [
  20823. run$1(dismissRequested(), () => cancelIfNecessary()),
  20824. run$1(highlight$1(), (_, se) => {
  20825. getOpt(se.event.target, 'id').each((id) => set$9(SugarElement.fromDom(editor.getBody()), 'aria-activedescendant', id));
  20826. }),
  20827. ])
  20828. ]),
  20829. lazySink: sharedBackstage.getSink
  20830. }));
  20831. const isMenuOpen = () => InlineView.isOpen(autocompleter);
  20832. const isActive = activeState.get;
  20833. const hideIfNecessary = () => {
  20834. if (isMenuOpen()) {
  20835. InlineView.hide(autocompleter);
  20836. editor.dom.remove(autocompleterId, false);
  20837. const editorBody = SugarElement.fromDom(editor.getBody());
  20838. getOpt(editorBody, 'aria-owns')
  20839. .filter((ariaOwnsAttr) => ariaOwnsAttr === autocompleterId)
  20840. .each(() => {
  20841. remove$8(editorBody, 'aria-owns');
  20842. remove$8(editorBody, 'aria-activedescendant');
  20843. });
  20844. }
  20845. };
  20846. const getMenu = () => InlineView.getContent(autocompleter).bind((tmenu) => {
  20847. // The autocompleter menu will be the first child component of the tiered menu.
  20848. // Unfortunately a memento can't be used to do this lookup because the component
  20849. // id is changed while generating the tiered menu.
  20850. return get$i(tmenu.components(), 0);
  20851. });
  20852. const cancelIfNecessary = () => editor.execCommand('mceAutocompleterClose');
  20853. const getCombinedItems = (matches) => {
  20854. const columns = findMap(matches, (m) => Optional.from(m.columns)).getOr(1);
  20855. return bind$3(matches, (match) => {
  20856. const choices = match.items;
  20857. return createAutocompleteItems(choices, match.matchText, (itemValue, itemMeta) => {
  20858. const autocompleterApi = {
  20859. hide: () => cancelIfNecessary(),
  20860. reload: (fetchOptions) => {
  20861. hideIfNecessary();
  20862. editor.execCommand('mceAutocompleterReload', false, { fetchOptions });
  20863. }
  20864. };
  20865. // Asks the editor for a new active range that emits an event that updates
  20866. // the activeRange state not ideal but trying to avoid direct method calls to the core.
  20867. // We need to get a fresh range since when you hit enter the IME commits and the updates the DOM so we then need to rescan.
  20868. editor.execCommand('mceAutocompleterRefreshActiveRange');
  20869. activeRange.get().each((range) => {
  20870. processingAction.set(true);
  20871. match.onAction(autocompleterApi, range, itemValue, itemMeta);
  20872. processingAction.set(false);
  20873. });
  20874. }, columns, ItemResponse$1.BUBBLE_TO_SANDBOX, sharedBackstage, match.highlightOn);
  20875. });
  20876. };
  20877. const display = (lookupData, items) => {
  20878. // Display the autocompleter menu
  20879. const columns = findMap(lookupData, (ld) => Optional.from(ld.columns)).getOr(1);
  20880. InlineView.showMenuAt(autocompleter, {
  20881. anchor: {
  20882. type: 'selection',
  20883. getSelection: () => activeRange.get().map(rangeToSimRange),
  20884. root: SugarElement.fromDom(editor.getBody()),
  20885. }
  20886. }, createInlineMenuFrom(createPartialMenuWithAlloyItems('autocompleter-value', true, items, columns, { menuType: 'normal' }), columns, FocusMode.ContentFocus,
  20887. // Use the constant.
  20888. 'normal'));
  20889. getMenu().each(Highlighting.highlightFirst);
  20890. };
  20891. const updateDisplay = (lookupData) => {
  20892. const combinedItems = getCombinedItems(lookupData);
  20893. // Open the autocompleter if there are items to show
  20894. if (combinedItems.length > 0) {
  20895. display(lookupData, combinedItems);
  20896. set$9(SugarElement.fromDom(editor.getBody()), 'aria-owns', autocompleterId);
  20897. if (!editor.inline) {
  20898. cloneAutocompleterToEditorDoc();
  20899. }
  20900. }
  20901. else {
  20902. hideIfNecessary();
  20903. }
  20904. };
  20905. const cloneAutocompleterToEditorDoc = () => {
  20906. if (editor.dom.get(autocompleterId)) {
  20907. editor.dom.remove(autocompleterId, false);
  20908. }
  20909. const docElm = editor.getDoc().documentElement;
  20910. const selection = editor.selection.getNode();
  20911. const newElm = deep(autocompleter.element);
  20912. setAll(newElm, {
  20913. border: '0',
  20914. clip: 'rect(0 0 0 0)',
  20915. height: '1px',
  20916. margin: '-1px',
  20917. overflow: 'hidden',
  20918. padding: '0',
  20919. position: 'absolute',
  20920. width: '1px',
  20921. top: `${selection.offsetTop}px`,
  20922. left: `${selection.offsetLeft}px`,
  20923. });
  20924. editor.dom.add(docElm, newElm.dom);
  20925. // Clean up positioning styles so that the "hidden" autocompleter is around the selection
  20926. descendant(newElm, '[role="menu"]').each((child) => {
  20927. remove$6(child, 'position');
  20928. remove$6(child, 'max-height');
  20929. });
  20930. };
  20931. editor.on('AutocompleterStart', ({ lookupData }) => {
  20932. activeState.set(true);
  20933. processingAction.set(false);
  20934. updateDisplay(lookupData);
  20935. });
  20936. editor.on('AutocompleterUpdate', ({ lookupData }) => updateDisplay(lookupData));
  20937. editor.on('AutocompleterUpdateActiveRange', ({ range }) => activeRange.set(range));
  20938. editor.on('AutocompleterEnd', () => {
  20939. // Hide the menu and reset
  20940. hideIfNecessary();
  20941. activeState.set(false);
  20942. processingAction.set(false);
  20943. activeRange.clear();
  20944. });
  20945. const autocompleterUiApi = {
  20946. cancelIfNecessary,
  20947. isMenuOpen,
  20948. isActive,
  20949. isProcessingAction: processingAction.get,
  20950. getMenu
  20951. };
  20952. AutocompleterEditorEvents.setup(autocompleterUiApi, editor);
  20953. };
  20954. const Autocompleter = {
  20955. register: register$c
  20956. };
  20957. const renderBar = (spec, backstage) => ({
  20958. dom: {
  20959. tag: 'div',
  20960. classes: ['tox-bar', 'tox-form__controls-h-stack']
  20961. },
  20962. components: map$2(spec.items, backstage.interpreter)
  20963. });
  20964. var global$4 = tinymce.util.Tools.resolve('tinymce.html.Entities');
  20965. const renderFormFieldWith = (pLabel, pField, extraClasses, extraBehaviours) => {
  20966. const spec = renderFormFieldSpecWith(pLabel, pField, extraClasses, extraBehaviours);
  20967. return FormField.sketch(spec);
  20968. };
  20969. const renderFormField = (pLabel, pField) => renderFormFieldWith(pLabel, pField, [], []);
  20970. const renderFormFieldSpecWith = (pLabel, pField, extraClasses, extraBehaviours) => ({
  20971. dom: renderFormFieldDomWith(extraClasses),
  20972. components: pLabel.toArray().concat([pField]),
  20973. fieldBehaviours: derive$1(extraBehaviours)
  20974. });
  20975. const renderFormFieldDom = () => renderFormFieldDomWith([]);
  20976. const renderFormFieldDomWith = (extraClasses) => ({
  20977. tag: 'div',
  20978. classes: ['tox-form__group'].concat(extraClasses)
  20979. });
  20980. const renderLabel$3 = (label, providersBackstage) => FormField.parts.label({
  20981. dom: {
  20982. tag: 'label',
  20983. classes: ['tox-label']
  20984. },
  20985. components: [
  20986. text$2(providersBackstage.translate(label))
  20987. ]
  20988. });
  20989. const formChangeEvent = generate$6('form-component-change');
  20990. const formInputEvent = generate$6('form-component-input');
  20991. const formCloseEvent = generate$6('form-close');
  20992. const formCancelEvent = generate$6('form-cancel');
  20993. const formActionEvent = generate$6('form-action');
  20994. const formSubmitEvent = generate$6('form-submit');
  20995. const formBlockEvent = generate$6('form-block');
  20996. const formUnblockEvent = generate$6('form-unblock');
  20997. const formTabChangeEvent = generate$6('form-tabchange');
  20998. const formResizeEvent = generate$6('form-resize');
  20999. const renderCollection = (spec, providersBackstage, initialData) => {
  21000. // DUPE with TextField.
  21001. const pLabel = spec.label.map((label) => renderLabel$3(label, providersBackstage));
  21002. const icons = providersBackstage.icons();
  21003. // TINY-10174: Icon string is either in icon pack or displayed directly
  21004. const getIcon = (icon) => { var _a; return (_a = icons[icon]) !== null && _a !== void 0 ? _a : icon; };
  21005. const runOnItem = (f) => (comp, se) => {
  21006. closest$3(se.event.target, '[data-collection-item-value]').each((target) => {
  21007. f(comp, se, target, get$g(target, 'data-collection-item-value'));
  21008. });
  21009. };
  21010. const setContents = (comp, items) => {
  21011. // Giving it a default `mode:design` context, these shouldn't run at all in mode:readonly
  21012. const disabled = providersBackstage.checkUiComponentContext('mode:design').shouldDisable || providersBackstage.isDisabled();
  21013. const disabledClass = disabled ? ' tox-collection__item--state-disabled' : '';
  21014. const htmlLines = map$2(items, (item) => {
  21015. const itemText = global$6.translate(item.text);
  21016. const textContent = spec.columns === 1 ? `<div class="tox-collection__item-label">${itemText}</div>` : '';
  21017. const iconContent = `<div class="tox-collection__item-icon">${getIcon(item.icon)}</div>`;
  21018. // Replacing the hyphens and underscores in collection items with spaces
  21019. // to ensure the screen readers pronounce the words correctly.
  21020. // This is only for aria purposes. Emoticon and Special Character names will still use _ and - for autocompletion.
  21021. const mapItemName = {
  21022. '_': ' ',
  21023. ' - ': ' ',
  21024. '-': ' '
  21025. };
  21026. // Using aria-label here overrides the Apple description of emojis and special characters in Mac/ MS description in Windows.
  21027. // But if only the title attribute is used instead, the names are read out twice. i.e., the description followed by the item.text.
  21028. const ariaLabel = itemText.replace(/\_| \- |\-/g, (match) => mapItemName[match]);
  21029. return `<div data-mce-tooltip="${ariaLabel}" class="tox-collection__item${disabledClass}" tabindex="-1" data-collection-item-value="${global$4.encodeAllRaw(item.value)}" aria-label="${ariaLabel}">${iconContent}${textContent}</div>`;
  21030. });
  21031. const chunks = spec.columns !== 'auto' && spec.columns > 1 ? chunk$1(htmlLines, spec.columns) : [htmlLines];
  21032. const html = map$2(chunks, (ch) => `<div class="tox-collection__group">${ch.join('')}</div>`);
  21033. set$8(comp.element, html.join(''));
  21034. };
  21035. const onClick = runOnItem((comp, se, tgt, itemValue) => {
  21036. se.stop();
  21037. if (!(providersBackstage.checkUiComponentContext('mode:design').shouldDisable || providersBackstage.isDisabled())) {
  21038. emitWith(comp, formActionEvent, {
  21039. name: spec.name,
  21040. value: itemValue
  21041. });
  21042. }
  21043. });
  21044. const collectionEvents = [
  21045. run$1(mouseover(), runOnItem((comp, se, tgt) => {
  21046. focus$4(tgt, true);
  21047. })),
  21048. run$1(click(), onClick),
  21049. run$1(tap(), onClick),
  21050. run$1(focusin(), runOnItem((comp, se, tgt) => {
  21051. descendant(comp.element, '.' + activeClass).each((currentActive) => {
  21052. remove$3(currentActive, activeClass);
  21053. });
  21054. add$2(tgt, activeClass);
  21055. })),
  21056. run$1(focusout(), runOnItem((comp) => {
  21057. descendant(comp.element, '.' + activeClass).each((currentActive) => {
  21058. remove$3(currentActive, activeClass);
  21059. blur$1(currentActive);
  21060. });
  21061. })),
  21062. runOnExecute$1(runOnItem((comp, se, tgt, itemValue) => {
  21063. emitWith(comp, formActionEvent, {
  21064. name: spec.name,
  21065. value: itemValue
  21066. });
  21067. })),
  21068. ];
  21069. const iterCollectionItems = (comp, applyAttributes) => map$2(descendants(comp.element, '.tox-collection__item'), applyAttributes);
  21070. const pField = FormField.parts.field({
  21071. dom: {
  21072. tag: 'div',
  21073. // FIX: Read from columns
  21074. classes: ['tox-collection'].concat(spec.columns !== 1 ? ['tox-collection--grid'] : ['tox-collection--list'])
  21075. },
  21076. components: [],
  21077. factory: { sketch: identity },
  21078. behaviours: derive$1([
  21079. Disabling.config({
  21080. disabled: () => providersBackstage.checkUiComponentContext(spec.context).shouldDisable,
  21081. onDisabled: (comp) => {
  21082. iterCollectionItems(comp, (childElm) => {
  21083. add$2(childElm, 'tox-collection__item--state-disabled');
  21084. set$9(childElm, 'aria-disabled', true);
  21085. });
  21086. },
  21087. onEnabled: (comp) => {
  21088. iterCollectionItems(comp, (childElm) => {
  21089. remove$3(childElm, 'tox-collection__item--state-disabled');
  21090. remove$8(childElm, 'aria-disabled');
  21091. });
  21092. }
  21093. }),
  21094. toggleOnReceive(() => providersBackstage.checkUiComponentContext(spec.context)),
  21095. Replacing.config({}),
  21096. Tooltipping.config({
  21097. ...providersBackstage.tooltips.getConfig({
  21098. tooltipText: '',
  21099. onShow: (comp) => {
  21100. descendant(comp.element, '.' + activeClass + '[data-mce-tooltip]').each((current) => {
  21101. getOpt(current, 'data-mce-tooltip').each((text) => {
  21102. Tooltipping.setComponents(comp, providersBackstage.tooltips.getComponents({ tooltipText: text }));
  21103. });
  21104. });
  21105. }
  21106. }),
  21107. mode: 'children-keyboard-focus',
  21108. anchor: (comp) => ({
  21109. type: 'node',
  21110. node: descendant(comp.element, '.' + activeClass).orThunk(() => first('.tox-collection__item')),
  21111. root: comp.element,
  21112. layouts: {
  21113. onLtr: constant$1([south$2, north$2, southeast$2, northeast$2, southwest$2, northwest$2]),
  21114. onRtl: constant$1([south$2, north$2, southeast$2, northeast$2, southwest$2, northwest$2])
  21115. },
  21116. bubble: nu$6(0, -2, {}),
  21117. })
  21118. }),
  21119. Representing.config({
  21120. store: {
  21121. mode: 'memory',
  21122. initialValue: initialData.getOr([])
  21123. },
  21124. onSetValue: (comp, items) => {
  21125. setContents(comp, items);
  21126. if (spec.columns === 'auto') {
  21127. detectSize(comp, 5, 'tox-collection__item').each(({ numRows, numColumns }) => {
  21128. Keying.setGridSize(comp, numRows, numColumns);
  21129. });
  21130. }
  21131. emit(comp, formResizeEvent);
  21132. }
  21133. }),
  21134. Tabstopping.config({}),
  21135. Keying.config(deriveCollectionMovement(spec.columns, 'normal')),
  21136. config('collection-events', collectionEvents)
  21137. ]),
  21138. eventOrder: {
  21139. [execute$5()]: ['disabling', 'alloy.base.behaviour', 'collection-events'],
  21140. [focusin()]: ['collection-events', 'tooltipping'],
  21141. }
  21142. });
  21143. const extraClasses = ['tox-form__group--collection'];
  21144. return renderFormFieldWith(pLabel, pField, extraClasses, []);
  21145. };
  21146. const renderPanelButton = (spec, sharedBackstage) => Dropdown.sketch({
  21147. dom: spec.dom,
  21148. components: spec.components,
  21149. toggleClass: 'mce-active',
  21150. dropdownBehaviours: derive$1([
  21151. DisablingConfigs.button(() => sharedBackstage.providers.isDisabled() || sharedBackstage.providers.checkUiComponentContext(spec.context).shouldDisable),
  21152. toggleOnReceive(() => sharedBackstage.providers.checkUiComponentContext(spec.context)),
  21153. Unselecting.config({}),
  21154. Tabstopping.config({})
  21155. ]),
  21156. layouts: spec.layouts,
  21157. sandboxClasses: ['tox-dialog__popups'],
  21158. lazySink: sharedBackstage.getSink,
  21159. fetch: (comp) => Future.nu((callback) => spec.fetch(callback)).map((items) => Optional.from(createTieredDataFrom(deepMerge(createPartialChoiceMenu(generate$6('menu-value'), items, (value) => {
  21160. spec.onItemAction(comp, value);
  21161. }, spec.columns, spec.presets, ItemResponse$1.CLOSE_ON_EXECUTE,
  21162. // No colour is ever selected on opening
  21163. never, sharedBackstage.providers), {
  21164. movement: deriveMenuMovement(spec.columns, spec.presets)
  21165. })))),
  21166. parts: {
  21167. menu: part(false, 1, spec.presets)
  21168. }
  21169. });
  21170. const colorInputChangeEvent = generate$6('color-input-change');
  21171. const colorSwatchChangeEvent = generate$6('color-swatch-change');
  21172. const colorPickerCancelEvent = generate$6('color-picker-cancel');
  21173. const renderColorInput = (spec, sharedBackstage, colorInputBackstage, initialData) => {
  21174. const pField = FormField.parts.field({
  21175. factory: Input,
  21176. inputClasses: ['tox-textfield'],
  21177. data: initialData,
  21178. onSetValue: (c) => Invalidating.run(c).get(noop),
  21179. inputBehaviours: derive$1([
  21180. Disabling.config({
  21181. disabled: () => sharedBackstage.providers.isDisabled() || sharedBackstage.providers.checkUiComponentContext(spec.context).shouldDisable
  21182. }),
  21183. toggleOnReceive(() => sharedBackstage.providers.checkUiComponentContext(spec.context)),
  21184. Tabstopping.config({}),
  21185. Invalidating.config({
  21186. invalidClass: 'tox-textbox-field-invalid',
  21187. getRoot: (comp) => parentElement(comp.element),
  21188. notify: {
  21189. onValid: (comp) => {
  21190. // onValid should pass through the value here
  21191. // We need a snapshot of the value validated.
  21192. const val = Representing.getValue(comp);
  21193. emitWith(comp, colorInputChangeEvent, {
  21194. color: val
  21195. });
  21196. }
  21197. },
  21198. validator: {
  21199. validateOnLoad: false,
  21200. validate: (input) => {
  21201. const inputValue = Representing.getValue(input);
  21202. // Consider empty strings valid colours
  21203. if (inputValue.length === 0) {
  21204. return Future.pure(Result.value(true));
  21205. }
  21206. else {
  21207. const span = SugarElement.fromTag('span');
  21208. set$7(span, 'background-color', inputValue);
  21209. const res = getRaw(span, 'background-color').fold(
  21210. // TODO: Work out what we want to do here.
  21211. () => Result.error('blah'), (_) => Result.value(inputValue));
  21212. return Future.pure(res);
  21213. }
  21214. }
  21215. }
  21216. })
  21217. ]),
  21218. selectOnFocus: false
  21219. });
  21220. const pLabel = spec.label.map((label) => renderLabel$3(label, sharedBackstage.providers));
  21221. const emitSwatchChange = (colorBit, value) => {
  21222. emitWith(colorBit, colorSwatchChangeEvent, {
  21223. value
  21224. });
  21225. };
  21226. const onItemAction = (comp, value) => {
  21227. memColorButton.getOpt(comp).each((colorBit) => {
  21228. if (value === 'custom') {
  21229. colorInputBackstage.colorPicker((valueOpt) => {
  21230. valueOpt.fold(() => emit(colorBit, colorPickerCancelEvent), (value) => {
  21231. emitSwatchChange(colorBit, value);
  21232. addColor(spec.storageKey, value);
  21233. });
  21234. }, '#ffffff');
  21235. }
  21236. else if (value === 'remove') {
  21237. emitSwatchChange(colorBit, '');
  21238. }
  21239. else {
  21240. emitSwatchChange(colorBit, value);
  21241. }
  21242. });
  21243. };
  21244. const memColorButton = record(renderPanelButton({
  21245. dom: {
  21246. tag: 'span',
  21247. attributes: {
  21248. 'aria-label': sharedBackstage.providers.translate('Color swatch')
  21249. }
  21250. },
  21251. layouts: {
  21252. onRtl: () => [southwest$2, southeast$2, south$2],
  21253. onLtr: () => [southeast$2, southwest$2, south$2]
  21254. },
  21255. components: [],
  21256. fetch: getFetch$1(colorInputBackstage.getColors(spec.storageKey), spec.storageKey, colorInputBackstage.hasCustomColors()),
  21257. columns: colorInputBackstage.getColorCols(spec.storageKey),
  21258. presets: 'color',
  21259. onItemAction,
  21260. context: spec.context
  21261. }, sharedBackstage));
  21262. return FormField.sketch({
  21263. dom: {
  21264. tag: 'div',
  21265. classes: ['tox-form__group']
  21266. },
  21267. components: pLabel.toArray().concat([
  21268. {
  21269. dom: {
  21270. tag: 'div',
  21271. classes: ['tox-color-input']
  21272. },
  21273. components: [
  21274. pField,
  21275. memColorButton.asSpec()
  21276. ]
  21277. }
  21278. ]),
  21279. fieldBehaviours: derive$1([
  21280. config('form-field-events', [
  21281. run$1(colorInputChangeEvent, (comp, se) => {
  21282. memColorButton.getOpt(comp).each((colorButton) => {
  21283. set$7(colorButton.element, 'background-color', se.event.color);
  21284. });
  21285. emitWith(comp, formChangeEvent, { name: spec.name });
  21286. }),
  21287. run$1(colorSwatchChangeEvent, (comp, se) => {
  21288. FormField.getField(comp).each((field) => {
  21289. Representing.setValue(field, se.event.value);
  21290. // Focus the field now that we've set its value
  21291. Composing.getCurrent(comp).each(Focusing.focus);
  21292. });
  21293. }),
  21294. run$1(colorPickerCancelEvent, (comp, _se) => {
  21295. FormField.getField(comp).each((_field) => {
  21296. Composing.getCurrent(comp).each(Focusing.focus);
  21297. });
  21298. })
  21299. ])
  21300. ])
  21301. });
  21302. };
  21303. // TODO: Move this to alloy if the concept works out
  21304. // eslint-disable-next-line consistent-this
  21305. const self = () => Composing.config({
  21306. find: Optional.some
  21307. });
  21308. const memento$1 = (mem) => Composing.config({
  21309. find: mem.getOpt
  21310. });
  21311. const childAt = (index) => Composing.config({
  21312. find: (comp) => child$2(comp.element, index)
  21313. .bind((element) => comp.getSystem().getByDom(element).toOptional())
  21314. });
  21315. const ComposingConfigs = {
  21316. self,
  21317. memento: memento$1,
  21318. childAt
  21319. };
  21320. const processors = objOf([
  21321. defaulted('preprocess', identity),
  21322. defaulted('postprocess', identity)
  21323. ]);
  21324. const memento = (mem, rawProcessors) => {
  21325. const ps = asRawOrDie$1('RepresentingConfigs.memento processors', processors, rawProcessors);
  21326. return Representing.config({
  21327. store: {
  21328. mode: 'manual',
  21329. getValue: (comp) => {
  21330. const other = mem.get(comp);
  21331. const rawValue = Representing.getValue(other);
  21332. return ps.postprocess(rawValue);
  21333. },
  21334. setValue: (comp, rawValue) => {
  21335. const newValue = ps.preprocess(rawValue);
  21336. const other = mem.get(comp);
  21337. Representing.setValue(other, newValue);
  21338. }
  21339. }
  21340. });
  21341. };
  21342. const withComp = (optInitialValue, getter, setter) => Representing.config({
  21343. store: {
  21344. mode: 'manual',
  21345. ...optInitialValue.map((initialValue) => ({ initialValue })).getOr({}),
  21346. getValue: getter,
  21347. setValue: setter
  21348. }
  21349. });
  21350. const withElement = (initialValue, getter, setter) => withComp(initialValue, (c) => getter(c.element), (c, v) => setter(c.element, v));
  21351. const domHtml = (optInitialValue) => withElement(optInitialValue, get$f, set$8);
  21352. const memory = (initialValue) => Representing.config({
  21353. store: {
  21354. mode: 'memory',
  21355. initialValue
  21356. }
  21357. });
  21358. const fieldsUpdate = generate$6('rgb-hex-update');
  21359. const sliderUpdate = generate$6('slider-update');
  21360. const paletteUpdate = generate$6('palette-update');
  21361. const sliderFactory = (translate, getClass) => {
  21362. const spectrum = Slider.parts.spectrum({
  21363. dom: {
  21364. tag: 'div',
  21365. classes: [getClass('hue-slider-spectrum')],
  21366. attributes: {
  21367. role: 'presentation'
  21368. }
  21369. }
  21370. });
  21371. const thumb = Slider.parts.thumb({
  21372. dom: {
  21373. tag: 'div',
  21374. classes: [getClass('hue-slider-thumb')],
  21375. attributes: {
  21376. role: 'presentation'
  21377. }
  21378. }
  21379. });
  21380. return Slider.sketch({
  21381. dom: {
  21382. tag: 'div',
  21383. classes: [getClass('hue-slider')],
  21384. attributes: {
  21385. 'role': 'slider',
  21386. 'aria-valuemin': 0,
  21387. 'aria-valuemax': 360,
  21388. 'aria-valuenow': 120,
  21389. }
  21390. },
  21391. rounded: false,
  21392. model: {
  21393. mode: 'y',
  21394. getInitialValue: constant$1(0)
  21395. },
  21396. components: [
  21397. spectrum,
  21398. thumb
  21399. ],
  21400. sliderBehaviours: derive$1([
  21401. Focusing.config({})
  21402. ]),
  21403. onChange: (slider, _thumb, value) => {
  21404. set$9(slider.element, 'aria-valuenow', Math.floor(360 - (value * 3.6)));
  21405. emitWith(slider, sliderUpdate, {
  21406. value
  21407. });
  21408. }
  21409. });
  21410. };
  21411. const validInput = generate$6('valid-input');
  21412. const invalidInput = generate$6('invalid-input');
  21413. const validatingInput = generate$6('validating-input');
  21414. const translatePrefix = 'colorcustom.rgb.';
  21415. const uninitiatedTooltipApi = {
  21416. isEnabled: always,
  21417. setEnabled: noop,
  21418. immediatelyShow: noop,
  21419. immediatelyHide: noop,
  21420. };
  21421. const rgbFormFactory = (translate, getClass, onValidHexx, onInvalidHexx, tooltipGetConfig, makeIcon) => {
  21422. const setTooltipEnabled = (enabled, tooltipApi) => {
  21423. const api = tooltipApi.get();
  21424. if (enabled === api.isEnabled()) {
  21425. return;
  21426. }
  21427. api.setEnabled(enabled);
  21428. if (enabled) {
  21429. api.immediatelyShow();
  21430. }
  21431. else {
  21432. api.immediatelyHide();
  21433. }
  21434. };
  21435. const invalidation = (label, isValid, tooltipApi) => Invalidating.config({
  21436. invalidClass: getClass('invalid'),
  21437. notify: {
  21438. onValidate: (comp) => {
  21439. emitWith(comp, validatingInput, {
  21440. type: label
  21441. });
  21442. },
  21443. onValid: (comp) => {
  21444. setTooltipEnabled(false, tooltipApi);
  21445. emitWith(comp, validInput, {
  21446. type: label,
  21447. value: Representing.getValue(comp)
  21448. });
  21449. },
  21450. onInvalid: (comp) => {
  21451. setTooltipEnabled(true, tooltipApi);
  21452. emitWith(comp, invalidInput, {
  21453. type: label,
  21454. value: Representing.getValue(comp)
  21455. });
  21456. }
  21457. },
  21458. validator: {
  21459. validate: (comp) => {
  21460. const value = Representing.getValue(comp);
  21461. const res = isValid(value) ? Result.value(true) : Result.error(translate('aria.input.invalid'));
  21462. return Future.pure(res);
  21463. },
  21464. validateOnLoad: false
  21465. }
  21466. });
  21467. const renderTextField = (isValid, name, label, description, data) => {
  21468. const tooltipApi = Cell(uninitiatedTooltipApi);
  21469. const helptext = translate(translatePrefix + 'range');
  21470. const pLabel = FormField.parts.label({
  21471. dom: { tag: 'label' },
  21472. components: [text$2(label)]
  21473. });
  21474. const pField = FormField.parts.field({
  21475. data,
  21476. factory: Input,
  21477. inputAttributes: {
  21478. 'type': 'text',
  21479. 'aria-label': description,
  21480. ...name === 'hex' ? { 'aria-live': 'polite' } : {}
  21481. },
  21482. inputClasses: [getClass('textfield')],
  21483. // Have basic invalidating and tabstopping behaviour.
  21484. inputBehaviours: derive$1([
  21485. invalidation(name, isValid, tooltipApi),
  21486. Tabstopping.config({}),
  21487. Tooltipping.config({
  21488. ...tooltipGetConfig({
  21489. tooltipText: '',
  21490. onSetup: (comp) => {
  21491. tooltipApi.set({
  21492. isEnabled: () => {
  21493. return Tooltipping.isEnabled(comp);
  21494. },
  21495. setEnabled: (enabled) => {
  21496. return Tooltipping.setEnabled(comp, enabled);
  21497. },
  21498. immediatelyShow: () => {
  21499. return Tooltipping.immediateOpenClose(comp, true);
  21500. },
  21501. immediatelyHide: () => {
  21502. return Tooltipping.immediateOpenClose(comp, false);
  21503. },
  21504. });
  21505. Tooltipping.setEnabled(comp, false);
  21506. },
  21507. onShow: (component, _tooltip) => {
  21508. Tooltipping.setComponents(component, [
  21509. {
  21510. dom: {
  21511. tag: 'p',
  21512. classes: [
  21513. getClass('rgb-warning-note')
  21514. ]
  21515. },
  21516. components: [text$2(translate(name === 'hex' ? 'colorcustom.rgb.invalidHex' : 'colorcustom.rgb.invalid'))]
  21517. }
  21518. ]);
  21519. },
  21520. })
  21521. })
  21522. ]),
  21523. // If it was invalid, and the value was set, run validation against it.
  21524. onSetValue: (input) => {
  21525. if (Invalidating.isInvalid(input)) {
  21526. const run = Invalidating.run(input);
  21527. run.get(noop);
  21528. }
  21529. }
  21530. });
  21531. const errorId = generate$6('aria-invalid');
  21532. const memInvalidIcon = record(makeIcon('invalid', Optional.some(errorId), 'warning'));
  21533. const memStatus = record({
  21534. dom: {
  21535. tag: 'div',
  21536. classes: [getClass('invalid-icon')]
  21537. },
  21538. components: [
  21539. memInvalidIcon.asSpec()
  21540. ]
  21541. });
  21542. const comps = [pLabel, pField, memStatus.asSpec()];
  21543. const concats = name !== 'hex' ? [FormField.parts['aria-descriptor']({
  21544. text: helptext
  21545. })] : [];
  21546. const components = comps.concat(concats);
  21547. return {
  21548. dom: {
  21549. tag: 'div',
  21550. attributes: {
  21551. role: 'presentation'
  21552. },
  21553. classes: [
  21554. getClass('rgb-container'),
  21555. ]
  21556. },
  21557. components
  21558. };
  21559. };
  21560. const copyRgbToHex = (form, rgba) => {
  21561. const hex = fromRgba(rgba);
  21562. Form.getField(form, 'hex').each((hexField) => {
  21563. // Not amazing, but it turns out that if we have an invalid RGB field, and no hex code
  21564. // and then type in a valid three digit hex code, the RGB field will be overriden, then validate and then set
  21565. // the hex field to be the six digit version of that same three digit hex code. This is incorrect.
  21566. if (!Focusing.isFocused(hexField)) {
  21567. Representing.setValue(form, {
  21568. hex: hex.value
  21569. });
  21570. }
  21571. });
  21572. return hex;
  21573. };
  21574. const copyRgbToForm = (form, rgb) => {
  21575. const red = rgb.red;
  21576. const green = rgb.green;
  21577. const blue = rgb.blue;
  21578. Representing.setValue(form, { red, green, blue });
  21579. };
  21580. const memPreview = record({
  21581. dom: {
  21582. tag: 'div',
  21583. classes: [getClass('rgba-preview')],
  21584. styles: {
  21585. 'background-color': 'white'
  21586. },
  21587. attributes: {
  21588. role: 'presentation'
  21589. }
  21590. }
  21591. });
  21592. const updatePreview = (anyInSystem, hex) => {
  21593. memPreview.getOpt(anyInSystem).each((preview) => {
  21594. set$7(preview.element, 'background-color', '#' + hex.value);
  21595. });
  21596. };
  21597. const factory = () => {
  21598. const state = {
  21599. red: Cell(Optional.some(255)),
  21600. green: Cell(Optional.some(255)),
  21601. blue: Cell(Optional.some(255)),
  21602. hex: Cell(Optional.some('ffffff'))
  21603. };
  21604. const copyHexToRgb = (form, hex) => {
  21605. const rgb = fromHex(hex);
  21606. copyRgbToForm(form, rgb);
  21607. setValueRgb(rgb);
  21608. };
  21609. const get = (prop) => state[prop].get();
  21610. const set = (prop, value) => {
  21611. state[prop].set(value);
  21612. };
  21613. const getValueRgb = () => get('red').bind((red) => get('green').bind((green) => get('blue').map((blue) => rgbaColour(red, green, blue, 1))));
  21614. // TODO: Find way to use this for palette and slider updates
  21615. const setValueRgb = (rgb) => {
  21616. const red = rgb.red;
  21617. const green = rgb.green;
  21618. const blue = rgb.blue;
  21619. set('red', Optional.some(red));
  21620. set('green', Optional.some(green));
  21621. set('blue', Optional.some(blue));
  21622. };
  21623. const onInvalidInput = (form, simulatedEvent) => {
  21624. const data = simulatedEvent.event;
  21625. if (data.type !== 'hex') {
  21626. set(data.type, Optional.none());
  21627. }
  21628. else {
  21629. onInvalidHexx(form);
  21630. }
  21631. };
  21632. const onValidHex = (form, value) => {
  21633. onValidHexx(form);
  21634. const hex = hexColour(value);
  21635. set('hex', Optional.some(hex.value));
  21636. const rgb = fromHex(hex);
  21637. copyRgbToForm(form, rgb);
  21638. setValueRgb(rgb);
  21639. emitWith(form, fieldsUpdate, {
  21640. hex
  21641. });
  21642. updatePreview(form, hex);
  21643. };
  21644. const onValidRgb = (form, prop, value) => {
  21645. const val = parseInt(value, 10);
  21646. set(prop, Optional.some(val));
  21647. getValueRgb().each((rgb) => {
  21648. const hex = copyRgbToHex(form, rgb);
  21649. emitWith(form, fieldsUpdate, {
  21650. hex
  21651. });
  21652. updatePreview(form, hex);
  21653. });
  21654. };
  21655. const isHexInputEvent = (data) => data.type === 'hex';
  21656. const onValidInput = (form, simulatedEvent) => {
  21657. const data = simulatedEvent.event;
  21658. if (isHexInputEvent(data)) {
  21659. onValidHex(form, data.value);
  21660. }
  21661. else {
  21662. onValidRgb(form, data.type, data.value);
  21663. }
  21664. };
  21665. const formPartStrings = (key) => ({
  21666. label: translate(translatePrefix + key + '.label'),
  21667. description: translate(translatePrefix + key + '.description')
  21668. });
  21669. const redStrings = formPartStrings('red');
  21670. const greenStrings = formPartStrings('green');
  21671. const blueStrings = formPartStrings('blue');
  21672. const hexStrings = formPartStrings('hex');
  21673. // TODO: Provide a nice way of adding APIs to existing sketchers
  21674. return deepMerge(Form.sketch((parts) => ({
  21675. dom: {
  21676. tag: 'form',
  21677. classes: [getClass('rgb-form')],
  21678. attributes: { 'aria-label': translate('aria.color.picker') }
  21679. },
  21680. components: [
  21681. parts.field('red', FormField.sketch(renderTextField(isRgbaComponent, 'red', redStrings.label, redStrings.description, 255))),
  21682. parts.field('green', FormField.sketch(renderTextField(isRgbaComponent, 'green', greenStrings.label, greenStrings.description, 255))),
  21683. parts.field('blue', FormField.sketch(renderTextField(isRgbaComponent, 'blue', blueStrings.label, blueStrings.description, 255))),
  21684. parts.field('hex', FormField.sketch(renderTextField(isHexString, 'hex', hexStrings.label, hexStrings.description, 'ffffff'))),
  21685. memPreview.asSpec()
  21686. ],
  21687. formBehaviours: derive$1([
  21688. Invalidating.config({
  21689. invalidClass: getClass('form-invalid')
  21690. }),
  21691. config('rgb-form-events', [
  21692. run$1(validInput, onValidInput),
  21693. run$1(invalidInput, onInvalidInput),
  21694. run$1(validatingInput, onInvalidInput)
  21695. ])
  21696. ])
  21697. })), {
  21698. apis: {
  21699. updateHex: (form, hex) => {
  21700. Representing.setValue(form, {
  21701. hex: hex.value
  21702. });
  21703. copyHexToRgb(form, hex);
  21704. updatePreview(form, hex);
  21705. }
  21706. }
  21707. });
  21708. };
  21709. const rgbFormSketcher = single({
  21710. factory,
  21711. name: 'RgbForm',
  21712. configFields: [],
  21713. apis: {
  21714. updateHex: (apis, form, hex) => {
  21715. apis.updateHex(form, hex);
  21716. }
  21717. },
  21718. extraApis: {}
  21719. });
  21720. return rgbFormSketcher;
  21721. };
  21722. const paletteFactory = (translate, getClass) => {
  21723. const spectrumPart = Slider.parts.spectrum({
  21724. dom: {
  21725. tag: 'canvas',
  21726. attributes: {
  21727. role: 'presentation'
  21728. },
  21729. classes: [getClass('sv-palette-spectrum')]
  21730. }
  21731. });
  21732. const thumbPart = Slider.parts.thumb({
  21733. dom: {
  21734. tag: 'div',
  21735. attributes: {
  21736. role: 'presentation'
  21737. },
  21738. classes: [getClass('sv-palette-thumb')],
  21739. innerHtml: `<div class=${getClass('sv-palette-inner-thumb')} role="presentation"></div>`
  21740. }
  21741. });
  21742. const setColour = (canvas, rgba) => {
  21743. const { width, height } = canvas;
  21744. const ctx = canvas.getContext('2d');
  21745. if (ctx === null) {
  21746. return;
  21747. }
  21748. ctx.fillStyle = rgba;
  21749. ctx.fillRect(0, 0, width, height);
  21750. const grdWhite = ctx.createLinearGradient(0, 0, width, 0);
  21751. grdWhite.addColorStop(0, 'rgba(255,255,255,1)');
  21752. grdWhite.addColorStop(1, 'rgba(255,255,255,0)');
  21753. ctx.fillStyle = grdWhite;
  21754. ctx.fillRect(0, 0, width, height);
  21755. const grdBlack = ctx.createLinearGradient(0, 0, 0, height);
  21756. grdBlack.addColorStop(0, 'rgba(0,0,0,0)');
  21757. grdBlack.addColorStop(1, 'rgba(0,0,0,1)');
  21758. ctx.fillStyle = grdBlack;
  21759. ctx.fillRect(0, 0, width, height);
  21760. };
  21761. const setPaletteHue = (slider, hue) => {
  21762. const canvas = slider.components()[0].element.dom;
  21763. const hsv = hsvColour(hue, 100, 100);
  21764. const rgba = fromHsv(hsv);
  21765. setColour(canvas, toString(rgba));
  21766. };
  21767. const setPaletteThumb = (slider, hex) => {
  21768. const hsv = fromRgb(fromHex(hex));
  21769. Slider.setValue(slider, { x: hsv.saturation, y: 100 - hsv.value });
  21770. set$9(slider.element, 'aria-valuetext', translate(['Saturation {0}%, Brightness {1}%', hsv.saturation, hsv.value]));
  21771. };
  21772. const factory = (_detail) => {
  21773. const getInitialValue = constant$1({
  21774. x: 0,
  21775. y: 0
  21776. });
  21777. const onChange = (slider, _thumb, value) => {
  21778. if (!isNumber(value)) {
  21779. set$9(slider.element, 'aria-valuetext', translate(['Saturation {0}%, Brightness {1}%', Math.floor(value.x), Math.floor(100 - value.y)]));
  21780. }
  21781. emitWith(slider, paletteUpdate, {
  21782. value
  21783. });
  21784. };
  21785. const onInit = (_slider, _thumb, spectrum, _value) => {
  21786. // Maybe make this initial value configurable?
  21787. setColour(spectrum.element.dom, toString(red));
  21788. };
  21789. const sliderBehaviours = derive$1([
  21790. Composing.config({
  21791. find: Optional.some
  21792. }),
  21793. Focusing.config({})
  21794. ]);
  21795. return Slider.sketch({
  21796. dom: {
  21797. tag: 'div',
  21798. attributes: {
  21799. 'role': 'slider',
  21800. 'aria-valuetext': translate(['Saturation {0}%, Brightness {1}%', 0, 0])
  21801. },
  21802. classes: [getClass('sv-palette')]
  21803. },
  21804. model: {
  21805. mode: 'xy',
  21806. getInitialValue,
  21807. },
  21808. rounded: false,
  21809. components: [
  21810. spectrumPart,
  21811. thumbPart
  21812. ],
  21813. onChange,
  21814. onInit,
  21815. sliderBehaviours
  21816. });
  21817. };
  21818. const saturationBrightnessPaletteSketcher = single({
  21819. factory,
  21820. name: 'SaturationBrightnessPalette',
  21821. configFields: [],
  21822. apis: {
  21823. setHue: (_apis, slider, hue) => {
  21824. setPaletteHue(slider, hue);
  21825. },
  21826. setThumb: (_apis, slider, hex) => {
  21827. setPaletteThumb(slider, hex);
  21828. }
  21829. },
  21830. extraApis: {}
  21831. });
  21832. return saturationBrightnessPaletteSketcher;
  21833. };
  21834. const makeFactory = (translate, getClass, tooltipConfig, makeIcon) => {
  21835. const factory = (detail) => {
  21836. const rgbForm = rgbFormFactory(translate, getClass, detail.onValidHex, detail.onInvalidHex, tooltipConfig, makeIcon);
  21837. const sbPalette = paletteFactory(translate, getClass);
  21838. const hueSliderToDegrees = (hue) => (100 - hue) / 100 * 360;
  21839. const hueDegreesToSlider = (hue) => 100 - (hue / 360) * 100;
  21840. const state = {
  21841. paletteRgba: Cell(red),
  21842. paletteHue: Cell(0)
  21843. };
  21844. const memSlider = record(sliderFactory(translate, getClass));
  21845. const memPalette = record(sbPalette.sketch({}));
  21846. const memRgb = record(rgbForm.sketch({}));
  21847. const updatePalette = (anyInSystem, _hex, hue) => {
  21848. memPalette.getOpt(anyInSystem).each((palette) => {
  21849. sbPalette.setHue(palette, hue);
  21850. });
  21851. };
  21852. const updateFields = (anyInSystem, hex) => {
  21853. memRgb.getOpt(anyInSystem).each((form) => {
  21854. rgbForm.updateHex(form, hex);
  21855. });
  21856. };
  21857. const updateSlider = (anyInSystem, _hex, hue) => {
  21858. memSlider.getOpt(anyInSystem).each((slider) => {
  21859. Slider.setValue(slider, hueDegreesToSlider(hue));
  21860. });
  21861. };
  21862. const updatePaletteThumb = (anyInSystem, hex) => {
  21863. memPalette.getOpt(anyInSystem).each((palette) => {
  21864. sbPalette.setThumb(palette, hex);
  21865. });
  21866. };
  21867. const updateState = (hex, hue) => {
  21868. const rgba = fromHex(hex);
  21869. state.paletteRgba.set(rgba);
  21870. state.paletteHue.set(hue);
  21871. };
  21872. const runUpdates = (anyInSystem, hex, hue, updates) => {
  21873. updateState(hex, hue);
  21874. each$1(updates, (update) => {
  21875. update(anyInSystem, hex, hue);
  21876. });
  21877. };
  21878. const onPaletteUpdate = () => {
  21879. const updates = [updateFields];
  21880. return (form, simulatedEvent) => {
  21881. const value = simulatedEvent.event.value;
  21882. const oldHue = state.paletteHue.get();
  21883. const newHsv = hsvColour(oldHue, value.x, (100 - value.y));
  21884. const newHex = hsvToHex(newHsv);
  21885. runUpdates(form, newHex, oldHue, updates);
  21886. };
  21887. };
  21888. const onSliderUpdate = () => {
  21889. const updates = [updatePalette, updateFields];
  21890. return (form, simulatedEvent) => {
  21891. const hue = hueSliderToDegrees(simulatedEvent.event.value);
  21892. const oldRgb = state.paletteRgba.get();
  21893. const oldHsv = fromRgb(oldRgb);
  21894. const newHsv = hsvColour(hue, oldHsv.saturation, oldHsv.value);
  21895. const newHex = hsvToHex(newHsv);
  21896. runUpdates(form, newHex, hue, updates);
  21897. };
  21898. };
  21899. const onFieldsUpdate = () => {
  21900. const updates = [updatePalette, updateSlider, updatePaletteThumb];
  21901. return (form, simulatedEvent) => {
  21902. const hex = simulatedEvent.event.hex;
  21903. const hsv = hexToHsv(hex);
  21904. runUpdates(form, hex, hsv.hue, updates);
  21905. };
  21906. };
  21907. return {
  21908. uid: detail.uid,
  21909. dom: detail.dom,
  21910. components: [
  21911. memPalette.asSpec(),
  21912. memSlider.asSpec(),
  21913. memRgb.asSpec()
  21914. ],
  21915. behaviours: derive$1([
  21916. config('colour-picker-events', [
  21917. run$1(fieldsUpdate, onFieldsUpdate()),
  21918. run$1(paletteUpdate, onPaletteUpdate()),
  21919. run$1(sliderUpdate, onSliderUpdate())
  21920. ]),
  21921. Composing.config({
  21922. find: (comp) => memRgb.getOpt(comp)
  21923. }),
  21924. Keying.config({
  21925. mode: 'acyclic'
  21926. })
  21927. ])
  21928. };
  21929. };
  21930. const colourPickerSketcher = single({
  21931. name: 'ColourPicker',
  21932. configFields: [
  21933. required$1('dom'),
  21934. defaulted('onValidHex', noop),
  21935. defaulted('onInvalidHex', noop)
  21936. ],
  21937. factory
  21938. });
  21939. return colourPickerSketcher;
  21940. };
  21941. const english = {
  21942. 'colorcustom.rgb.red.label': 'R',
  21943. 'colorcustom.rgb.red.description': 'Red channel',
  21944. 'colorcustom.rgb.green.label': 'G',
  21945. 'colorcustom.rgb.green.description': 'Green channel',
  21946. 'colorcustom.rgb.blue.label': 'B',
  21947. 'colorcustom.rgb.blue.description': 'Blue channel',
  21948. 'colorcustom.rgb.hex.label': '#',
  21949. 'colorcustom.rgb.hex.description': 'Hex color code',
  21950. 'colorcustom.rgb.range': 'Range 0 to 255',
  21951. 'colorcustom.rgb.invalid': 'Numbers only, 0 to 255',
  21952. 'colorcustom.rgb.invalidHex': 'Hexadecimal only, 000000 to FFFFFF',
  21953. 'aria.color.picker': 'Color Picker',
  21954. 'aria.input.invalid': 'Invalid input'
  21955. };
  21956. const translate = (providerBackstage) => (key) => {
  21957. if (isString(key)) {
  21958. return providerBackstage.translate(english[key]);
  21959. }
  21960. else {
  21961. return providerBackstage.translate(key);
  21962. }
  21963. };
  21964. const renderColorPicker = (_spec, providerBackstage, initialData) => {
  21965. const getClass = (key) => 'tox-' + key;
  21966. const renderIcon = (name, errId, icon = name, label = name) => render$4(icon, {
  21967. tag: 'div',
  21968. classes: ['tox-icon', 'tox-control-wrap__status-icon-' + name],
  21969. attributes: {
  21970. 'title': providerBackstage.translate(label),
  21971. 'aria-live': 'polite',
  21972. ...errId.fold(() => ({}), (id) => ({ id }))
  21973. }
  21974. }, providerBackstage.icons);
  21975. const colourPickerFactory = makeFactory(translate(providerBackstage), getClass, providerBackstage.tooltips.getConfig, renderIcon);
  21976. const onValidHex = (form) => {
  21977. emitWith(form, formActionEvent, { name: 'hex-valid', value: true });
  21978. };
  21979. const onInvalidHex = (form) => {
  21980. emitWith(form, formActionEvent, { name: 'hex-valid', value: false });
  21981. };
  21982. const memPicker = record(colourPickerFactory.sketch({
  21983. dom: {
  21984. tag: 'div',
  21985. classes: [getClass('color-picker-container')],
  21986. attributes: {
  21987. role: 'presentation'
  21988. }
  21989. },
  21990. onValidHex,
  21991. onInvalidHex
  21992. }));
  21993. return {
  21994. dom: {
  21995. tag: 'div'
  21996. },
  21997. components: [
  21998. memPicker.asSpec()
  21999. ],
  22000. behaviours: derive$1([
  22001. // We'll allow invalid values
  22002. withComp(initialData, (comp) => {
  22003. const picker = memPicker.get(comp);
  22004. const optRgbForm = Composing.getCurrent(picker);
  22005. const optHex = optRgbForm.bind((rgbForm) => {
  22006. const formValues = Representing.getValue(rgbForm);
  22007. return formValues.hex;
  22008. });
  22009. return optHex.map((hex) => '#' + removeLeading(hex, '#')).getOr('');
  22010. }, (comp, newValue) => {
  22011. const pattern = /^#([a-fA-F0-9]{3}(?:[a-fA-F0-9]{3})?)/;
  22012. const valOpt = Optional.from(pattern.exec(newValue)).bind((matches) => get$i(matches, 1));
  22013. const picker = memPicker.get(comp);
  22014. const optRgbForm = Composing.getCurrent(picker);
  22015. optRgbForm.fold(() => {
  22016. // eslint-disable-next-line no-console
  22017. console.log('Can not find form');
  22018. }, (rgbForm) => {
  22019. Representing.setValue(rgbForm, {
  22020. hex: valOpt.getOr('')
  22021. });
  22022. // So not the way to do this.
  22023. Form.getField(rgbForm, 'hex').each((hexField) => {
  22024. emit(hexField, input());
  22025. });
  22026. });
  22027. }),
  22028. ComposingConfigs.self()
  22029. ])
  22030. };
  22031. };
  22032. var global$3 = tinymce.util.Tools.resolve('tinymce.Resource');
  22033. const isOldCustomEditor = (spec) => has$2(spec, 'init');
  22034. const renderCustomEditor = (spec) => {
  22035. const editorApi = value$2();
  22036. const memReplaced = record({
  22037. dom: {
  22038. tag: spec.tag
  22039. }
  22040. });
  22041. const initialValue = value$2();
  22042. const focusBehaviour = !isOldCustomEditor(spec) && spec.onFocus.isSome() ? [
  22043. Focusing.config({
  22044. onFocus: (comp) => {
  22045. spec.onFocus.each((onFocusFn) => {
  22046. onFocusFn(comp.element.dom);
  22047. });
  22048. }
  22049. }),
  22050. Tabstopping.config({})
  22051. ] : [];
  22052. return {
  22053. dom: {
  22054. tag: 'div',
  22055. classes: ['tox-custom-editor']
  22056. },
  22057. behaviours: derive$1([
  22058. config('custom-editor-events', [
  22059. runOnAttached((component) => {
  22060. memReplaced.getOpt(component).each((ta) => {
  22061. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  22062. (isOldCustomEditor(spec)
  22063. ? spec.init(ta.element.dom)
  22064. : global$3.load(spec.scriptId, spec.scriptUrl).then((init) => init(ta.element.dom, spec.settings))).then((ea) => {
  22065. initialValue.on((cvalue) => {
  22066. ea.setValue(cvalue);
  22067. });
  22068. initialValue.clear();
  22069. editorApi.set(ea);
  22070. });
  22071. });
  22072. })
  22073. ]),
  22074. withComp(Optional.none(), () => editorApi.get().fold(() => initialValue.get().getOr(''), (ed) => ed.getValue()), (_component, value) => {
  22075. editorApi.get().fold(() => initialValue.set(value), (ed) => ed.setValue(value));
  22076. }),
  22077. ComposingConfigs.self()
  22078. ].concat(focusBehaviour)),
  22079. components: [memReplaced.asSpec()]
  22080. };
  22081. };
  22082. var global$2 = tinymce.util.Tools.resolve('tinymce.util.Tools');
  22083. const browseFilesEvent = generate$6('browse.files.event');
  22084. const filterByExtension = (files, providersBackstage) => {
  22085. const allowedImageFileTypes = global$2.explode(providersBackstage.getOption('images_file_types'));
  22086. const isFileInAllowedTypes = (file) => exists(allowedImageFileTypes, (type) => endsWith(file.name.toLowerCase(), `.${type.toLowerCase()}`));
  22087. return filter$2(from(files), isFileInAllowedTypes);
  22088. };
  22089. const renderDropZone = (spec, providersBackstage, initialData) => {
  22090. // TODO: Consider moving to alloy
  22091. const stopper = (_, se) => {
  22092. se.stop();
  22093. };
  22094. // TODO: Consider moving to alloy
  22095. const sequence = (actions) => (comp, se) => {
  22096. each$1(actions, (a) => {
  22097. a(comp, se);
  22098. });
  22099. };
  22100. const onDrop = (comp, se) => {
  22101. var _a;
  22102. if (!Disabling.isDisabled(comp)) {
  22103. const transferEvent = se.event.raw;
  22104. emitWith(comp, browseFilesEvent, { files: (_a = transferEvent.dataTransfer) === null || _a === void 0 ? void 0 : _a.files });
  22105. }
  22106. };
  22107. const onSelect = (component, simulatedEvent) => {
  22108. const input = simulatedEvent.event.raw.target;
  22109. emitWith(component, browseFilesEvent, { files: input.files });
  22110. };
  22111. const handleFiles = (component, files) => {
  22112. if (files) {
  22113. Representing.setValue(component, filterByExtension(files, providersBackstage));
  22114. emitWith(component, formChangeEvent, { name: spec.name });
  22115. }
  22116. };
  22117. const memInput = record({
  22118. dom: {
  22119. tag: 'input',
  22120. attributes: {
  22121. type: 'file',
  22122. accept: 'image/*'
  22123. },
  22124. styles: {
  22125. display: 'none'
  22126. }
  22127. },
  22128. behaviours: derive$1([
  22129. config('input-file-events', [
  22130. cutter(click()),
  22131. cutter(tap())
  22132. ])
  22133. ])
  22134. });
  22135. const pLabel = spec.label.map((label) => renderLabel$3(label, providersBackstage));
  22136. const pField = FormField.parts.field({
  22137. factory: Button,
  22138. dom: {
  22139. tag: 'button',
  22140. styles: {
  22141. position: 'relative'
  22142. },
  22143. classes: ['tox-button', 'tox-button--secondary']
  22144. },
  22145. components: [
  22146. text$2(providersBackstage.translate('Browse for an image')),
  22147. memInput.asSpec()
  22148. ],
  22149. action: (comp) => {
  22150. const inputComp = memInput.get(comp);
  22151. inputComp.element.dom.click();
  22152. },
  22153. buttonBehaviours: derive$1([
  22154. ComposingConfigs.self(),
  22155. memory(initialData.getOr([])),
  22156. Tabstopping.config({}),
  22157. DisablingConfigs.button(() => providersBackstage.checkUiComponentContext(spec.context).shouldDisable),
  22158. toggleOnReceive(() => providersBackstage.checkUiComponentContext(spec.context))
  22159. ])
  22160. });
  22161. const wrapper = {
  22162. dom: {
  22163. tag: 'div',
  22164. classes: ['tox-dropzone-container']
  22165. },
  22166. behaviours: derive$1([
  22167. Disabling.config({
  22168. disabled: () => providersBackstage.checkUiComponentContext(spec.context).shouldDisable
  22169. }),
  22170. toggleOnReceive(() => providersBackstage.checkUiComponentContext(spec.context)),
  22171. Toggling.config({
  22172. toggleClass: 'dragenter',
  22173. toggleOnExecute: false
  22174. }),
  22175. config('dropzone-events', [
  22176. run$1('dragenter', sequence([stopper, Toggling.toggle])),
  22177. run$1('dragleave', sequence([stopper, Toggling.toggle])),
  22178. run$1('dragover', stopper),
  22179. run$1('drop', sequence([stopper, onDrop])),
  22180. run$1(change(), onSelect)
  22181. ])
  22182. ]),
  22183. components: [
  22184. {
  22185. dom: {
  22186. tag: 'div',
  22187. classes: ['tox-dropzone'],
  22188. styles: {}
  22189. },
  22190. components: [
  22191. {
  22192. dom: {
  22193. tag: 'p'
  22194. },
  22195. components: [
  22196. text$2(providersBackstage.translate('Drop an image here'))
  22197. ]
  22198. },
  22199. pField
  22200. ]
  22201. }
  22202. ]
  22203. };
  22204. return renderFormFieldWith(pLabel, wrapper, ['tox-form__group--stretched'], [config('handle-files', [
  22205. run$1(browseFilesEvent, (comp, se) => {
  22206. FormField.getField(comp).each((field) => {
  22207. handleFiles(field, se.event.files);
  22208. });
  22209. })
  22210. ])]);
  22211. };
  22212. const renderGrid = (spec, backstage) => ({
  22213. dom: {
  22214. tag: 'div',
  22215. classes: ['tox-form__grid', `tox-form__grid--${spec.columns}col`]
  22216. },
  22217. components: map$2(spec.items, backstage.interpreter)
  22218. });
  22219. const beforeObject = generate$6('alloy-fake-before-tabstop');
  22220. const afterObject = generate$6('alloy-fake-after-tabstop');
  22221. const craftWithClasses = (classes) => {
  22222. return {
  22223. dom: {
  22224. tag: 'div',
  22225. styles: {
  22226. width: '1px',
  22227. height: '1px',
  22228. outline: 'none'
  22229. },
  22230. attributes: {
  22231. tabindex: '0' // Capture native tabbing in the appropriate order
  22232. },
  22233. classes
  22234. },
  22235. behaviours: derive$1([
  22236. Focusing.config({ ignore: true }),
  22237. Tabstopping.config({})
  22238. ])
  22239. };
  22240. };
  22241. const craft = (containerClasses, spec) => {
  22242. return {
  22243. dom: {
  22244. tag: 'div',
  22245. classes: ['tox-navobj', ...containerClasses.getOr([])]
  22246. },
  22247. components: [
  22248. craftWithClasses([beforeObject]),
  22249. spec,
  22250. craftWithClasses([afterObject])
  22251. ],
  22252. behaviours: derive$1([
  22253. ComposingConfigs.childAt(1)
  22254. ])
  22255. };
  22256. };
  22257. // TODO: Create an API in alloy to do this.
  22258. const triggerTab = (placeholder, shiftKey) => {
  22259. emitWith(placeholder, keydown(), {
  22260. raw: {
  22261. which: 9,
  22262. shiftKey
  22263. }
  22264. });
  22265. };
  22266. const onFocus = (container, targetComp) => {
  22267. const target = targetComp.element;
  22268. // If focus has shifted naturally to a before object, the tab direction is backwards.
  22269. if (has(target, beforeObject)) {
  22270. triggerTab(container, true);
  22271. }
  22272. else if (has(target, afterObject)) {
  22273. triggerTab(container, false);
  22274. }
  22275. };
  22276. const isPseudoStop = (element) => {
  22277. return closest$1(element, ['.' + beforeObject, '.' + afterObject].join(','), never);
  22278. };
  22279. const dialogChannel = generate$6('update-dialog');
  22280. const titleChannel = generate$6('update-title');
  22281. const bodyChannel = generate$6('update-body');
  22282. const footerChannel = generate$6('update-footer');
  22283. const bodySendMessageChannel = generate$6('body-send-message');
  22284. const dialogFocusShiftedChannel = generate$6('dialog-focus-shifted');
  22285. const browser = detect$1().browser;
  22286. const isSafari = browser.isSafari();
  22287. const isFirefox = browser.isFirefox();
  22288. const isSafariOrFirefox = isSafari || isFirefox;
  22289. const isChromium = browser.isChromium();
  22290. const isElementScrollAtBottom = ({ scrollTop, scrollHeight, clientHeight }) => Math.ceil(scrollTop) + clientHeight >= scrollHeight;
  22291. const scrollToY = (win, y) =>
  22292. // TINY-10128: The iframe body is occasionally null when we attempt to scroll, so instead of using body.scrollHeight, use a
  22293. // fallback value of 99999999. To minimise the potential impact of future browser changes, this fallback is significantly smaller
  22294. // than the minimum of the maximum value Window.scrollTo would take on supported browsers:
  22295. // Chromium: > Number.MAX_SAFE_INTEGER
  22296. // Safari: 2^31 - 1 = 2147483647
  22297. // Firefox: 2147483583
  22298. win.scrollTo(0, y === 'bottom' ? 99999999 : y);
  22299. const getScrollingElement = (doc, html) => {
  22300. // TINY-10110: The scrolling element can change between body and documentElement depending on whether there
  22301. // is a doctype declaration. However, this behavior is inconsistent on Chrome and Safari so checking for
  22302. // the scroll properties is the most reliable way to determine which element is the scrolling element, at
  22303. // least for the purposes of determining whether scroll is at bottom.
  22304. const body = doc.body;
  22305. return Optional.from(!/^<!DOCTYPE (html|HTML)/.test(html) &&
  22306. (!isChromium && !isSafari || isNonNullable(body) && (body.scrollTop !== 0 || Math.abs(body.scrollHeight - body.clientHeight) > 1))
  22307. ? body : doc.documentElement);
  22308. };
  22309. const writeValue = (iframeElement, html, fallbackFn) => {
  22310. const iframe = iframeElement.dom;
  22311. Optional.from(iframe.contentDocument).fold(fallbackFn, (doc) => {
  22312. let lastScrollTop = 0;
  22313. // TINY-10032: If documentElement (or body) is nullable, we assume document is empty and so scroll is at bottom.
  22314. const isScrollAtBottom = getScrollingElement(doc, html).map((el) => {
  22315. lastScrollTop = el.scrollTop;
  22316. return el;
  22317. }).forall(isElementScrollAtBottom);
  22318. const scrollAfterWrite = () => {
  22319. const win = iframe.contentWindow;
  22320. if (isNonNullable(win)) {
  22321. if (isScrollAtBottom) {
  22322. scrollToY(win, 'bottom');
  22323. }
  22324. else if (!isScrollAtBottom && isSafariOrFirefox && lastScrollTop !== 0) {
  22325. // TINY-10078: Safari and Firefox reset scroll to top on each document.write(), so we need to restore scroll manually
  22326. scrollToY(win, lastScrollTop);
  22327. }
  22328. }
  22329. };
  22330. // TINY-10109: On Safari, attempting to scroll before the iframe has finished loading will cause scroll to reset to top upon load.
  22331. // TINY-10128: We will not wait for the load event on Chrome and Firefox since doing so causes the scroll to jump around erratically,
  22332. // especially on Firefox. However, not waiting for load has the trade-off of potentially losing bottom scroll when updating at a very
  22333. // rapid rate, as attempting to scroll before the iframe body is loaded will not work.
  22334. if (isSafari) {
  22335. iframe.addEventListener('load', scrollAfterWrite, { once: true });
  22336. }
  22337. doc.open();
  22338. doc.write(html);
  22339. doc.close();
  22340. if (!isSafari) {
  22341. scrollAfterWrite();
  22342. }
  22343. });
  22344. };
  22345. // TINY-10078: On Firefox, throttle to 200ms to improve scrolling experience. Since we are manually maintaining previous scroll position
  22346. // on each update, when updating rapidly without a throttle, attempting to scroll around the iframe can feel stuck.
  22347. // TINY-10097: On Safari, throttle to 500ms to reduce flickering as the document.write() method still observes significant flickering.
  22348. // Also improves scrolling, as scroll positions are maintained manually similar to Firefox.
  22349. const throttleInterval = someIf(isSafariOrFirefox, isSafari ? 500 : 200);
  22350. // TINY-10078: Use Throttler.adaptable to ensure that any content added during the waiting period is not lost.
  22351. const writeValueThrottler = throttleInterval.map((interval) => adaptable(writeValue, interval));
  22352. const getDynamicSource = (initialData, stream) => {
  22353. const cachedValue = Cell(initialData.getOr(''));
  22354. return {
  22355. getValue: (_frameComponent) =>
  22356. // Ideally we should fetch data from the iframe...innerHtml, this triggers Cors errors
  22357. cachedValue.get(),
  22358. setValue: (frameComponent, html) => {
  22359. if (cachedValue.get() !== html) {
  22360. const iframeElement = frameComponent.element;
  22361. const setSrcdocValue = () => set$9(iframeElement, 'srcdoc', html);
  22362. if (stream) {
  22363. writeValueThrottler.fold(constant$1(writeValue), (throttler) => throttler.throttle)(iframeElement, html, setSrcdocValue);
  22364. }
  22365. else {
  22366. // TINY-3769: We need to use srcdoc here, instead of src with a data URI, otherwise browsers won't retain the Origin.
  22367. // See https://bugs.chromium.org/p/chromium/issues/detail?id=58999#c11
  22368. setSrcdocValue();
  22369. }
  22370. }
  22371. cachedValue.set(html);
  22372. }
  22373. };
  22374. };
  22375. const renderIFrame = (spec, providersBackstage, initialData) => {
  22376. const baseClass = 'tox-dialog__iframe';
  22377. const opaqueClass = spec.transparent ? [] : [`${baseClass}--opaque`];
  22378. const containerBorderedClass = spec.border ? [`tox-navobj-bordered`] : [];
  22379. const attributes = {
  22380. ...spec.label.map((title) => ({ title })).getOr({}),
  22381. ...initialData.map((html) => ({ srcdoc: html })).getOr({}),
  22382. ...spec.sandboxed ? { sandbox: 'allow-scripts allow-same-origin' } : {}
  22383. };
  22384. const sourcing = getDynamicSource(initialData, spec.streamContent);
  22385. const pLabel = spec.label.map((label) => renderLabel$3(label, providersBackstage));
  22386. const factory = (newSpec) => craft(Optional.from(containerBorderedClass), {
  22387. // We need to use the part uid or the label and field won't be linked with ARIA
  22388. uid: newSpec.uid,
  22389. dom: {
  22390. tag: 'iframe',
  22391. attributes,
  22392. classes: [
  22393. baseClass,
  22394. ...opaqueClass
  22395. ]
  22396. },
  22397. behaviours: derive$1([
  22398. Tabstopping.config({}),
  22399. Focusing.config({}),
  22400. withComp(initialData, sourcing.getValue, sourcing.setValue),
  22401. Receiving.config({
  22402. channels: {
  22403. [dialogFocusShiftedChannel]: {
  22404. onReceive: (comp, message) => {
  22405. message.newFocus.each((newFocus) => {
  22406. parentElement(comp.element).each((parent) => {
  22407. const f = eq(comp.element, newFocus) ? add$2 : remove$3;
  22408. f(parent, 'tox-navobj-bordered-focus');
  22409. });
  22410. });
  22411. }
  22412. }
  22413. }
  22414. })
  22415. ])
  22416. });
  22417. // Note, it's not going to handle escape at this point.
  22418. const pField = FormField.parts.field({
  22419. factory: { sketch: factory }
  22420. });
  22421. return renderFormFieldWith(pLabel, pField, ['tox-form__group--stretched'], []);
  22422. };
  22423. const calculateImagePosition = (panelWidth, panelHeight, imageWidth, imageHeight, zoom) => {
  22424. const width = imageWidth * zoom;
  22425. const height = imageHeight * zoom;
  22426. const left = Math.max(0, panelWidth / 2 - width / 2);
  22427. const top = Math.max(0, panelHeight / 2 - height / 2);
  22428. return {
  22429. left: left.toString() + 'px',
  22430. top: top.toString() + 'px',
  22431. width: width.toString() + 'px',
  22432. height: height.toString() + 'px',
  22433. };
  22434. };
  22435. const zoomToFit = (panel, width, height) => {
  22436. const panelW = get$c(panel);
  22437. const panelH = get$d(panel);
  22438. return Math.min(panelW / width, panelH / height, 1);
  22439. };
  22440. const renderImagePreview = (spec, initialData) => {
  22441. const cachedData = Cell(initialData.getOr({ url: '' }));
  22442. const memImage = record({
  22443. dom: {
  22444. tag: 'img',
  22445. classes: ['tox-imagepreview__image'],
  22446. attributes: initialData.map((data) => ({ src: data.url })).getOr({})
  22447. },
  22448. });
  22449. const memContainer = record({
  22450. dom: {
  22451. tag: 'div',
  22452. classes: ['tox-imagepreview__container'],
  22453. attributes: {
  22454. role: 'presentation'
  22455. },
  22456. },
  22457. components: [
  22458. memImage.asSpec()
  22459. ]
  22460. });
  22461. const setValue = (frameComponent, data) => {
  22462. const translatedData = {
  22463. url: data.url
  22464. };
  22465. // update properties that are set by the data
  22466. data.zoom.each((z) => translatedData.zoom = z);
  22467. data.cachedWidth.each((z) => translatedData.cachedWidth = z);
  22468. data.cachedHeight.each((z) => translatedData.cachedHeight = z);
  22469. cachedData.set(translatedData);
  22470. const applyFramePositioning = () => {
  22471. const { cachedWidth, cachedHeight, zoom } = translatedData;
  22472. if (!isUndefined(cachedWidth) && !isUndefined(cachedHeight)) {
  22473. if (isUndefined(zoom)) {
  22474. const z = zoomToFit(frameComponent.element, cachedWidth, cachedHeight);
  22475. // sneaky mutation since we own the object
  22476. translatedData.zoom = z;
  22477. }
  22478. const position = calculateImagePosition(get$c(frameComponent.element), get$d(frameComponent.element), cachedWidth, cachedHeight, translatedData.zoom);
  22479. memContainer.getOpt(frameComponent).each((container) => {
  22480. setAll(container.element, position);
  22481. });
  22482. }
  22483. };
  22484. memImage.getOpt(frameComponent).each((imageComponent) => {
  22485. const img = imageComponent.element;
  22486. if (data.url !== get$g(img, 'src')) {
  22487. set$9(img, 'src', data.url);
  22488. remove$3(frameComponent.element, 'tox-imagepreview__loaded');
  22489. }
  22490. applyFramePositioning();
  22491. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  22492. image(img).then((img) => {
  22493. // Ensure the component hasn't been removed while the image was loading
  22494. // if it is disconnected, just do nothing
  22495. if (frameComponent.getSystem().isConnected()) {
  22496. add$2(frameComponent.element, 'tox-imagepreview__loaded');
  22497. // sneaky mutation since we own the object
  22498. translatedData.cachedWidth = img.dom.naturalWidth;
  22499. translatedData.cachedHeight = img.dom.naturalHeight;
  22500. applyFramePositioning();
  22501. }
  22502. });
  22503. });
  22504. };
  22505. const styles = {};
  22506. spec.height.each((h) => styles.height = h);
  22507. // TODO: TINY-8393 Use the initial data properly once it's validated
  22508. const fakeValidatedData = initialData.map((d) => ({
  22509. url: d.url,
  22510. zoom: Optional.from(d.zoom),
  22511. cachedWidth: Optional.from(d.cachedWidth),
  22512. cachedHeight: Optional.from(d.cachedHeight),
  22513. }));
  22514. return {
  22515. dom: {
  22516. tag: 'div',
  22517. classes: ['tox-imagepreview'],
  22518. styles,
  22519. attributes: {
  22520. role: 'presentation'
  22521. }
  22522. },
  22523. components: [
  22524. memContainer.asSpec(),
  22525. ],
  22526. behaviours: derive$1([
  22527. ComposingConfigs.self(),
  22528. withComp(fakeValidatedData, () =>
  22529. /*
  22530. NOTE: This is intentionally returning the cached image width and height.
  22531. Including those details in the dialog data helps when `setData` only changes the URL, as
  22532. the old image must continue to be displayed at the old size until the new image has loaded.
  22533. */
  22534. cachedData.get(), setValue),
  22535. ])
  22536. };
  22537. };
  22538. const renderLabel$2 = (spec, backstageShared, getCompByName) => {
  22539. const baseClass = 'tox-label';
  22540. const centerClass = spec.align === 'center' ? [`${baseClass}--center`] : [];
  22541. const endClass = spec.align === 'end' ? [`${baseClass}--end`] : [];
  22542. const label = record({
  22543. dom: {
  22544. tag: 'label',
  22545. classes: [baseClass, ...centerClass, ...endClass]
  22546. },
  22547. components: [
  22548. text$2(backstageShared.providers.translate(spec.label))
  22549. ]
  22550. });
  22551. const comps = map$2(spec.items, backstageShared.interpreter);
  22552. return {
  22553. dom: {
  22554. tag: 'div',
  22555. classes: ['tox-form__group']
  22556. },
  22557. components: [
  22558. label.asSpec(),
  22559. ...comps
  22560. ],
  22561. behaviours: derive$1([
  22562. ComposingConfigs.self(),
  22563. Replacing.config({}),
  22564. domHtml(Optional.none()),
  22565. Keying.config({
  22566. mode: 'acyclic'
  22567. }),
  22568. config('label', [
  22569. runOnAttached((comp) => {
  22570. spec.for.each((name) => {
  22571. getCompByName(name).each((target) => {
  22572. label.getOpt(comp).each((labelComp) => {
  22573. var _a;
  22574. const id = (_a = get$g(target.element, 'id')) !== null && _a !== void 0 ? _a : generate$6('form-field');
  22575. set$9(target.element, 'id', id);
  22576. set$9(labelComp.element, 'for', id);
  22577. });
  22578. });
  22579. });
  22580. })
  22581. ]),
  22582. ])
  22583. };
  22584. };
  22585. const internalToolbarButtonExecute = generate$6('toolbar.button.execute');
  22586. // Perform `action` when an item is clicked on, close menus, and stop event
  22587. const onToolbarButtonExecute = (info) => runOnExecute$1((comp, _simulatedEvent) => {
  22588. // If there is an action, run the action
  22589. runWithApi(info, comp)((itemApi) => {
  22590. emitWith(comp, internalToolbarButtonExecute, {
  22591. buttonApi: itemApi
  22592. });
  22593. info.onAction(itemApi);
  22594. });
  22595. });
  22596. const commonButtonDisplayEvent = generate$6('common-button-display-events');
  22597. const toolbarButtonEventOrder = {
  22598. // TODO: use the constants provided by behaviours.
  22599. [execute$5()]: ['disabling', 'alloy.base.behaviour', 'toggling', 'toolbar-button-events', 'tooltipping'],
  22600. [attachedToDom()]: [
  22601. 'toolbar-button-events',
  22602. commonButtonDisplayEvent
  22603. ],
  22604. [detachedFromDom()]: ['toolbar-button-events', 'dropdown-events', 'tooltipping'],
  22605. [mousedown()]: [
  22606. 'focusing',
  22607. 'alloy.base.behaviour',
  22608. commonButtonDisplayEvent
  22609. ]
  22610. };
  22611. const forceInitialSize = (comp) => set$7(comp.element, 'width', get$e(comp.element, 'width'));
  22612. const renderIcon$1 = (iconName, iconsProvider, behaviours) => render$4(iconName, {
  22613. tag: 'span',
  22614. classes: ["tox-icon" /* ToolbarButtonClasses.Icon */, "tox-tbtn__icon-wrap" /* ToolbarButtonClasses.IconWrap */],
  22615. behaviours
  22616. }, iconsProvider);
  22617. const renderIconFromPack$1 = (iconName, iconsProvider) => renderIcon$1(iconName, iconsProvider, []);
  22618. const renderReplaceableIconFromPack = (iconName, iconsProvider) => renderIcon$1(iconName, iconsProvider, [Replacing.config({})]);
  22619. const renderLabel$1 = (text, prefix, providersBackstage) => ({
  22620. dom: {
  22621. tag: 'span',
  22622. classes: [`${prefix}__select-label`]
  22623. },
  22624. components: [
  22625. text$2(providersBackstage.translate(text))
  22626. ],
  22627. behaviours: derive$1([
  22628. Replacing.config({})
  22629. ])
  22630. });
  22631. const updateMenuText = generate$6('update-menu-text');
  22632. const updateMenuIcon = generate$6('update-menu-icon');
  22633. const updateTooltiptext = generate$6('update-tooltip-text');
  22634. // TODO: Use renderCommonStructure here.
  22635. const renderCommonDropdown = (spec, prefix, sharedBackstage, btnName) => {
  22636. const editorOffCell = Cell(noop);
  22637. const tooltip = Cell(spec.tooltip);
  22638. // We need mementos for display text and display icon because on the events
  22639. // updateMenuText and updateMenuIcon respectively, their contents are changed
  22640. // via Replacing. These events are generally emitted by dropdowns that want the
  22641. // main text and icon to match the current selection (e.g. bespokes like font family)
  22642. const optMemDisplayText = spec.text.map((text) => record(renderLabel$1(text, prefix, sharedBackstage.providers)));
  22643. const optMemDisplayIcon = spec.icon.map((iconName) => record(renderReplaceableIconFromPack(iconName, sharedBackstage.providers.icons)));
  22644. /*
  22645. * The desired behaviour here is:
  22646. *
  22647. * when left or right is pressed, and it isn't associated with expanding or
  22648. * collapsing a submenu, then it should navigate to the next menu item, and
  22649. * expand it (without highlighting any items in the expanded menu).
  22650. * It also needs to close the previous menu
  22651. */
  22652. const onLeftOrRightInMenu = (comp, se) => {
  22653. // The originating dropdown is stored on the sandbox itself. This is just an
  22654. // implementation detail of alloy. We really need to make it a fully-fledged API.
  22655. // TODO: TINY-9014 Make SandboxAPI have a function that just delegates to Representing
  22656. const dropdown = Representing.getValue(comp);
  22657. // Focus the dropdown. Current workaround required to make FlowLayout recognise the current focus.
  22658. // The triggering keydown is going to try to move the focus left or
  22659. // right of the current menu, so it needs to know what the current menu dropdown is. It
  22660. // can't work it out by the current focus, because the current focus is *in* the menu, so
  22661. // we help it by moving the focus to the button, so it can work out what the next menu to
  22662. // the left or right is.
  22663. Focusing.focus(dropdown);
  22664. emitWith(dropdown, 'keydown', {
  22665. raw: se.event.raw
  22666. });
  22667. // Because we have just navigated off this open menu, we want to close it.
  22668. // INVESTIGATE: TINY-9014: Is this handling situations where there were no menus
  22669. // to move to? Does it matter if we still close it when there are no other menus?
  22670. Dropdown.close(dropdown);
  22671. // The Optional.some(true) tells the keyboard handler that this event was handled,
  22672. // which will do things like stopPropagation and preventDefault.
  22673. return Optional.some(true);
  22674. };
  22675. const role = spec.role.fold(() => ({}), (role) => ({ role }));
  22676. const listRole = Optional.from(spec.listRole).map((listRole) => ({ listRole })).getOr({});
  22677. const ariaLabelAttribute = spec.ariaLabel.fold(() => ({}), (ariaLabel) => {
  22678. const translatedAriaLabel = sharedBackstage.providers.translate(ariaLabel);
  22679. return {
  22680. 'aria-label': translatedAriaLabel
  22681. };
  22682. });
  22683. const iconSpec = render$4('chevron-down', {
  22684. tag: 'div',
  22685. classes: [`${prefix}__select-chevron`]
  22686. }, sharedBackstage.providers.icons);
  22687. const fixWidthBehaviourName = generate$6('common-button-display-events');
  22688. // Should we use Id.generate here?
  22689. const customEventsName = 'dropdown-events';
  22690. const memDropdown = record(Dropdown.sketch({
  22691. ...spec.uid ? { uid: spec.uid } : {},
  22692. ...role,
  22693. ...listRole,
  22694. dom: {
  22695. tag: 'button',
  22696. classes: [prefix, `${prefix}--select`].concat(map$2(spec.classes, (c) => `${prefix}--${c}`)),
  22697. attributes: {
  22698. ...ariaLabelAttribute,
  22699. ...(isNonNullable(btnName) ? { 'data-mce-name': btnName } : {})
  22700. }
  22701. },
  22702. components: componentRenderPipeline([
  22703. optMemDisplayIcon.map((mem) => mem.asSpec()),
  22704. optMemDisplayText.map((mem) => mem.asSpec()),
  22705. Optional.some(iconSpec)
  22706. ]),
  22707. matchWidth: true,
  22708. useMinWidth: true,
  22709. // When the dropdown opens, if we are in search mode, then we want to
  22710. // focus our searcher.
  22711. onOpen: (anchor, dropdownComp, tmenuComp) => {
  22712. if (spec.searchable) {
  22713. focusSearchField(tmenuComp);
  22714. }
  22715. },
  22716. dropdownBehaviours: derive$1([
  22717. ...spec.dropdownBehaviours,
  22718. DisablingConfigs.button(() => spec.disabled || sharedBackstage.providers.checkUiComponentContext(spec.context).shouldDisable),
  22719. toggleOnReceive(() => sharedBackstage.providers.checkUiComponentContext(spec.context)),
  22720. // INVESTIGATE (TINY-9012): There was a old comment here about something not quite working, and that
  22721. // we can still get the button focused. It was probably related to Unselecting.
  22722. Unselecting.config({}),
  22723. Replacing.config({}),
  22724. ...(spec.tooltip.map((t) => Tooltipping.config(sharedBackstage.providers.tooltips.getConfig({
  22725. tooltipText: sharedBackstage.providers.translate(t),
  22726. onShow: (comp) => {
  22727. if (lift2(tooltip.get(), spec.tooltip, (tooltipStr, tt) => tt !== tooltipStr).getOr(false)) {
  22728. const translatedTooltip = sharedBackstage.providers.translate(tooltip.get().getOr(''));
  22729. Tooltipping.setComponents(comp, sharedBackstage.providers.tooltips.getComponents({ tooltipText: translatedTooltip }));
  22730. }
  22731. }
  22732. })))).toArray(),
  22733. // This is the generic way to make onSetup and onDestroy call as the component is attached /
  22734. // detached from the page/DOM.
  22735. config(customEventsName, [
  22736. onControlAttached(spec, editorOffCell),
  22737. onControlDetached(spec, editorOffCell)
  22738. ]),
  22739. config(fixWidthBehaviourName, [
  22740. runOnAttached((comp, _se) => {
  22741. if (spec.listRole !== 'listbox') {
  22742. forceInitialSize(comp);
  22743. }
  22744. }),
  22745. ]),
  22746. config('update-dropdown-width-variable', [
  22747. run$1(windowResize(), (comp, _se) => Dropdown.close(comp)),
  22748. ]),
  22749. config('menubutton-update-display-text', [
  22750. // These handlers are just using Replacing to replace either the menu
  22751. // text or the icon.
  22752. run$1(updateMenuText, (comp, se) => {
  22753. optMemDisplayText.bind((mem) => mem.getOpt(comp)).each((displayText) => {
  22754. Replacing.set(displayText, [text$2(sharedBackstage.providers.translate(se.event.text))]);
  22755. });
  22756. }),
  22757. run$1(updateMenuIcon, (comp, se) => {
  22758. optMemDisplayIcon.bind((mem) => mem.getOpt(comp)).each((displayIcon) => {
  22759. Replacing.set(displayIcon, [
  22760. renderReplaceableIconFromPack(se.event.icon, sharedBackstage.providers.icons)
  22761. ]);
  22762. });
  22763. }),
  22764. run$1(updateTooltiptext, (comp, se) => {
  22765. const translatedTooltip = sharedBackstage.providers.translate(se.event.text);
  22766. set$9(comp.element, 'aria-label', translatedTooltip);
  22767. tooltip.set(Optional.some(se.event.text));
  22768. })
  22769. ])
  22770. ]),
  22771. eventOrder: deepMerge(toolbarButtonEventOrder, {
  22772. // INVESTIGATE (TINY-9014): Explain why we need the events in this order.
  22773. // Ideally, have a test that fails when they are in a different order if order
  22774. // is important
  22775. [mousedown()]: ['focusing', 'alloy.base.behaviour', 'item-type-events', 'normal-dropdown-events'],
  22776. [attachedToDom()]: [
  22777. 'toolbar-button-events',
  22778. Tooltipping.name(),
  22779. customEventsName,
  22780. fixWidthBehaviourName,
  22781. ]
  22782. }),
  22783. sandboxBehaviours: derive$1([
  22784. Keying.config({
  22785. mode: 'special',
  22786. onLeft: onLeftOrRightInMenu,
  22787. onRight: onLeftOrRightInMenu
  22788. }),
  22789. config('dropdown-sandbox-events', [
  22790. run$1(refetchTriggerEvent, (originalSandboxComp, se) => {
  22791. handleRefetchTrigger(originalSandboxComp);
  22792. // It's a custom event that no-one else should be listening to, so stop it.
  22793. se.stop();
  22794. }),
  22795. run$1(redirectMenuItemInteractionEvent, (sandboxComp, se) => {
  22796. handleRedirectToMenuItem(sandboxComp, se);
  22797. // It's a custom event that no-one else should be listening to, so stop it.
  22798. se.stop();
  22799. })
  22800. ])
  22801. ]),
  22802. lazySink: sharedBackstage.getSink,
  22803. toggleClass: `${prefix}--active`,
  22804. parts: {
  22805. menu: {
  22806. ...part(false, spec.columns, spec.presets),
  22807. // When the menu is "searchable", use fakeFocus so that keyboard
  22808. // focus stays in the search field
  22809. fakeFocus: spec.searchable,
  22810. // We don't want to update the `aria-selected` on highlight or dehighlight for the `listbox` role because that is used to indicate the selected item
  22811. ...(spec.listRole === 'listbox' ? {} : {
  22812. onHighlightItem: updateAriaOnHighlight,
  22813. onCollapseMenu: (tmenuComp, itemCompCausingCollapse, nowActiveMenuComp) => {
  22814. // We want to update ARIA on collapsing as well, because it isn't changing
  22815. // the highlights. So what we need to do is get the right parameters to
  22816. // pass to updateAriaOnHighlight
  22817. Highlighting.getHighlighted(nowActiveMenuComp).each((itemComp) => {
  22818. updateAriaOnHighlight(tmenuComp, nowActiveMenuComp, itemComp);
  22819. });
  22820. },
  22821. onDehighlightItem: updateAriaOnDehighlight
  22822. })
  22823. }
  22824. },
  22825. getAnchorOverrides: () => {
  22826. return {
  22827. maxHeightFunction: (element, available) => {
  22828. anchored()(element, available - 10);
  22829. },
  22830. };
  22831. },
  22832. fetch: (comp) => Future.nu(curry(spec.fetch, comp))
  22833. }));
  22834. return memDropdown.asSpec();
  22835. };
  22836. const isMenuItemReference = (item) => isString(item);
  22837. const isSeparator$2 = (item) => item.type === 'separator';
  22838. const isExpandingMenuItem = (item) => has$2(item, 'getSubmenuItems');
  22839. const separator$2 = {
  22840. type: 'separator'
  22841. };
  22842. const unwrapReferences = (items, menuItems) => {
  22843. // Unwrap any string based menu item references
  22844. const realItems = foldl(items, (acc, item) => {
  22845. if (isMenuItemReference(item)) {
  22846. if (item === '') {
  22847. return acc;
  22848. }
  22849. else if (item === '|') {
  22850. // Ignore the separator if it's at the start or a duplicate
  22851. return acc.length > 0 && !isSeparator$2(acc[acc.length - 1]) ? acc.concat([separator$2]) : acc;
  22852. }
  22853. else if (has$2(menuItems, item.toLowerCase())) {
  22854. return acc.concat([menuItems[item.toLowerCase()]]);
  22855. }
  22856. else {
  22857. // TODO: Add back after TINY-3232 is implemented
  22858. // console.error('No representation for menuItem: ' + item);
  22859. return acc;
  22860. }
  22861. }
  22862. else {
  22863. return acc.concat([item]);
  22864. }
  22865. }, []);
  22866. // Remove any trailing separators
  22867. if (realItems.length > 0 && isSeparator$2(realItems[realItems.length - 1])) {
  22868. realItems.pop();
  22869. }
  22870. return realItems;
  22871. };
  22872. const getFromExpandingItem = (item, menuItems) => {
  22873. const submenuItems = item.getSubmenuItems();
  22874. const rest = expand(submenuItems, menuItems);
  22875. const newMenus = deepMerge(rest.menus, { [item.value]: rest.items });
  22876. const newExpansions = deepMerge(rest.expansions, { [item.value]: item.value });
  22877. return {
  22878. item,
  22879. menus: newMenus,
  22880. expansions: newExpansions
  22881. };
  22882. };
  22883. const generateValueIfRequired = (item) => {
  22884. // Use the value already in item if it has one.
  22885. const itemValue = get$h(item, 'value').getOrThunk(() => generate$6('generated-menu-item'));
  22886. return deepMerge({ value: itemValue }, item);
  22887. };
  22888. // Takes items, and consolidates them into its return value
  22889. const expand = (items, menuItems) => {
  22890. // Fistly, we do all substitution using the registry for any items referenced by their
  22891. // string key.
  22892. const realItems = unwrapReferences(isString(items) ? items.split(' ') : items, menuItems);
  22893. // Now that we have complete bridge Item specs for all items, we need to collect the
  22894. // submenus, items in the primary menu, and triggering menu items all into one
  22895. // giant object to from the building blocks on our TieredData
  22896. return foldr(realItems, (acc, item) => {
  22897. if (isExpandingMenuItem(item)) {
  22898. // We generate a random value for item, but only if there isn't an existing value
  22899. const itemWithValue = generateValueIfRequired(item);
  22900. // The newData isn't quite in the format you might expect. The list of items
  22901. // for an item with nested items is just the single parent item. All of the nested
  22902. // items becomes part of '.menus'. Finally, the expansions is just a map from
  22903. // the triggering item to the first submenu. Incidentally, they are given the same
  22904. // value (triggering item and submenu), for convenience.
  22905. const newData = getFromExpandingItem(itemWithValue, menuItems);
  22906. return {
  22907. // Combine all of our current submenus and items with the new submenus created by
  22908. // this item with nested subitems
  22909. menus: deepMerge(acc.menus, newData.menus),
  22910. // Add our parent item into the list of items in the *current menu*.
  22911. items: [newData.item, ...acc.items],
  22912. // Merge together our "this item opens this submenu" objects
  22913. expansions: deepMerge(acc.expansions, newData.expansions)
  22914. };
  22915. }
  22916. else {
  22917. // If we aren't creating any submenus, then all we need to do is add this item
  22918. // to the list of items in the current menu. So this is the same as an expanding
  22919. // menu item, except it doesn't add to `menus` or `expansions`.
  22920. return {
  22921. ...acc,
  22922. items: [item, ...acc.items]
  22923. };
  22924. }
  22925. }, {
  22926. menus: {},
  22927. expansions: {},
  22928. items: []
  22929. });
  22930. };
  22931. const getSearchModeForField = (settings) => {
  22932. return settings.search.fold(() => ({ searchMode: 'no-search' }), (searchSettings) => ({
  22933. searchMode: 'search-with-field',
  22934. placeholder: searchSettings.placeholder
  22935. }));
  22936. };
  22937. const getSearchModeForResults = (settings) => {
  22938. return settings.search.fold(() => ({ searchMode: 'no-search' }), (_) => ({ searchMode: 'search-with-results' }));
  22939. };
  22940. const build = (items, itemResponse, backstage, settings) => {
  22941. const primary = generate$6('primary-menu');
  22942. // The expand process identifies all the items, submenus, and triggering items
  22943. // defined by the list of items. It substitutes the strings using the values registered
  22944. // in the menuItem registry where necessary. It is the building blocks of TieredData,
  22945. // but everything is still just in the bridge item format ... nothing has been turned
  22946. // into AlloySpecs.
  22947. const data = expand(items, backstage.shared.providers.menuItems());
  22948. if (data.items.length === 0) {
  22949. return Optional.none();
  22950. }
  22951. // Only the main menu has a searchable widget (if it is enabled)
  22952. const mainMenuSearchMode = getSearchModeForField(settings);
  22953. const mainMenu = createPartialMenu(primary, data.items, itemResponse, backstage, settings.isHorizontalMenu, mainMenuSearchMode);
  22954. // The submenus do not have the search field, but will have search results for
  22955. // connecting to the search field via aria-controls
  22956. const submenuSearchMode = getSearchModeForResults(settings);
  22957. const submenus = map$1(data.menus, (menuItems, menuName) => createPartialMenu(menuName, menuItems, itemResponse, backstage,
  22958. // Currently, submenus cannot be horizontal menus (so always false)
  22959. false, submenuSearchMode));
  22960. const menus = deepMerge(submenus, wrap(primary, mainMenu));
  22961. return Optional.from(tieredMenu.tieredData(primary, menus, data.expansions));
  22962. };
  22963. const isSingleListItem = (item) => !has$2(item, 'items');
  22964. const dataAttribute = 'data-value';
  22965. const fetchItems = (dropdownComp, name, items, selectedValue, hasNestedItems) => map$2(items, (item) => {
  22966. if (!isSingleListItem(item)) {
  22967. return {
  22968. type: 'nestedmenuitem',
  22969. text: item.text,
  22970. getSubmenuItems: () => fetchItems(dropdownComp, name, item.items, selectedValue, hasNestedItems)
  22971. };
  22972. }
  22973. else {
  22974. return {
  22975. type: 'togglemenuitem',
  22976. ...(hasNestedItems ? {} : { role: 'option' }),
  22977. text: item.text,
  22978. value: item.value,
  22979. active: item.value === selectedValue,
  22980. onAction: () => {
  22981. Representing.setValue(dropdownComp, item.value);
  22982. emitWith(dropdownComp, formChangeEvent, { name });
  22983. Focusing.focus(dropdownComp);
  22984. }
  22985. };
  22986. }
  22987. });
  22988. const findItemByValue = (items, value) => findMap(items, (item) => {
  22989. if (!isSingleListItem(item)) {
  22990. return findItemByValue(item.items, value);
  22991. }
  22992. else {
  22993. return someIf(item.value === value, item);
  22994. }
  22995. });
  22996. const renderListBox = (spec, backstage, initialData) => {
  22997. const hasNestedItems = exists(spec.items, (item) => !isSingleListItem(item));
  22998. const providersBackstage = backstage.shared.providers;
  22999. const initialItem = initialData
  23000. .bind((value) => findItemByValue(spec.items, value))
  23001. .orThunk(() => head(spec.items).filter(isSingleListItem));
  23002. const pLabel = spec.label.map((label) => renderLabel$3(label, providersBackstage));
  23003. const pField = FormField.parts.field({
  23004. dom: {},
  23005. factory: {
  23006. sketch: (sketchSpec) => renderCommonDropdown({
  23007. context: spec.context,
  23008. uid: sketchSpec.uid,
  23009. text: initialItem.map((item) => item.text),
  23010. icon: Optional.none(),
  23011. tooltip: Optional.none(),
  23012. role: someIf(!hasNestedItems, 'combobox'),
  23013. ...(hasNestedItems ? {} : { listRole: 'listbox' }),
  23014. ariaLabel: spec.label,
  23015. fetch: (comp, callback) => {
  23016. const items = fetchItems(comp, spec.name, spec.items, Representing.getValue(comp), hasNestedItems);
  23017. callback(build(items, ItemResponse$1.CLOSE_ON_EXECUTE, backstage, {
  23018. isHorizontalMenu: false,
  23019. search: Optional.none()
  23020. }));
  23021. },
  23022. onSetup: constant$1(noop),
  23023. getApi: constant$1({}),
  23024. columns: 1,
  23025. presets: 'normal',
  23026. classes: [],
  23027. dropdownBehaviours: [
  23028. Tabstopping.config({}),
  23029. withComp(initialItem.map((item) => item.value), (comp) => get$g(comp.element, dataAttribute), (comp, data) => {
  23030. // We only want to update the saved value if the value set is a valid property
  23031. findItemByValue(spec.items, data)
  23032. .each((item) => {
  23033. set$9(comp.element, dataAttribute, item.value);
  23034. emitWith(comp, updateMenuText, { text: item.text });
  23035. });
  23036. })
  23037. ]
  23038. }, 'tox-listbox', backstage.shared)
  23039. }
  23040. });
  23041. const listBoxWrap = {
  23042. dom: {
  23043. tag: 'div',
  23044. classes: ['tox-listboxfield']
  23045. },
  23046. components: [pField]
  23047. };
  23048. return FormField.sketch({
  23049. dom: {
  23050. tag: 'div',
  23051. classes: ['tox-form__group']
  23052. },
  23053. components: flatten([pLabel.toArray(), [listBoxWrap]]),
  23054. fieldBehaviours: derive$1([
  23055. Disabling.config({
  23056. disabled: () => !spec.enabled || providersBackstage.checkUiComponentContext(spec.context).shouldDisable,
  23057. onDisabled: (comp) => {
  23058. FormField.getField(comp).each(Disabling.disable);
  23059. },
  23060. onEnabled: (comp) => {
  23061. FormField.getField(comp).each(Disabling.enable);
  23062. }
  23063. })
  23064. ])
  23065. });
  23066. };
  23067. const renderPanel = (spec, backstage) => ({
  23068. dom: {
  23069. tag: 'div',
  23070. classes: spec.classes
  23071. },
  23072. // All of the items passed through the form need to be put through the interpreter
  23073. // with their form part preserved.
  23074. components: map$2(spec.items, backstage.shared.interpreter)
  23075. });
  23076. const renderSelectBox = (spec, providersBackstage, initialData) => {
  23077. const translatedOptions = map$2(spec.items, (item) => ({
  23078. text: providersBackstage.translate(item.text),
  23079. value: item.value
  23080. }));
  23081. // DUPE with TextField.
  23082. const pLabel = spec.label.map((label) => renderLabel$3(label, providersBackstage));
  23083. const pField = FormField.parts.field({
  23084. // TODO: Alloy should not allow dom changing of an HTML select!
  23085. dom: {},
  23086. ...initialData.map((data) => ({ data })).getOr({}),
  23087. selectAttributes: {
  23088. size: spec.size
  23089. },
  23090. options: translatedOptions,
  23091. factory: HtmlSelect,
  23092. selectBehaviours: derive$1([
  23093. Disabling.config({
  23094. disabled: () => !spec.enabled || providersBackstage.checkUiComponentContext(spec.context).shouldDisable
  23095. }),
  23096. Tabstopping.config({}),
  23097. config('selectbox-change', [
  23098. run$1(change(), (component, _) => {
  23099. emitWith(component, formChangeEvent, { name: spec.name });
  23100. })
  23101. ])
  23102. ])
  23103. });
  23104. const chevron = spec.size > 1 ? Optional.none() :
  23105. Optional.some(render$4('chevron-down', { tag: 'div', classes: ['tox-selectfield__icon-js'] }, providersBackstage.icons));
  23106. const selectWrap = {
  23107. dom: {
  23108. tag: 'div',
  23109. classes: ['tox-selectfield']
  23110. },
  23111. components: flatten([[pField], chevron.toArray()])
  23112. };
  23113. return FormField.sketch({
  23114. dom: {
  23115. tag: 'div',
  23116. classes: ['tox-form__group']
  23117. },
  23118. components: flatten([pLabel.toArray(), [selectWrap]]),
  23119. fieldBehaviours: derive$1([
  23120. Disabling.config({
  23121. disabled: () => !spec.enabled || providersBackstage.checkUiComponentContext(spec.context).shouldDisable,
  23122. onDisabled: (comp) => {
  23123. FormField.getField(comp).each(Disabling.disable);
  23124. },
  23125. onEnabled: (comp) => {
  23126. FormField.getField(comp).each(Disabling.enable);
  23127. }
  23128. }),
  23129. toggleOnReceive(() => providersBackstage.checkUiComponentContext(spec.context))
  23130. ])
  23131. });
  23132. };
  23133. const formatSize = (size) => {
  23134. const unitDec = {
  23135. '': 0,
  23136. 'px': 0,
  23137. 'pt': 1,
  23138. 'mm': 1,
  23139. 'pc': 2,
  23140. 'ex': 2,
  23141. 'em': 2,
  23142. 'ch': 2,
  23143. 'rem': 2,
  23144. 'cm': 3,
  23145. 'in': 4,
  23146. '%': 4
  23147. };
  23148. const maxDecimal = (unit) => unit in unitDec ? unitDec[unit] : 1;
  23149. let numText = size.value.toFixed(maxDecimal(size.unit));
  23150. if (numText.indexOf('.') !== -1) {
  23151. numText = numText.replace(/\.?0*$/, '');
  23152. }
  23153. return numText + size.unit;
  23154. };
  23155. const parseSize = (sizeText) => {
  23156. const numPattern = /^\s*(\d+(?:\.\d+)?)\s*(|cm|mm|in|px|pt|pc|em|ex|ch|rem|vw|vh|vmin|vmax|%)\s*$/;
  23157. const match = numPattern.exec(sizeText);
  23158. if (match !== null) {
  23159. const value = parseFloat(match[1]);
  23160. const unit = match[2];
  23161. return Result.value({ value, unit });
  23162. }
  23163. else {
  23164. return Result.error(sizeText);
  23165. }
  23166. };
  23167. const convertUnit = (size, unit) => {
  23168. const inInch = {
  23169. '': 96,
  23170. 'px': 96,
  23171. 'pt': 72,
  23172. 'cm': 2.54,
  23173. 'pc': 12,
  23174. 'mm': 25.4,
  23175. 'in': 1
  23176. };
  23177. const supported = (u) => has$2(inInch, u);
  23178. if (size.unit === unit) {
  23179. return Optional.some(size.value);
  23180. }
  23181. else if (supported(size.unit) && supported(unit)) {
  23182. if (inInch[size.unit] === inInch[unit]) {
  23183. return Optional.some(size.value);
  23184. }
  23185. else {
  23186. return Optional.some(size.value / inInch[size.unit] * inInch[unit]);
  23187. }
  23188. }
  23189. else {
  23190. return Optional.none();
  23191. }
  23192. };
  23193. const noSizeConversion = (_input) => Optional.none();
  23194. const ratioSizeConversion = (scale, unit) => (size) => convertUnit(size, unit).map((value) => ({ value: value * scale, unit }));
  23195. const makeRatioConverter = (currentFieldText, otherFieldText) => {
  23196. const cValue = parseSize(currentFieldText).toOptional();
  23197. const oValue = parseSize(otherFieldText).toOptional();
  23198. return lift2(cValue, oValue, (cSize, oSize) => convertUnit(cSize, oSize.unit).map((val) => oSize.value / val).map((r) => ratioSizeConversion(r, oSize.unit)).getOr(noSizeConversion)).getOr(noSizeConversion);
  23199. };
  23200. const renderSizeInput = (spec, providersBackstage) => {
  23201. let converter = noSizeConversion;
  23202. const ratioEvent = generate$6('ratio-event');
  23203. const makeIcon = (iconName) => render$4(iconName, { tag: 'span', classes: ['tox-icon', 'tox-lock-icon__' + iconName] }, providersBackstage.icons);
  23204. const disabled = () => !spec.enabled || providersBackstage.checkUiComponentContext(spec.context).shouldDisable;
  23205. const toggleOnReceive$1 = toggleOnReceive(() => providersBackstage.checkUiComponentContext(spec.context));
  23206. const label = spec.label.getOr('Constrain proportions');
  23207. const translatedLabel = providersBackstage.translate(label);
  23208. const pLock = FormCoupledInputs.parts.lock({
  23209. dom: {
  23210. tag: 'button',
  23211. classes: ['tox-lock', 'tox-button', 'tox-button--naked', 'tox-button--icon'],
  23212. attributes: {
  23213. 'aria-label': translatedLabel,
  23214. 'data-mce-name': label
  23215. }
  23216. },
  23217. components: [
  23218. makeIcon('lock'),
  23219. makeIcon('unlock')
  23220. ],
  23221. buttonBehaviours: derive$1([
  23222. Disabling.config({ disabled }),
  23223. toggleOnReceive$1,
  23224. Tabstopping.config({}),
  23225. Tooltipping.config(providersBackstage.tooltips.getConfig({
  23226. tooltipText: translatedLabel
  23227. }))
  23228. ])
  23229. });
  23230. const formGroup = (components) => ({
  23231. dom: {
  23232. tag: 'div',
  23233. classes: ['tox-form__group']
  23234. },
  23235. components
  23236. });
  23237. const getFieldPart = (isField1) => FormField.parts.field({
  23238. factory: Input,
  23239. inputClasses: ['tox-textfield'],
  23240. inputBehaviours: derive$1([
  23241. Disabling.config({ disabled }),
  23242. toggleOnReceive$1,
  23243. Tabstopping.config({}),
  23244. config('size-input-events', [
  23245. run$1(focusin(), (component, _simulatedEvent) => {
  23246. emitWith(component, ratioEvent, { isField1 });
  23247. }),
  23248. run$1(change(), (component, _simulatedEvent) => {
  23249. emitWith(component, formChangeEvent, { name: spec.name });
  23250. })
  23251. ])
  23252. ]),
  23253. selectOnFocus: false
  23254. });
  23255. const getLabel = (label) => ({
  23256. dom: {
  23257. tag: 'label',
  23258. classes: ['tox-label']
  23259. },
  23260. components: [
  23261. text$2(providersBackstage.translate(label))
  23262. ]
  23263. });
  23264. const widthField = FormCoupledInputs.parts.field1(formGroup([FormField.parts.label(getLabel('Width')), getFieldPart(true)]));
  23265. const heightField = FormCoupledInputs.parts.field2(formGroup([FormField.parts.label(getLabel('Height')), getFieldPart(false)]));
  23266. return FormCoupledInputs.sketch({
  23267. dom: {
  23268. tag: 'div',
  23269. classes: ['tox-form__group']
  23270. },
  23271. components: [
  23272. {
  23273. dom: {
  23274. tag: 'div',
  23275. classes: ['tox-form__controls-h-stack']
  23276. },
  23277. components: [
  23278. // NOTE: Form coupled inputs to the FormField.sketch themselves.
  23279. widthField,
  23280. heightField,
  23281. formGroup([
  23282. getLabel(nbsp),
  23283. pLock
  23284. ])
  23285. ]
  23286. }
  23287. ],
  23288. field1Name: 'width',
  23289. field2Name: 'height',
  23290. locked: true,
  23291. markers: {
  23292. lockClass: 'tox-locked'
  23293. },
  23294. onLockedChange: (current, other, _lock) => {
  23295. parseSize(Representing.getValue(current)).each((size) => {
  23296. converter(size).each((newSize) => {
  23297. Representing.setValue(other, formatSize(newSize));
  23298. });
  23299. });
  23300. },
  23301. coupledFieldBehaviours: derive$1([
  23302. Disabling.config({
  23303. disabled,
  23304. onDisabled: (comp) => {
  23305. FormCoupledInputs.getField1(comp).bind(FormField.getField).each(Disabling.disable);
  23306. FormCoupledInputs.getField2(comp).bind(FormField.getField).each(Disabling.disable);
  23307. FormCoupledInputs.getLock(comp).each(Disabling.disable);
  23308. },
  23309. onEnabled: (comp) => {
  23310. FormCoupledInputs.getField1(comp).bind(FormField.getField).each(Disabling.enable);
  23311. FormCoupledInputs.getField2(comp).bind(FormField.getField).each(Disabling.enable);
  23312. FormCoupledInputs.getLock(comp).each(Disabling.enable);
  23313. }
  23314. }),
  23315. toggleOnReceive(() => providersBackstage.checkUiComponentContext('mode:design')),
  23316. config('size-input-events2', [
  23317. run$1(ratioEvent, (component, simulatedEvent) => {
  23318. const isField1 = simulatedEvent.event.isField1;
  23319. const optCurrent = isField1 ? FormCoupledInputs.getField1(component) : FormCoupledInputs.getField2(component);
  23320. const optOther = isField1 ? FormCoupledInputs.getField2(component) : FormCoupledInputs.getField1(component);
  23321. const value1 = optCurrent.map(Representing.getValue).getOr('');
  23322. const value2 = optOther.map(Representing.getValue).getOr('');
  23323. converter = makeRatioConverter(value1, value2);
  23324. })
  23325. ])
  23326. ])
  23327. });
  23328. };
  23329. const renderSlider = (spec, providerBackstage, initialData) => {
  23330. const labelPart = Slider.parts.label({
  23331. dom: {
  23332. tag: 'label',
  23333. classes: ['tox-label']
  23334. },
  23335. components: [
  23336. text$2(providerBackstage.translate(spec.label))
  23337. ]
  23338. });
  23339. const spectrum = Slider.parts.spectrum({
  23340. dom: {
  23341. tag: 'div',
  23342. classes: ['tox-slider__rail'],
  23343. attributes: {
  23344. role: 'presentation'
  23345. }
  23346. }
  23347. });
  23348. const thumb = Slider.parts.thumb({
  23349. dom: {
  23350. tag: 'div',
  23351. classes: ['tox-slider__handle'],
  23352. attributes: {
  23353. role: 'presentation'
  23354. }
  23355. }
  23356. });
  23357. return Slider.sketch({
  23358. dom: {
  23359. tag: 'div',
  23360. classes: ['tox-slider'],
  23361. attributes: {
  23362. role: 'presentation'
  23363. }
  23364. },
  23365. model: {
  23366. mode: 'x',
  23367. minX: spec.min,
  23368. maxX: spec.max,
  23369. getInitialValue: constant$1(initialData.getOrThunk(() => (Math.abs(spec.max) - Math.abs(spec.min)) / 2))
  23370. },
  23371. components: [
  23372. labelPart,
  23373. spectrum,
  23374. thumb
  23375. ],
  23376. sliderBehaviours: derive$1([
  23377. ComposingConfigs.self(),
  23378. Focusing.config({})
  23379. ]),
  23380. onChoose: (component, thumb, value) => {
  23381. emitWith(component, formChangeEvent, { name: spec.name, value });
  23382. },
  23383. onChange: (component, thumb, value) => {
  23384. emitWith(component, formChangeEvent, { name: spec.name, value });
  23385. },
  23386. });
  23387. };
  23388. const renderTable = (spec, providersBackstage) => {
  23389. const renderTh = (text) => ({
  23390. dom: {
  23391. tag: 'th',
  23392. innerHtml: providersBackstage.translate(text)
  23393. }
  23394. });
  23395. const renderHeader = (header) => ({
  23396. dom: {
  23397. tag: 'thead'
  23398. },
  23399. components: [
  23400. {
  23401. dom: {
  23402. tag: 'tr'
  23403. },
  23404. components: map$2(header, renderTh)
  23405. }
  23406. ]
  23407. });
  23408. const renderTd = (text) => ({ dom: { tag: 'td', innerHtml: providersBackstage.translate(text) } });
  23409. const renderTr = (row) => ({ dom: { tag: 'tr' }, components: map$2(row, renderTd) });
  23410. const renderRows = (rows) => ({ dom: { tag: 'tbody' }, components: map$2(rows, renderTr) });
  23411. return {
  23412. dom: {
  23413. tag: 'table',
  23414. classes: ['tox-dialog__table']
  23415. },
  23416. components: [
  23417. renderHeader(spec.header),
  23418. renderRows(spec.cells)
  23419. ],
  23420. behaviours: derive$1([
  23421. Tabstopping.config({}),
  23422. Focusing.config({})
  23423. ])
  23424. };
  23425. };
  23426. const renderTextField = (spec, providersBackstage) => {
  23427. const pLabel = spec.label.map((label) => renderLabel$3(label, providersBackstage));
  23428. const baseInputBehaviours = [
  23429. Disabling.config({
  23430. disabled: () => spec.disabled || providersBackstage.checkUiComponentContext(spec.context).shouldDisable
  23431. }),
  23432. toggleOnReceive(() => providersBackstage.checkUiComponentContext(spec.context)),
  23433. Keying.config({
  23434. mode: 'execution',
  23435. useEnter: spec.multiline !== true,
  23436. useControlEnter: spec.multiline === true,
  23437. execute: (comp) => {
  23438. emit(comp, formSubmitEvent);
  23439. return Optional.some(true);
  23440. }
  23441. }),
  23442. config('textfield-change', [
  23443. run$1(input(), (component, _) => {
  23444. emitWith(component, formChangeEvent, { name: spec.name });
  23445. }),
  23446. run$1(postPaste(), (component, _) => {
  23447. emitWith(component, formChangeEvent, { name: spec.name });
  23448. })
  23449. ]),
  23450. Tabstopping.config({})
  23451. ];
  23452. const validatingBehaviours = spec.validation.map((vl) => Invalidating.config({
  23453. getRoot: (input) => {
  23454. return parentElement(input.element);
  23455. },
  23456. invalidClass: 'tox-invalid',
  23457. validator: {
  23458. validate: (input) => {
  23459. const v = Representing.getValue(input);
  23460. const result = vl.validator(v);
  23461. return Future.pure(result === true ? Result.value(v) : Result.error(result));
  23462. },
  23463. validateOnLoad: vl.validateOnLoad
  23464. }
  23465. })).toArray();
  23466. const placeholder = spec.placeholder.fold(constant$1({}), (p) => ({ placeholder: providersBackstage.translate(p) }));
  23467. const inputMode = spec.inputMode.fold(constant$1({}), (mode) => ({ inputmode: mode }));
  23468. const spellcheck = spec.spellcheck.fold(constant$1({}), (spellchecker) => ({ spellcheck: spellchecker }));
  23469. const inputAttributes = {
  23470. ...spellcheck,
  23471. ...placeholder,
  23472. ...inputMode,
  23473. 'data-mce-name': spec.name
  23474. };
  23475. const pField = FormField.parts.field({
  23476. tag: spec.multiline === true ? 'textarea' : 'input',
  23477. ...spec.data.map((data) => ({ data })).getOr({}),
  23478. inputAttributes,
  23479. inputClasses: [spec.classname],
  23480. inputBehaviours: derive$1(flatten([
  23481. baseInputBehaviours,
  23482. validatingBehaviours
  23483. ])),
  23484. selectOnFocus: false,
  23485. factory: Input
  23486. });
  23487. // TINY-9331: This wrapper is needed to avoid border-radius rendering issues when the textarea has a scrollbar
  23488. const pTextField = spec.multiline ? {
  23489. dom: {
  23490. tag: 'div',
  23491. classes: ['tox-textarea-wrap']
  23492. },
  23493. components: [pField]
  23494. } : pField;
  23495. const extraClasses = spec.flex ? ['tox-form__group--stretched'] : [];
  23496. const extraClasses2 = extraClasses.concat(spec.maximized ? ['tox-form-group--maximize'] : []);
  23497. const extraBehaviours = [
  23498. Disabling.config({
  23499. disabled: () => spec.disabled || providersBackstage.checkUiComponentContext(spec.context).shouldDisable,
  23500. onDisabled: (comp) => {
  23501. FormField.getField(comp).each(Disabling.disable);
  23502. },
  23503. onEnabled: (comp) => {
  23504. FormField.getField(comp).each(Disabling.enable);
  23505. }
  23506. }),
  23507. toggleOnReceive(() => providersBackstage.checkUiComponentContext(spec.context)),
  23508. ];
  23509. return renderFormFieldWith(pLabel, pTextField, extraClasses2, extraBehaviours);
  23510. };
  23511. const renderInput = (spec, providersBackstage, initialData) => renderTextField({
  23512. name: spec.name,
  23513. multiline: false,
  23514. label: spec.label,
  23515. inputMode: spec.inputMode,
  23516. placeholder: spec.placeholder,
  23517. flex: false,
  23518. disabled: !spec.enabled,
  23519. classname: 'tox-textfield',
  23520. validation: Optional.none(),
  23521. maximized: spec.maximized,
  23522. data: initialData,
  23523. context: spec.context,
  23524. spellcheck: Optional.none(),
  23525. }, providersBackstage);
  23526. const renderTextarea = (spec, providersBackstage, initialData) => renderTextField({
  23527. name: spec.name,
  23528. multiline: true,
  23529. label: spec.label,
  23530. inputMode: Optional.none(), // type attribute is not valid for textareas
  23531. placeholder: spec.placeholder,
  23532. flex: true,
  23533. disabled: !spec.enabled,
  23534. classname: 'tox-textarea',
  23535. validation: Optional.none(),
  23536. maximized: spec.maximized,
  23537. data: initialData,
  23538. context: spec.context,
  23539. spellcheck: spec.spellcheck,
  23540. }, providersBackstage);
  23541. const getMenuButtonApi = (component) => ({
  23542. isEnabled: () => !Disabling.isDisabled(component),
  23543. setEnabled: (state) => Disabling.set(component, !state),
  23544. setActive: (state) => {
  23545. // Note: We can't use the toggling behaviour here, as the dropdown for the menu also relies on it.
  23546. // As such, we'll need to do this manually
  23547. const elm = component.element;
  23548. if (state) {
  23549. add$2(elm, "tox-tbtn--enabled" /* ToolbarButtonClasses.Ticked */);
  23550. set$9(elm, 'aria-pressed', true);
  23551. }
  23552. else {
  23553. remove$3(elm, "tox-tbtn--enabled" /* ToolbarButtonClasses.Ticked */);
  23554. remove$8(elm, 'aria-pressed');
  23555. }
  23556. },
  23557. isActive: () => has(component.element, "tox-tbtn--enabled" /* ToolbarButtonClasses.Ticked */),
  23558. setTooltip: (tooltip) => {
  23559. emitWith(component, updateTooltiptext, {
  23560. text: tooltip
  23561. });
  23562. },
  23563. setText: (text) => {
  23564. emitWith(component, updateMenuText, {
  23565. text
  23566. });
  23567. },
  23568. setIcon: (icon) => emitWith(component, updateMenuIcon, {
  23569. icon
  23570. })
  23571. });
  23572. const renderMenuButton = (spec, prefix, backstage, role, tabstopping = true, btnName) => {
  23573. const classes = spec.buttonType === 'bordered' ? ['bordered'] : [];
  23574. return renderCommonDropdown({
  23575. text: spec.text,
  23576. icon: spec.icon,
  23577. tooltip: spec.tooltip,
  23578. ariaLabel: spec.tooltip,
  23579. searchable: spec.search.isSome(),
  23580. // https://www.w3.org/TR/wai-aria-practices/examples/menubar/menubar-2/menubar-2.html
  23581. role,
  23582. fetch: (dropdownComp, callback) => {
  23583. const fetchContext = {
  23584. pattern: spec.search.isSome() ? getSearchPattern(dropdownComp) : ''
  23585. };
  23586. spec.fetch((items) => {
  23587. callback(build(items, ItemResponse$1.CLOSE_ON_EXECUTE, backstage, {
  23588. isHorizontalMenu: false,
  23589. // MenuButtons are the only dropdowns that support searchable (2022-08-16)
  23590. search: spec.search
  23591. }));
  23592. }, fetchContext, getMenuButtonApi(dropdownComp));
  23593. },
  23594. onSetup: spec.onSetup,
  23595. getApi: (comp) => getMenuButtonApi(comp),
  23596. columns: 1,
  23597. presets: 'normal',
  23598. classes,
  23599. dropdownBehaviours: [
  23600. ...(tabstopping ? [Tabstopping.config({})] : []),
  23601. ],
  23602. context: spec.context
  23603. }, prefix, backstage.shared, btnName);
  23604. };
  23605. const getFetch = (items, getButton, backstage) => {
  23606. const getMenuItemAction = (item) => (api) => {
  23607. // Update the menu item state
  23608. const newValue = !api.isActive();
  23609. api.setActive(newValue);
  23610. item.storage.set(newValue);
  23611. // Fire the form action event
  23612. backstage.shared.getSink().each((sink) => {
  23613. getButton().getOpt(sink).each((orig) => {
  23614. focus$4(orig.element);
  23615. emitWith(orig, formActionEvent, {
  23616. name: item.name,
  23617. value: item.storage.get()
  23618. });
  23619. });
  23620. });
  23621. };
  23622. const getMenuItemSetup = (item) => (api) => {
  23623. api.setActive(item.storage.get());
  23624. };
  23625. return (success) => {
  23626. success(map$2(items, (item) => {
  23627. const text = item.text.fold(() => ({}), (text) => ({
  23628. text
  23629. }));
  23630. return {
  23631. type: item.type,
  23632. active: false,
  23633. ...text,
  23634. context: item.context,
  23635. onAction: getMenuItemAction(item),
  23636. onSetup: getMenuItemSetup(item)
  23637. };
  23638. }));
  23639. };
  23640. };
  23641. const renderLabel = (text) => ({
  23642. dom: {
  23643. tag: 'span',
  23644. classes: ['tox-tree__label'],
  23645. attributes: {
  23646. 'aria-label': text,
  23647. }
  23648. },
  23649. components: [
  23650. text$2(text)
  23651. ],
  23652. });
  23653. const renderCustomStateIcon = (container, components, backstage) => {
  23654. container.customStateIcon.each((icon) => components.push(renderIcon(icon, backstage.shared.providers.icons, container.customStateIconTooltip.fold(() => [], (tooltip) => [
  23655. Tooltipping.config(backstage.shared.providers.tooltips.getConfig({
  23656. tooltipText: tooltip
  23657. }))
  23658. ]), ['tox-icon-custom-state'])));
  23659. };
  23660. const leafLabelEventsId = generate$6('leaf-label-event-id');
  23661. const renderLeafLabel = ({ leaf, onLeafAction, visible, treeId, selectedId, backstage }) => {
  23662. const internalMenuButton = leaf.menu.map((btn) => renderMenuButton(btn, 'tox-mbtn', backstage, Optional.none(), visible));
  23663. const components = [renderLabel(leaf.title)];
  23664. renderCustomStateIcon(leaf, components, backstage);
  23665. internalMenuButton.each((btn) => components.push(btn));
  23666. return Button.sketch({
  23667. dom: {
  23668. tag: 'div',
  23669. classes: ['tox-tree--leaf__label', 'tox-trbtn']
  23670. .concat(visible ? ['tox-tree--leaf__label--visible'] : []),
  23671. },
  23672. components,
  23673. role: 'treeitem',
  23674. action: (button) => {
  23675. onLeafAction(leaf.id);
  23676. button.getSystem().broadcastOn([`update-active-item-${treeId}`], {
  23677. value: leaf.id
  23678. });
  23679. },
  23680. eventOrder: {
  23681. [keydown()]: [
  23682. leafLabelEventsId,
  23683. 'keying',
  23684. ]
  23685. },
  23686. buttonBehaviours: derive$1([
  23687. ...(visible ? [Tabstopping.config({})] : []),
  23688. Toggling.config({
  23689. toggleClass: 'tox-trbtn--enabled',
  23690. toggleOnExecute: false,
  23691. aria: {
  23692. mode: 'selected'
  23693. }
  23694. }),
  23695. Receiving.config({
  23696. channels: {
  23697. [`update-active-item-${treeId}`]: {
  23698. onReceive: (comp, message) => {
  23699. (message.value === leaf.id ? Toggling.on : Toggling.off)(comp);
  23700. }
  23701. }
  23702. }
  23703. }),
  23704. config(leafLabelEventsId, [
  23705. runOnAttached((comp, _se) => {
  23706. selectedId.each((id) => {
  23707. const toggle = id === leaf.id ? Toggling.on : Toggling.off;
  23708. toggle(comp);
  23709. });
  23710. }),
  23711. run$1(keydown(), (comp, se) => {
  23712. const isLeftArrowKey = se.event.raw.code === 'ArrowLeft';
  23713. const isRightArrowKey = se.event.raw.code === 'ArrowRight';
  23714. if (isLeftArrowKey) {
  23715. ancestor$1(comp.element, '.tox-tree--directory').each((dirElement) => {
  23716. comp.getSystem().getByDom(dirElement).each((dirComp) => {
  23717. child(dirElement, '.tox-tree--directory__label').each((dirLabelElement) => {
  23718. dirComp.getSystem().getByDom(dirLabelElement).each(Focusing.focus);
  23719. });
  23720. });
  23721. });
  23722. se.stop();
  23723. }
  23724. else if (isRightArrowKey) {
  23725. se.stop();
  23726. }
  23727. })
  23728. ])
  23729. ]),
  23730. });
  23731. };
  23732. const renderIcon = (iconName, iconsProvider, behaviours, extraClasses, extraAttributes) => render$4(iconName, {
  23733. tag: 'span',
  23734. classes: [
  23735. 'tox-tree__icon-wrap',
  23736. 'tox-icon',
  23737. ].concat(extraClasses || []),
  23738. behaviours,
  23739. attributes: extraAttributes
  23740. }, iconsProvider);
  23741. const renderIconFromPack = (iconName, iconsProvider) => renderIcon(iconName, iconsProvider, []);
  23742. const directoryLabelEventsId = generate$6('directory-label-event-id');
  23743. const renderDirectoryLabel = ({ directory, visible, noChildren, backstage }) => {
  23744. const internalMenuButton = directory.menu.map((btn) => renderMenuButton(btn, 'tox-mbtn', backstage, Optional.none()));
  23745. const components = [
  23746. {
  23747. dom: {
  23748. tag: 'div',
  23749. classes: ['tox-chevron'],
  23750. },
  23751. components: [
  23752. renderIconFromPack('chevron-right', backstage.shared.providers.icons),
  23753. ]
  23754. },
  23755. renderLabel(directory.title)
  23756. ];
  23757. renderCustomStateIcon(directory, components, backstage);
  23758. internalMenuButton.each((btn) => {
  23759. components.push(btn);
  23760. });
  23761. const toggleExpandChildren = (button) => {
  23762. ancestor$1(button.element, '.tox-tree--directory').each((directoryEle) => {
  23763. button.getSystem().getByDom(directoryEle).each((directoryComp) => {
  23764. const willExpand = !Toggling.isOn(directoryComp);
  23765. Toggling.toggle(directoryComp);
  23766. emitWith(button, 'expand-tree-node', { expanded: willExpand, node: directory.id });
  23767. });
  23768. });
  23769. };
  23770. return Button.sketch({
  23771. dom: {
  23772. tag: 'div',
  23773. classes: ['tox-tree--directory__label', 'tox-trbtn'].concat(visible ? ['tox-tree--directory__label--visible'] : []),
  23774. },
  23775. components,
  23776. action: toggleExpandChildren,
  23777. eventOrder: {
  23778. [keydown()]: [
  23779. directoryLabelEventsId,
  23780. 'keying',
  23781. ]
  23782. },
  23783. buttonBehaviours: derive$1([
  23784. ...(visible ? [Tabstopping.config({})] : []),
  23785. config(directoryLabelEventsId, [
  23786. run$1(keydown(), (comp, se) => {
  23787. const isRightArrowKey = se.event.raw.code === 'ArrowRight';
  23788. const isLeftArrowKey = se.event.raw.code === 'ArrowLeft';
  23789. if (isRightArrowKey && noChildren) {
  23790. se.stop();
  23791. }
  23792. if (isRightArrowKey || isLeftArrowKey) {
  23793. ancestor$1(comp.element, '.tox-tree--directory').each((directoryEle) => {
  23794. comp.getSystem().getByDom(directoryEle).each((directoryComp) => {
  23795. if (!Toggling.isOn(directoryComp) && isRightArrowKey || Toggling.isOn(directoryComp) && isLeftArrowKey) {
  23796. toggleExpandChildren(comp);
  23797. se.stop();
  23798. }
  23799. else if (isLeftArrowKey && !Toggling.isOn(directoryComp)) {
  23800. ancestor$1(directoryComp.element, '.tox-tree--directory').each((parentDirElement) => {
  23801. child(parentDirElement, '.tox-tree--directory__label').each((parentDirLabelElement) => {
  23802. directoryComp.getSystem().getByDom(parentDirLabelElement).each(Focusing.focus);
  23803. });
  23804. });
  23805. se.stop();
  23806. }
  23807. });
  23808. });
  23809. }
  23810. })
  23811. ])
  23812. ])
  23813. });
  23814. };
  23815. const renderDirectoryChildren = ({ children, onLeafAction, visible, treeId, expandedIds, selectedId, backstage }) => {
  23816. return {
  23817. dom: {
  23818. tag: 'div',
  23819. classes: ['tox-tree--directory__children'],
  23820. },
  23821. components: children.map((item) => {
  23822. return item.type === 'leaf' ?
  23823. renderLeafLabel({ leaf: item, selectedId, onLeafAction, visible, treeId, backstage }) :
  23824. renderDirectory({ directory: item, expandedIds, selectedId, onLeafAction, labelTabstopping: visible, treeId, backstage });
  23825. }),
  23826. behaviours: derive$1([
  23827. Sliding.config({
  23828. dimension: {
  23829. property: 'height'
  23830. },
  23831. closedClass: 'tox-tree--directory__children--closed',
  23832. openClass: 'tox-tree--directory__children--open',
  23833. growingClass: 'tox-tree--directory__children--growing',
  23834. shrinkingClass: 'tox-tree--directory__children--shrinking',
  23835. expanded: visible,
  23836. }),
  23837. Replacing.config({})
  23838. ])
  23839. };
  23840. };
  23841. const directoryEventsId = generate$6('directory-event-id');
  23842. const renderDirectory = ({ directory, onLeafAction, labelTabstopping, treeId, backstage, expandedIds, selectedId }) => {
  23843. const { children } = directory;
  23844. const expandedIdsCell = Cell(expandedIds);
  23845. const computedChildrenComponents = (visible) => children.map((item) => {
  23846. return item.type === 'leaf' ?
  23847. renderLeafLabel({ leaf: item, selectedId, onLeafAction, visible, treeId, backstage }) :
  23848. renderDirectory({ directory: item, expandedIds: expandedIdsCell.get(), selectedId, onLeafAction, labelTabstopping: visible, treeId, backstage });
  23849. });
  23850. const childrenVisible = expandedIds.includes(directory.id);
  23851. return ({
  23852. dom: {
  23853. tag: 'div',
  23854. classes: ['tox-tree--directory'],
  23855. attributes: {
  23856. role: 'treeitem'
  23857. }
  23858. },
  23859. components: [
  23860. renderDirectoryLabel({ directory, visible: labelTabstopping, noChildren: directory.children.length === 0, backstage }),
  23861. renderDirectoryChildren({ children, expandedIds, selectedId, onLeafAction, visible: childrenVisible, treeId, backstage })
  23862. ],
  23863. behaviours: derive$1([
  23864. config(directoryEventsId, [
  23865. runOnAttached((comp, _se) => {
  23866. Toggling.set(comp, childrenVisible);
  23867. }),
  23868. run$1('expand-tree-node', (_cmp, se) => {
  23869. const { expanded, node } = se.event;
  23870. expandedIdsCell.set(expanded ?
  23871. [...expandedIdsCell.get(), node] :
  23872. expandedIdsCell.get().filter((id) => id !== node));
  23873. }),
  23874. ]),
  23875. Toggling.config({
  23876. ...(directory.children.length > 0 ? {
  23877. aria: {
  23878. mode: 'expanded',
  23879. },
  23880. } : {}),
  23881. toggleClass: 'tox-tree--directory--expanded',
  23882. onToggled: (comp, childrenVisible) => {
  23883. const childrenComp = comp.components()[1];
  23884. const newChildren = computedChildrenComponents(childrenVisible);
  23885. if (childrenVisible) {
  23886. Sliding.grow(childrenComp);
  23887. }
  23888. else {
  23889. Sliding.shrink(childrenComp);
  23890. }
  23891. Replacing.set(childrenComp, newChildren);
  23892. },
  23893. }),
  23894. ])
  23895. });
  23896. };
  23897. const treeEventsId = generate$6('tree-event-id');
  23898. const renderTree = (spec, backstage) => {
  23899. const onLeafAction = spec.onLeafAction.getOr(noop);
  23900. const onToggleExpand = spec.onToggleExpand.getOr(noop);
  23901. const defaultExpandedIds = spec.defaultExpandedIds;
  23902. const expandedIds = Cell(defaultExpandedIds);
  23903. const selectedIdCell = Cell(spec.defaultSelectedId);
  23904. const treeId = generate$6('tree-id');
  23905. const children = (selectedId, expandedIds) => spec.items.map((item) => {
  23906. return item.type === 'leaf' ?
  23907. renderLeafLabel({ leaf: item, selectedId, onLeafAction, visible: true, treeId, backstage }) :
  23908. renderDirectory({ directory: item, selectedId, onLeafAction, expandedIds, labelTabstopping: true, treeId, backstage });
  23909. });
  23910. return {
  23911. dom: {
  23912. tag: 'div',
  23913. classes: ['tox-tree'],
  23914. attributes: {
  23915. role: 'tree'
  23916. }
  23917. },
  23918. components: children(selectedIdCell.get(), expandedIds.get()),
  23919. behaviours: derive$1([
  23920. Keying.config({
  23921. mode: 'flow',
  23922. selector: '.tox-tree--leaf__label--visible, .tox-tree--directory__label--visible',
  23923. cycles: false,
  23924. }),
  23925. config(treeEventsId, [
  23926. run$1('expand-tree-node', (_cmp, se) => {
  23927. const { expanded, node } = se.event;
  23928. expandedIds.set(expanded ?
  23929. [...expandedIds.get(), node] :
  23930. expandedIds.get().filter((id) => id !== node));
  23931. onToggleExpand(expandedIds.get(), { expanded, node });
  23932. })
  23933. ]),
  23934. Receiving.config({
  23935. channels: {
  23936. [`update-active-item-${treeId}`]: {
  23937. onReceive: (comp, message) => {
  23938. selectedIdCell.set(Optional.some(message.value));
  23939. Replacing.set(comp, children(Optional.some(message.value), expandedIds.get()));
  23940. }
  23941. }
  23942. }
  23943. }),
  23944. Replacing.config({})
  23945. ])
  23946. };
  23947. };
  23948. const renderCommonSpec = (spec, actionOpt, extraBehaviours = [], dom, components, tooltip, providersBackstage) => {
  23949. const action = actionOpt.fold(() => ({}), (action) => ({
  23950. action
  23951. }));
  23952. const common = {
  23953. buttonBehaviours: derive$1([
  23954. DisablingConfigs.item(() => !spec.enabled || providersBackstage.checkUiComponentContext(spec.context).shouldDisable),
  23955. toggleOnReceive(() => providersBackstage.checkUiComponentContext(spec.context)),
  23956. Tabstopping.config({}),
  23957. ...tooltip.map((t) => Tooltipping.config(providersBackstage.tooltips.getConfig({
  23958. tooltipText: providersBackstage.translate(t)
  23959. }))).toArray(),
  23960. config('button press', [
  23961. preventDefault('click')
  23962. ])
  23963. ].concat(extraBehaviours)),
  23964. eventOrder: {
  23965. click: ['button press', 'alloy.base.behaviour'],
  23966. mousedown: ['button press', 'alloy.base.behaviour']
  23967. },
  23968. ...action
  23969. };
  23970. const domFinal = deepMerge(common, { dom });
  23971. return deepMerge(domFinal, { components });
  23972. };
  23973. // An IconButton just seems to be a button that *cannot* have text, but
  23974. // can have a tooltip. It's only used for the More Drawer button at the moment.
  23975. const renderIconButtonSpec = (spec, action, providersBackstage, extraBehaviours = [], btnName) => {
  23976. const tooltipAttributes = spec.tooltip.map((tooltip) => ({
  23977. 'aria-label': providersBackstage.translate(tooltip),
  23978. })).getOr({});
  23979. const dom = {
  23980. tag: 'button',
  23981. classes: ["tox-tbtn" /* ToolbarButtonClasses.Button */],
  23982. attributes: { ...tooltipAttributes, 'data-mce-name': btnName }
  23983. };
  23984. const icon = spec.icon.map((iconName) => renderIconFromPack$1(iconName, providersBackstage.icons));
  23985. const components = componentRenderPipeline([
  23986. icon
  23987. ]);
  23988. return renderCommonSpec(spec, action, extraBehaviours, dom, components, spec.tooltip, providersBackstage);
  23989. };
  23990. const calculateClassesFromButtonType = (buttonType) => {
  23991. switch (buttonType) {
  23992. case 'primary':
  23993. return ['tox-button'];
  23994. case 'toolbar':
  23995. return ['tox-tbtn'];
  23996. case 'secondary':
  23997. default:
  23998. return ['tox-button', 'tox-button--secondary'];
  23999. }
  24000. };
  24001. // Maybe the list of extraBehaviours is better than doing a Merger.deepMerge that
  24002. // we do elsewhere? Not sure.
  24003. const renderButtonSpec = (spec, action, providersBackstage, extraBehaviours = [], extraClasses = []) => {
  24004. // It's a bit confusing that this is called text. It seems to be a tooltip. Although I can see
  24005. // that it's used if there is no icon
  24006. const translatedText = providersBackstage.translate(spec.text);
  24007. const icon = spec.icon.map((iconName) => renderIconFromPack$1(iconName, providersBackstage.icons));
  24008. const components = [icon.getOrThunk(() => text$2(translatedText))];
  24009. // The old default is based on the now-deprecated 'primary' property. `buttonType` takes precedence now.
  24010. const buttonType = spec.buttonType.getOr(!spec.primary && !spec.borderless ? 'secondary' : 'primary');
  24011. const baseClasses = calculateClassesFromButtonType(buttonType);
  24012. const classes = [
  24013. ...baseClasses,
  24014. ...icon.isSome() ? ['tox-button--icon'] : [],
  24015. ...spec.borderless ? ['tox-button--naked'] : [],
  24016. ...extraClasses
  24017. ];
  24018. const dom = {
  24019. tag: 'button',
  24020. classes,
  24021. attributes: {
  24022. 'aria-label': translatedText,
  24023. 'data-mce-name': spec.text
  24024. }
  24025. };
  24026. // Only provide a tooltip if we are using an icon. This is because above, a button is only an icon
  24027. // or text, and not both.
  24028. const optTooltip = spec.icon.map(constant$1(translatedText));
  24029. return renderCommonSpec(spec, action, extraBehaviours, dom, components, optTooltip, providersBackstage);
  24030. };
  24031. // This actually seems to be a button on the dialog for UrlInput only (browse). Interesting.
  24032. const renderButton$1 = (spec, action, providersBackstage, extraBehaviours = [], extraClasses = []) => {
  24033. const buttonSpec = renderButtonSpec(spec, Optional.some(action), providersBackstage, extraBehaviours, extraClasses);
  24034. return Button.sketch(buttonSpec);
  24035. };
  24036. const getAction = (name, buttonType) => (comp) => {
  24037. if (buttonType === 'custom') {
  24038. emitWith(comp, formActionEvent, {
  24039. name,
  24040. value: {}
  24041. });
  24042. }
  24043. else if (buttonType === 'submit') {
  24044. emit(comp, formSubmitEvent);
  24045. }
  24046. else if (buttonType === 'cancel') {
  24047. emit(comp, formCancelEvent);
  24048. }
  24049. else {
  24050. // eslint-disable-next-line no-console
  24051. console.error('Unknown button type: ', buttonType);
  24052. }
  24053. };
  24054. const isMenuFooterButtonSpec = (spec, buttonType) => buttonType === 'menu';
  24055. const isNormalFooterButtonSpec = (spec, buttonType) => buttonType === 'custom' || buttonType === 'cancel' || buttonType === 'submit';
  24056. const isToggleButtonSpec = (spec, buttonType) => buttonType === 'togglebutton';
  24057. const renderToggleButton = (spec, providers, btnName) => {
  24058. var _a, _b;
  24059. const optMemIcon = spec.icon
  24060. .map((memIcon) => renderReplaceableIconFromPack(memIcon, providers.icons))
  24061. .map(record);
  24062. const action = (comp) => {
  24063. emitWith(comp, formActionEvent, {
  24064. name: spec.name,
  24065. value: {
  24066. setIcon: (newIcon) => {
  24067. optMemIcon.map((memIcon) => memIcon.getOpt(comp).each((displayIcon) => {
  24068. Replacing.set(displayIcon, [
  24069. renderReplaceableIconFromPack(newIcon, providers.icons)
  24070. ]);
  24071. }));
  24072. }
  24073. }
  24074. });
  24075. };
  24076. // The old default is based on the now-deprecated 'primary' property. `buttonType` takes precedence now.
  24077. const buttonType = spec.buttonType.getOr(!spec.primary ? 'secondary' : 'primary');
  24078. const buttonSpec = {
  24079. ...spec,
  24080. name: (_a = spec.name) !== null && _a !== void 0 ? _a : '',
  24081. primary: buttonType === 'primary',
  24082. tooltip: spec.tooltip,
  24083. enabled: (_b = spec.enabled) !== null && _b !== void 0 ? _b : false,
  24084. borderless: false
  24085. };
  24086. const tooltipAttributes = buttonSpec.tooltip.or(spec.text).map((tooltip) => ({
  24087. 'aria-label': providers.translate(tooltip),
  24088. })).getOr({});
  24089. const buttonTypeClasses = calculateClassesFromButtonType(buttonType !== null && buttonType !== void 0 ? buttonType : 'secondary');
  24090. const showIconAndText = spec.icon.isSome() && spec.text.isSome();
  24091. const dom = {
  24092. tag: 'button',
  24093. classes: [
  24094. ...buttonTypeClasses.concat(spec.icon.isSome() ? ['tox-button--icon'] : []),
  24095. ...(spec.active ? ["tox-button--enabled" /* ViewButtonClasses.Ticked */] : []),
  24096. ...(showIconAndText ? ['tox-button--icon-and-text'] : [])
  24097. ],
  24098. attributes: {
  24099. ...tooltipAttributes,
  24100. ...(isNonNullable(btnName) ? { 'data-mce-name': btnName } : {})
  24101. }
  24102. };
  24103. const extraBehaviours = [];
  24104. const translatedText = providers.translate(spec.text.getOr(''));
  24105. const translatedTextComponed = text$2(translatedText);
  24106. const iconComp = componentRenderPipeline([optMemIcon.map((memIcon) => memIcon.asSpec())]);
  24107. const components = [
  24108. ...iconComp,
  24109. ...(spec.text.isSome() ? [translatedTextComponed] : [])
  24110. ];
  24111. const iconButtonSpec = renderCommonSpec(buttonSpec, Optional.some(action), extraBehaviours, dom, components, spec.tooltip, providers);
  24112. return Button.sketch(iconButtonSpec);
  24113. };
  24114. const renderFooterButton = (spec, buttonType, backstage) => {
  24115. if (isMenuFooterButtonSpec(spec, buttonType)) {
  24116. const getButton = () => memButton;
  24117. const menuButtonSpec = spec;
  24118. const fixedSpec = {
  24119. ...spec,
  24120. buttonType: 'default',
  24121. type: 'menubutton',
  24122. // Currently, dialog-based menu buttons cannot be searchable.
  24123. search: Optional.none(),
  24124. onSetup: (api) => {
  24125. api.setEnabled(spec.enabled);
  24126. return noop;
  24127. },
  24128. fetch: getFetch(menuButtonSpec.items, getButton, backstage)
  24129. };
  24130. const memButton = record(renderMenuButton(fixedSpec, "tox-tbtn" /* ToolbarButtonClasses.Button */, backstage, Optional.none(), true, spec.text.or(spec.tooltip).getOrUndefined()));
  24131. return memButton.asSpec();
  24132. }
  24133. else if (isNormalFooterButtonSpec(spec, buttonType)) {
  24134. const action = getAction(spec.name, buttonType);
  24135. const buttonSpec = {
  24136. ...spec,
  24137. context: buttonType === 'cancel' ? 'any' : spec.context,
  24138. borderless: false
  24139. };
  24140. return renderButton$1(buttonSpec, action, backstage.shared.providers, []);
  24141. }
  24142. else if (isToggleButtonSpec(spec, buttonType)) {
  24143. return renderToggleButton(spec, backstage.shared.providers, spec.text.or(spec.tooltip).getOrUndefined());
  24144. }
  24145. else {
  24146. // eslint-disable-next-line no-console
  24147. console.error('Unknown footer button type: ', buttonType);
  24148. throw new Error('Unknown footer button type');
  24149. }
  24150. };
  24151. const renderDialogButton = (spec, providersBackstage) => {
  24152. const action = getAction(spec.name, 'custom');
  24153. return renderFormField(Optional.none(), FormField.parts.field({
  24154. factory: Button,
  24155. ...renderButtonSpec(spec, Optional.some(action), providersBackstage, [
  24156. memory(''),
  24157. ComposingConfigs.self()
  24158. ])
  24159. }));
  24160. };
  24161. const separator$1 = {
  24162. type: 'separator'
  24163. };
  24164. const toMenuItem = (target) => ({
  24165. type: 'menuitem',
  24166. value: target.url,
  24167. text: target.title,
  24168. meta: {
  24169. attach: target.attach
  24170. },
  24171. onAction: noop
  24172. });
  24173. const staticMenuItem = (title, url) => ({
  24174. type: 'menuitem',
  24175. value: url,
  24176. text: title,
  24177. meta: {
  24178. attach: undefined
  24179. },
  24180. onAction: noop
  24181. });
  24182. const toMenuItems = (targets) => map$2(targets, toMenuItem);
  24183. const filterLinkTargets = (type, targets) => filter$2(targets, (target) => target.type === type);
  24184. const filteredTargets = (type, targets) => toMenuItems(filterLinkTargets(type, targets));
  24185. const headerTargets = (linkInfo) => filteredTargets('header', linkInfo.targets);
  24186. const anchorTargets = (linkInfo) => filteredTargets('anchor', linkInfo.targets);
  24187. const anchorTargetTop = (linkInfo) => Optional.from(linkInfo.anchorTop).map((url) => staticMenuItem('<top>', url)).toArray();
  24188. const anchorTargetBottom = (linkInfo) => Optional.from(linkInfo.anchorBottom).map((url) => staticMenuItem('<bottom>', url)).toArray();
  24189. const historyTargets = (history) => map$2(history, (url) => staticMenuItem(url, url));
  24190. const joinMenuLists = (items) => {
  24191. return foldl(items, (a, b) => {
  24192. const bothEmpty = a.length === 0 || b.length === 0;
  24193. return bothEmpty ? a.concat(b) : a.concat(separator$1, b);
  24194. }, []);
  24195. };
  24196. const filterByQuery = (term, menuItems) => {
  24197. const lowerCaseTerm = term.toLowerCase();
  24198. return filter$2(menuItems, (item) => {
  24199. var _a;
  24200. const text = item.meta !== undefined && item.meta.text !== undefined ? item.meta.text : item.text;
  24201. const value = (_a = item.value) !== null && _a !== void 0 ? _a : '';
  24202. return contains$1(text.toLowerCase(), lowerCaseTerm) || contains$1(value.toLowerCase(), lowerCaseTerm);
  24203. });
  24204. };
  24205. const getItems = (fileType, input, urlBackstage) => {
  24206. var _a, _b;
  24207. const urlInputValue = Representing.getValue(input);
  24208. const term = (_b = (_a = urlInputValue === null || urlInputValue === void 0 ? void 0 : urlInputValue.meta) === null || _a === void 0 ? void 0 : _a.text) !== null && _b !== void 0 ? _b : urlInputValue.value;
  24209. const info = urlBackstage.getLinkInformation();
  24210. return info.fold(() => [], (linkInfo) => {
  24211. const history = filterByQuery(term, historyTargets(urlBackstage.getHistory(fileType)));
  24212. return fileType === 'file' ? joinMenuLists([
  24213. history,
  24214. filterByQuery(term, headerTargets(linkInfo)),
  24215. filterByQuery(term, flatten([
  24216. anchorTargetTop(linkInfo),
  24217. anchorTargets(linkInfo),
  24218. anchorTargetBottom(linkInfo)
  24219. ]))
  24220. ])
  24221. : history;
  24222. });
  24223. };
  24224. const errorId = generate$6('aria-invalid');
  24225. const renderUrlInput = (spec, backstage, urlBackstage, initialData) => {
  24226. const providersBackstage = backstage.shared.providers;
  24227. const updateHistory = (component) => {
  24228. const urlEntry = Representing.getValue(component);
  24229. urlBackstage.addToHistory(urlEntry.value, spec.filetype);
  24230. };
  24231. // TODO: Make alloy's typeahead only swallow enter and escape if menu is open
  24232. const typeaheadSpec = {
  24233. ...initialData.map((initialData) => ({ initialData })).getOr({}),
  24234. dismissOnBlur: true,
  24235. inputClasses: ['tox-textfield'],
  24236. sandboxClasses: ['tox-dialog__popups'],
  24237. inputAttributes: {
  24238. 'aria-errormessage': errorId,
  24239. 'type': 'url'
  24240. },
  24241. minChars: 0,
  24242. responseTime: 0,
  24243. fetch: (input) => {
  24244. const items = getItems(spec.filetype, input, urlBackstage);
  24245. const tdata = build(items, ItemResponse$1.BUBBLE_TO_SANDBOX, backstage, {
  24246. isHorizontalMenu: false,
  24247. search: Optional.none()
  24248. });
  24249. return Future.pure(tdata);
  24250. },
  24251. getHotspot: (comp) => memUrlBox.getOpt(comp),
  24252. onSetValue: (comp, _newValue) => {
  24253. if (comp.hasConfigured(Invalidating)) {
  24254. Invalidating.run(comp).get(noop);
  24255. }
  24256. },
  24257. typeaheadBehaviours: derive$1([
  24258. ...urlBackstage.getValidationHandler().map((handler) => Invalidating.config({
  24259. getRoot: (comp) => parentElement(comp.element),
  24260. invalidClass: 'tox-control-wrap--status-invalid',
  24261. notify: {
  24262. onInvalid: (comp, err) => {
  24263. memInvalidIcon.getOpt(comp).each((invalidComp) => {
  24264. set$9(invalidComp.element, 'title', providersBackstage.translate(err));
  24265. });
  24266. }
  24267. },
  24268. validator: {
  24269. validate: (input) => {
  24270. const urlEntry = Representing.getValue(input);
  24271. return FutureResult.nu((completer) => {
  24272. handler({ type: spec.filetype, url: urlEntry.value }, (validation) => {
  24273. if (validation.status === 'invalid') {
  24274. const err = Result.error(validation.message);
  24275. completer(err);
  24276. }
  24277. else {
  24278. const val = Result.value(validation.message);
  24279. completer(val);
  24280. }
  24281. });
  24282. });
  24283. },
  24284. validateOnLoad: false
  24285. }
  24286. })).toArray(),
  24287. Disabling.config({
  24288. disabled: () => !spec.enabled || providersBackstage.checkUiComponentContext(spec.context).shouldDisable
  24289. }),
  24290. Tabstopping.config({}),
  24291. config('urlinput-events',
  24292. // We want to get fast feedback for the link dialog, but not sure about others
  24293. [
  24294. run$1(input(), (comp) => {
  24295. const currentValue = get$5(comp.element);
  24296. const trimmedValue = currentValue.trim();
  24297. if (trimmedValue !== currentValue) {
  24298. set$4(comp.element, trimmedValue);
  24299. }
  24300. if (spec.filetype === 'file') {
  24301. emitWith(comp, formChangeEvent, { name: spec.name });
  24302. }
  24303. }),
  24304. run$1(change(), (comp) => {
  24305. emitWith(comp, formChangeEvent, { name: spec.name });
  24306. updateHistory(comp);
  24307. }),
  24308. run$1(postPaste(), (comp) => {
  24309. emitWith(comp, formChangeEvent, { name: spec.name });
  24310. updateHistory(comp);
  24311. })
  24312. ])
  24313. ]),
  24314. eventOrder: {
  24315. [input()]: ['streaming', 'urlinput-events', 'invalidating']
  24316. },
  24317. model: {
  24318. getDisplayText: (itemData) => itemData.value,
  24319. selectsOver: false,
  24320. populateFromBrowse: false
  24321. },
  24322. markers: {
  24323. openClass: 'tox-textfield--popup-open'
  24324. },
  24325. lazySink: backstage.shared.getSink,
  24326. parts: {
  24327. menu: part(false, 1, 'normal')
  24328. },
  24329. onExecute: (_menu, component, _entry) => {
  24330. emitWith(component, formSubmitEvent, {});
  24331. },
  24332. onItemExecute: (typeahead, _sandbox, _item, _value) => {
  24333. updateHistory(typeahead);
  24334. emitWith(typeahead, formChangeEvent, { name: spec.name });
  24335. }
  24336. };
  24337. const pField = FormField.parts.field({
  24338. ...typeaheadSpec,
  24339. factory: Typeahead
  24340. });
  24341. const pLabel = spec.label.map((label) => renderLabel$3(label, providersBackstage));
  24342. // TODO: Consider a way of merging with Checkbox.
  24343. const makeIcon = (name, errId, icon = name, label = name) => render$4(icon, {
  24344. tag: 'div',
  24345. classes: ['tox-icon', 'tox-control-wrap__status-icon-' + name],
  24346. attributes: {
  24347. 'title': providersBackstage.translate(label),
  24348. 'aria-live': 'polite',
  24349. ...errId.fold(() => ({}), (id) => ({ id }))
  24350. }
  24351. }, providersBackstage.icons);
  24352. const memInvalidIcon = record(makeIcon('invalid', Optional.some(errorId), 'warning'));
  24353. const memStatus = record({
  24354. dom: {
  24355. tag: 'div',
  24356. classes: ['tox-control-wrap__status-icon-wrap']
  24357. },
  24358. components: [
  24359. // Include the 'valid' and 'unknown' icons here only if they are to be displayed
  24360. memInvalidIcon.asSpec()
  24361. ]
  24362. });
  24363. const optUrlPicker = urlBackstage.getUrlPicker(spec.filetype);
  24364. const browseUrlEvent = generate$6('browser.url.event');
  24365. const memUrlBox = record({
  24366. dom: {
  24367. tag: 'div',
  24368. classes: ['tox-control-wrap']
  24369. },
  24370. components: [pField, memStatus.asSpec()],
  24371. behaviours: derive$1([
  24372. Disabling.config({
  24373. disabled: () => !spec.enabled || providersBackstage.checkUiComponentContext(spec.context).shouldDisable
  24374. })
  24375. ])
  24376. });
  24377. const memUrlPickerButton = record(renderButton$1({
  24378. context: spec.context,
  24379. name: spec.name,
  24380. icon: Optional.some('browse'),
  24381. text: spec.picker_text.or(spec.label).getOr(''),
  24382. enabled: spec.enabled,
  24383. primary: false,
  24384. buttonType: Optional.none(),
  24385. borderless: true
  24386. }, (component) => emit(component, browseUrlEvent), providersBackstage, [], ['tox-browse-url']));
  24387. const controlHWrapper = () => ({
  24388. dom: {
  24389. tag: 'div',
  24390. classes: ['tox-form__controls-h-stack']
  24391. },
  24392. components: flatten([
  24393. [memUrlBox.asSpec()],
  24394. optUrlPicker.map(() => memUrlPickerButton.asSpec()).toArray()
  24395. ])
  24396. });
  24397. const openUrlPicker = (comp) => {
  24398. Composing.getCurrent(comp).each((field) => {
  24399. const componentData = Representing.getValue(field);
  24400. const urlData = {
  24401. fieldname: spec.name,
  24402. ...componentData
  24403. };
  24404. optUrlPicker.each((picker) => {
  24405. picker(urlData).get((chosenData) => {
  24406. Representing.setValue(field, chosenData);
  24407. emitWith(comp, formChangeEvent, { name: spec.name });
  24408. });
  24409. });
  24410. });
  24411. };
  24412. return FormField.sketch({
  24413. dom: renderFormFieldDom(),
  24414. components: pLabel.toArray().concat([
  24415. controlHWrapper()
  24416. ]),
  24417. fieldBehaviours: derive$1([
  24418. Disabling.config({
  24419. disabled: () => !spec.enabled || providersBackstage.checkUiComponentContext(spec.context).shouldDisable,
  24420. onDisabled: (comp) => {
  24421. FormField.getField(comp).each(Disabling.disable);
  24422. memUrlPickerButton.getOpt(comp).each(Disabling.disable);
  24423. },
  24424. onEnabled: (comp) => {
  24425. FormField.getField(comp).each(Disabling.enable);
  24426. memUrlPickerButton.getOpt(comp).each(Disabling.enable);
  24427. }
  24428. }),
  24429. toggleOnReceive(() => providersBackstage.checkUiComponentContext(spec.context)),
  24430. config('url-input-events', [
  24431. run$1(browseUrlEvent, openUrlPicker)
  24432. ])
  24433. ])
  24434. });
  24435. };
  24436. const renderAlertBanner = (spec, providersBackstage) => {
  24437. const icon = get(spec.icon, providersBackstage.icons);
  24438. // For using the alert banner inside a dialog
  24439. return Container.sketch({
  24440. dom: {
  24441. tag: 'div',
  24442. attributes: {
  24443. role: 'alert'
  24444. },
  24445. classes: ['tox-notification', 'tox-notification--in', `tox-notification--${spec.level}`]
  24446. },
  24447. components: [
  24448. {
  24449. dom: {
  24450. tag: 'div',
  24451. classes: ['tox-notification__icon'],
  24452. innerHtml: !spec.url ? icon : undefined
  24453. },
  24454. components: spec.url ? [
  24455. Button.sketch({
  24456. dom: {
  24457. tag: 'button',
  24458. classes: ['tox-button', 'tox-button--naked', 'tox-button--icon'],
  24459. innerHtml: icon,
  24460. attributes: {
  24461. title: providersBackstage.translate(spec.iconTooltip)
  24462. }
  24463. },
  24464. // TODO: aria label this button!
  24465. action: (comp) => emitWith(comp, formActionEvent, { name: 'alert-banner', value: spec.url }),
  24466. buttonBehaviours: derive$1([
  24467. addFocusableBehaviour()
  24468. ])
  24469. })
  24470. ] : undefined
  24471. },
  24472. {
  24473. dom: {
  24474. tag: 'div',
  24475. classes: ['tox-notification__body'],
  24476. // TODO: AP-247: Escape this text so that it can't contain script tags
  24477. innerHtml: providersBackstage.translate(spec.text)
  24478. }
  24479. }
  24480. ]
  24481. });
  24482. };
  24483. const renderCheckbox = (spec, providerBackstage, initialData) => {
  24484. const toggleCheckboxHandler = (comp) => {
  24485. comp.element.dom.click();
  24486. return Optional.some(true);
  24487. };
  24488. const pField = FormField.parts.field({
  24489. factory: { sketch: identity },
  24490. dom: {
  24491. tag: 'input',
  24492. classes: ['tox-checkbox__input'],
  24493. attributes: {
  24494. type: 'checkbox'
  24495. }
  24496. },
  24497. behaviours: derive$1([
  24498. ComposingConfigs.self(),
  24499. Disabling.config({
  24500. disabled: () => !spec.enabled || providerBackstage.checkUiComponentContext(spec.context).shouldDisable,
  24501. onDisabled: (component) => {
  24502. parentElement(component.element).each((element) => add$2(element, 'tox-checkbox--disabled'));
  24503. },
  24504. onEnabled: (component) => {
  24505. parentElement(component.element).each((element) => remove$3(element, 'tox-checkbox--disabled'));
  24506. }
  24507. }),
  24508. Tabstopping.config({}),
  24509. Focusing.config({}),
  24510. withElement(initialData, get$9, set$5),
  24511. Keying.config({
  24512. mode: 'special',
  24513. onEnter: toggleCheckboxHandler,
  24514. onSpace: toggleCheckboxHandler,
  24515. stopSpaceKeyup: true
  24516. }),
  24517. config('checkbox-events', [
  24518. run$1(change(), (component, _) => {
  24519. emitWith(component, formChangeEvent, { name: spec.name });
  24520. })
  24521. ])
  24522. ])
  24523. });
  24524. const pLabel = FormField.parts.label({
  24525. dom: {
  24526. tag: 'span',
  24527. classes: ['tox-checkbox__label']
  24528. },
  24529. components: [
  24530. text$2(providerBackstage.translate(spec.label))
  24531. ],
  24532. behaviours: derive$1([
  24533. Unselecting.config({})
  24534. ])
  24535. });
  24536. const makeIcon = (className) => {
  24537. const iconName = className === 'checked' ? 'selected' : 'unselected';
  24538. return render$4(iconName, { tag: 'span', classes: ['tox-icon', 'tox-checkbox-icon__' + className] }, providerBackstage.icons);
  24539. };
  24540. const memIcons = record({
  24541. dom: {
  24542. tag: 'div',
  24543. classes: ['tox-checkbox__icons']
  24544. },
  24545. components: [
  24546. makeIcon('checked'),
  24547. makeIcon('unchecked')
  24548. ]
  24549. });
  24550. return FormField.sketch({
  24551. dom: {
  24552. tag: 'label',
  24553. classes: ['tox-checkbox']
  24554. },
  24555. components: [
  24556. pField,
  24557. memIcons.asSpec(),
  24558. pLabel
  24559. ],
  24560. fieldBehaviours: derive$1([
  24561. Disabling.config({
  24562. disabled: () => !spec.enabled || providerBackstage.checkUiComponentContext(spec.context).shouldDisable,
  24563. }),
  24564. toggleOnReceive(() => providerBackstage.checkUiComponentContext(spec.context))
  24565. ])
  24566. });
  24567. };
  24568. const renderHtmlPanel = (spec, providersBackstage) => {
  24569. const classes = ['tox-form__group', ...(spec.stretched ? ['tox-form__group--stretched'] : [])];
  24570. const init = config('htmlpanel', [
  24571. runOnAttached((comp) => {
  24572. spec.onInit(comp.element.dom);
  24573. })
  24574. ]);
  24575. if (spec.presets === 'presentation') {
  24576. return Container.sketch({
  24577. dom: {
  24578. tag: 'div',
  24579. classes,
  24580. innerHtml: spec.html
  24581. },
  24582. containerBehaviours: derive$1([
  24583. Tooltipping.config({
  24584. ...providersBackstage.tooltips.getConfig({
  24585. tooltipText: '',
  24586. onShow: (comp) => {
  24587. descendant(comp.element, '[data-mce-tooltip]:hover').orThunk(() => search(comp.element))
  24588. .each((current) => {
  24589. getOpt(current, 'data-mce-tooltip').each((text) => {
  24590. Tooltipping.setComponents(comp, providersBackstage.tooltips.getComponents({ tooltipText: text }));
  24591. });
  24592. });
  24593. },
  24594. }),
  24595. mode: 'children-normal',
  24596. anchor: (comp) => ({
  24597. type: 'node',
  24598. node: descendant(comp.element, '[data-mce-tooltip]:hover')
  24599. .orThunk(() => search(comp.element).filter((current) => getOpt(current, 'data-mce-tooltip').isSome())),
  24600. root: comp.element,
  24601. layouts: {
  24602. onLtr: constant$1([south$2, north$2, southeast$2, northeast$2, southwest$2, northwest$2]),
  24603. onRtl: constant$1([south$2, north$2, southeast$2, northeast$2, southwest$2, northwest$2])
  24604. },
  24605. bubble: nu$6(0, -2, {}),
  24606. })
  24607. }),
  24608. init
  24609. ])
  24610. });
  24611. }
  24612. else {
  24613. return Container.sketch({
  24614. dom: {
  24615. tag: 'div',
  24616. classes,
  24617. innerHtml: spec.html,
  24618. attributes: {
  24619. role: 'document'
  24620. }
  24621. },
  24622. containerBehaviours: derive$1([
  24623. Tabstopping.config({}),
  24624. Focusing.config({}),
  24625. init
  24626. ])
  24627. });
  24628. }
  24629. };
  24630. const make$1 = (render) => {
  24631. return (parts, spec, dialogData, backstage, getCompByName) => get$h(spec, 'name').fold(() => render(spec, backstage, Optional.none(), getCompByName), (fieldName) => parts.field(fieldName, render(spec, backstage, get$h(dialogData, fieldName), getCompByName)));
  24632. };
  24633. const makeIframe = (render) => (parts, spec, dialogData, backstage, getCompByName) => {
  24634. const iframeSpec = deepMerge(spec, {
  24635. source: 'dynamic'
  24636. });
  24637. return make$1(render)(parts, iframeSpec, dialogData, backstage, getCompByName);
  24638. };
  24639. const factories = {
  24640. bar: make$1((spec, backstage) => renderBar(spec, backstage.shared)),
  24641. collection: make$1((spec, backstage, data) => renderCollection(spec, backstage.shared.providers, data)),
  24642. alertbanner: make$1((spec, backstage) => renderAlertBanner(spec, backstage.shared.providers)),
  24643. input: make$1((spec, backstage, data) => renderInput(spec, backstage.shared.providers, data)),
  24644. textarea: make$1((spec, backstage, data) => renderTextarea(spec, backstage.shared.providers, data)),
  24645. label: make$1((spec, backstage, _data, getCompByName) => renderLabel$2(spec, backstage.shared, getCompByName)),
  24646. iframe: makeIframe((spec, backstage, data) => renderIFrame(spec, backstage.shared.providers, data)),
  24647. button: make$1((spec, backstage) => renderDialogButton(spec, backstage.shared.providers)),
  24648. checkbox: make$1((spec, backstage, data) => renderCheckbox(spec, backstage.shared.providers, data)),
  24649. colorinput: make$1((spec, backstage, data) => renderColorInput(spec, backstage.shared, backstage.colorinput, data)),
  24650. colorpicker: make$1((spec, backstage, data) => renderColorPicker(spec, backstage.shared.providers, data)), // Not sure if this needs name.
  24651. dropzone: make$1((spec, backstage, data) => renderDropZone(spec, backstage.shared.providers, data)),
  24652. grid: make$1((spec, backstage) => renderGrid(spec, backstage.shared)),
  24653. listbox: make$1((spec, backstage, data) => renderListBox(spec, backstage, data)),
  24654. selectbox: make$1((spec, backstage, data) => renderSelectBox(spec, backstage.shared.providers, data)),
  24655. sizeinput: make$1((spec, backstage) => renderSizeInput(spec, backstage.shared.providers)),
  24656. slider: make$1((spec, backstage, data) => renderSlider(spec, backstage.shared.providers, data)),
  24657. urlinput: make$1((spec, backstage, data) => renderUrlInput(spec, backstage, backstage.urlinput, data)),
  24658. customeditor: make$1(renderCustomEditor),
  24659. htmlpanel: make$1((spec, backstage) => renderHtmlPanel(spec, backstage.shared.providers)),
  24660. imagepreview: make$1((spec, _, data) => renderImagePreview(spec, data)),
  24661. table: make$1((spec, backstage) => renderTable(spec, backstage.shared.providers)),
  24662. tree: make$1((spec, backstage) => renderTree(spec, backstage)),
  24663. panel: make$1((spec, backstage) => renderPanel(spec, backstage))
  24664. };
  24665. const noFormParts = {
  24666. // This is cast as we only actually want an alloy spec and don't need the actual part here
  24667. field: (_name, spec) => spec,
  24668. record: constant$1([])
  24669. };
  24670. const interpretInForm = (parts, spec, dialogData, oldBackstage, getCompByName) => {
  24671. // Now, we need to update the backstage to use the parts variant.
  24672. const newBackstage = deepMerge(oldBackstage, {
  24673. // Add the interpreter based on the form parts.
  24674. shared: {
  24675. interpreter: (childSpec) => interpretParts(parts, childSpec, dialogData, newBackstage, getCompByName)
  24676. }
  24677. });
  24678. return interpretParts(parts, spec, dialogData, newBackstage, getCompByName);
  24679. };
  24680. const interpretParts = (parts, spec, dialogData, backstage, getCompByName) => get$h(factories, spec.type).fold(() => {
  24681. console.error(`Unknown factory type "${spec.type}", defaulting to container: `, spec);
  24682. return spec;
  24683. }, (factory) => factory(parts, spec, dialogData, backstage, getCompByName));
  24684. const interpretWithoutForm = (spec, dialogData, backstage, getCompByName) => interpretParts(noFormParts, spec, dialogData, backstage, getCompByName);
  24685. const bubbleAlignments$2 = {
  24686. valignCentre: [],
  24687. alignCentre: [],
  24688. alignLeft: [],
  24689. alignRight: [],
  24690. right: [],
  24691. left: [],
  24692. bottom: [],
  24693. top: []
  24694. };
  24695. const getInlineDialogAnchor = (contentAreaElement, lazyAnchorbar, lazyUseEditableAreaAnchor) => {
  24696. const bubbleSize = 12;
  24697. const overrides = {
  24698. maxHeightFunction: expandable$1()
  24699. };
  24700. const editableAreaAnchor = () => ({
  24701. type: 'node',
  24702. root: getContentContainer(getRootNode(contentAreaElement())),
  24703. node: Optional.from(contentAreaElement()),
  24704. bubble: nu$6(bubbleSize, bubbleSize, bubbleAlignments$2),
  24705. layouts: {
  24706. onRtl: () => [northeast$1],
  24707. onLtr: () => [northwest$1]
  24708. },
  24709. overrides
  24710. });
  24711. const standardAnchor = () => ({
  24712. type: 'hotspot',
  24713. hotspot: lazyAnchorbar(),
  24714. bubble: nu$6(-bubbleSize, bubbleSize, bubbleAlignments$2),
  24715. layouts: {
  24716. onRtl: () => [southeast$2, southwest$2, south$2],
  24717. onLtr: () => [southwest$2, southeast$2, south$2]
  24718. },
  24719. overrides
  24720. });
  24721. return () => lazyUseEditableAreaAnchor() ? editableAreaAnchor() : standardAnchor();
  24722. };
  24723. const getInlineBottomDialogAnchor = (inline, contentAreaElement, lazyBottomAnchorBar, lazyUseEditableAreaAnchor) => {
  24724. const bubbleSize = 12;
  24725. const overrides = {
  24726. maxHeightFunction: expandable$1()
  24727. };
  24728. const editableAreaAnchor = () => ({
  24729. type: 'node',
  24730. root: getContentContainer(getRootNode(contentAreaElement())),
  24731. node: Optional.from(contentAreaElement()),
  24732. bubble: nu$6(bubbleSize, bubbleSize, bubbleAlignments$2),
  24733. layouts: {
  24734. onRtl: () => [north$1],
  24735. onLtr: () => [north$1]
  24736. },
  24737. overrides
  24738. });
  24739. const standardAnchor = () => inline ?
  24740. ({
  24741. type: 'node',
  24742. root: getContentContainer(getRootNode(contentAreaElement())),
  24743. node: Optional.from(contentAreaElement()),
  24744. bubble: nu$6(0, -getOuter$1(contentAreaElement()), bubbleAlignments$2),
  24745. layouts: {
  24746. onRtl: () => [north$2],
  24747. onLtr: () => [north$2]
  24748. },
  24749. overrides
  24750. })
  24751. : ({
  24752. type: 'hotspot',
  24753. hotspot: lazyBottomAnchorBar(),
  24754. bubble: nu$6(0, 0, bubbleAlignments$2),
  24755. layouts: {
  24756. onRtl: () => [north$2],
  24757. onLtr: () => [north$2]
  24758. },
  24759. overrides
  24760. });
  24761. return () => lazyUseEditableAreaAnchor() ? editableAreaAnchor() : standardAnchor();
  24762. };
  24763. const getBannerAnchor = (contentAreaElement, lazyAnchorbar, lazyUseEditableAreaAnchor) => {
  24764. const editableAreaAnchor = () => ({
  24765. type: 'node',
  24766. root: getContentContainer(getRootNode(contentAreaElement())),
  24767. node: Optional.from(contentAreaElement()),
  24768. layouts: {
  24769. onRtl: () => [north$1],
  24770. onLtr: () => [north$1]
  24771. }
  24772. });
  24773. const standardAnchor = () => ({
  24774. type: 'hotspot',
  24775. hotspot: lazyAnchorbar(),
  24776. layouts: {
  24777. onRtl: () => [south$2],
  24778. onLtr: () => [south$2]
  24779. }
  24780. });
  24781. return () => lazyUseEditableAreaAnchor() ? editableAreaAnchor() : standardAnchor();
  24782. };
  24783. const getCursorAnchor = (editor, bodyElement) => () => ({
  24784. type: 'selection',
  24785. root: bodyElement(),
  24786. getSelection: () => {
  24787. const rng = editor.selection.getRng();
  24788. // Only return a range if there is a selection of more than one cell.
  24789. const selectedCells = editor.model.table.getSelectedCells();
  24790. if (selectedCells.length > 1) {
  24791. const firstCell = selectedCells[0];
  24792. const lastCell = selectedCells[selectedCells.length - 1];
  24793. const selectionTableCellRange = {
  24794. firstCell: SugarElement.fromDom(firstCell),
  24795. lastCell: SugarElement.fromDom(lastCell)
  24796. };
  24797. return Optional.some(selectionTableCellRange);
  24798. }
  24799. return Optional.some(SimSelection.range(SugarElement.fromDom(rng.startContainer), rng.startOffset, SugarElement.fromDom(rng.endContainer), rng.endOffset));
  24800. }
  24801. });
  24802. const getNodeAnchor$1 = (bodyElement) => (element) => ({
  24803. type: 'node',
  24804. root: bodyElement(),
  24805. node: element
  24806. });
  24807. const getAnchors = (editor, lazyAnchorbar, lazyBottomAnchorBar, isToolbarTop) => {
  24808. const useFixedToolbarContainer = useFixedContainer(editor);
  24809. const bodyElement = () => SugarElement.fromDom(editor.getBody());
  24810. const contentAreaElement = () => SugarElement.fromDom(editor.getContentAreaContainer());
  24811. // If using fixed_toolbar_container or if the toolbar is positioned at the bottom
  24812. // of the editor, some things should anchor to the top of the editable area.
  24813. const lazyUseEditableAreaAnchor = () => useFixedToolbarContainer || !isToolbarTop();
  24814. return {
  24815. inlineDialog: getInlineDialogAnchor(contentAreaElement, lazyAnchorbar, lazyUseEditableAreaAnchor),
  24816. inlineBottomDialog: getInlineBottomDialogAnchor(editor.inline, contentAreaElement, lazyBottomAnchorBar, lazyUseEditableAreaAnchor),
  24817. banner: getBannerAnchor(contentAreaElement, lazyAnchorbar, lazyUseEditableAreaAnchor),
  24818. cursor: getCursorAnchor(editor, bodyElement),
  24819. node: getNodeAnchor$1(bodyElement)
  24820. };
  24821. };
  24822. const colorPicker = (editor) => (callback, value) => {
  24823. const dialog = colorPickerDialog(editor);
  24824. dialog(callback, value);
  24825. };
  24826. const hasCustomColors = (editor) => () => hasCustomColors$1(editor);
  24827. const getColors = (editor) => (id) => getColors$2(editor, id);
  24828. const getColorCols = (editor) => (id) => getColorCols$1(editor, id);
  24829. const ColorInputBackstage = (editor) => ({
  24830. colorPicker: colorPicker(editor),
  24831. hasCustomColors: hasCustomColors(editor),
  24832. getColors: getColors(editor),
  24833. getColorCols: getColorCols(editor)
  24834. });
  24835. const isDraggableModal = (editor) => () => isDraggableModal$1(editor);
  24836. const DialogBackstage = (editor) => ({
  24837. isDraggableModal: isDraggableModal(editor)
  24838. });
  24839. const HeaderBackstage = (editor) => {
  24840. const mode = Cell(isToolbarLocationBottom(editor) ? 'bottom' : 'top');
  24841. return {
  24842. isPositionedAtTop: () => mode.get() === 'top',
  24843. getDockingMode: mode.get,
  24844. setDockingMode: mode.set
  24845. };
  24846. };
  24847. const isNestedFormat = (format) => hasNonNullableKey(format, 'items');
  24848. const isFormatReference = (format) => hasNonNullableKey(format, 'format');
  24849. const defaultStyleFormats = [
  24850. {
  24851. title: 'Headings', items: [
  24852. { title: 'Heading 1', format: 'h1' },
  24853. { title: 'Heading 2', format: 'h2' },
  24854. { title: 'Heading 3', format: 'h3' },
  24855. { title: 'Heading 4', format: 'h4' },
  24856. { title: 'Heading 5', format: 'h5' },
  24857. { title: 'Heading 6', format: 'h6' }
  24858. ]
  24859. },
  24860. {
  24861. title: 'Inline', items: [
  24862. { title: 'Bold', format: 'bold' },
  24863. { title: 'Italic', format: 'italic' },
  24864. { title: 'Underline', format: 'underline' },
  24865. { title: 'Strikethrough', format: 'strikethrough' },
  24866. { title: 'Superscript', format: 'superscript' },
  24867. { title: 'Subscript', format: 'subscript' },
  24868. { title: 'Code', format: 'code' }
  24869. ]
  24870. },
  24871. {
  24872. title: 'Blocks', items: [
  24873. { title: 'Paragraph', format: 'p' },
  24874. { title: 'Blockquote', format: 'blockquote' },
  24875. { title: 'Div', format: 'div' },
  24876. { title: 'Pre', format: 'pre' }
  24877. ]
  24878. },
  24879. {
  24880. title: 'Align', items: [
  24881. { title: 'Left', format: 'alignleft' },
  24882. { title: 'Center', format: 'aligncenter' },
  24883. { title: 'Right', format: 'alignright' },
  24884. { title: 'Justify', format: 'alignjustify' }
  24885. ]
  24886. }
  24887. ];
  24888. // Note: Need to cast format below to expected type, as Obj.has uses "K keyof T", which doesn't work with aliases
  24889. const isNestedFormats = (format) => has$2(format, 'items');
  24890. const isBlockFormat = (format) => has$2(format, 'block');
  24891. const isInlineFormat = (format) => has$2(format, 'inline');
  24892. const isSelectorFormat = (format) => has$2(format, 'selector');
  24893. const mapFormats = (userFormats) => foldl(userFormats, (acc, fmt) => {
  24894. if (isNestedFormats(fmt)) {
  24895. // Map the child formats
  24896. const result = mapFormats(fmt.items);
  24897. return {
  24898. customFormats: acc.customFormats.concat(result.customFormats),
  24899. formats: acc.formats.concat([{ title: fmt.title, items: result.formats }])
  24900. };
  24901. }
  24902. else if (isInlineFormat(fmt) || isBlockFormat(fmt) || isSelectorFormat(fmt)) {
  24903. // Convert the format to a reference and add the original to the custom formats to be registered
  24904. const formatName = isString(fmt.name) ? fmt.name : fmt.title.toLowerCase();
  24905. const formatNameWithPrefix = `custom-${formatName}`;
  24906. return {
  24907. customFormats: acc.customFormats.concat([{ name: formatNameWithPrefix, format: fmt }]),
  24908. formats: acc.formats.concat([{ title: fmt.title, format: formatNameWithPrefix, icon: fmt.icon }])
  24909. };
  24910. }
  24911. else {
  24912. return { ...acc, formats: acc.formats.concat(fmt) };
  24913. }
  24914. }, { customFormats: [], formats: [] });
  24915. const registerCustomFormats = (editor, userFormats) => {
  24916. const result = mapFormats(userFormats);
  24917. const registerFormats = (customFormats) => {
  24918. each$1(customFormats, (fmt) => {
  24919. // Only register the custom format with the editor, if it's not already registered
  24920. if (!editor.formatter.has(fmt.name)) {
  24921. editor.formatter.register(fmt.name, fmt.format);
  24922. }
  24923. });
  24924. };
  24925. // The editor may not yet be initialized, so check for that
  24926. if (editor.formatter) {
  24927. registerFormats(result.customFormats);
  24928. }
  24929. else {
  24930. editor.on('init', () => {
  24931. registerFormats(result.customFormats);
  24932. });
  24933. }
  24934. return result.formats;
  24935. };
  24936. const getStyleFormats = (editor) => getUserStyleFormats(editor).map((userFormats) => {
  24937. // Ensure that any custom formats specified by the user are registered with the editor
  24938. const registeredUserFormats = registerCustomFormats(editor, userFormats);
  24939. // Merge the default formats with the custom formats if required
  24940. return shouldMergeStyleFormats(editor) ? defaultStyleFormats.concat(registeredUserFormats) : registeredUserFormats;
  24941. }).getOr(defaultStyleFormats);
  24942. const isSeparator$1 = (format) => {
  24943. const keys$1 = keys(format);
  24944. return keys$1.length === 1 && contains$2(keys$1, 'title');
  24945. };
  24946. const processBasic = (item, isSelectedFor, getPreviewFor) => ({
  24947. ...item,
  24948. type: 'formatter',
  24949. isSelected: isSelectedFor(item.format),
  24950. getStylePreview: getPreviewFor(item.format)
  24951. });
  24952. // TODO: This is adapted from StyleFormats in the mobile theme. Consolidate.
  24953. const register$b = (editor, formats, isSelectedFor, getPreviewFor) => {
  24954. const enrichSupported = (item) => processBasic(item, isSelectedFor, getPreviewFor);
  24955. // Item that triggers a submenu
  24956. const enrichMenu = (item) => {
  24957. const newItems = doEnrich(item.items);
  24958. return {
  24959. ...item,
  24960. type: 'submenu',
  24961. getStyleItems: constant$1(newItems)
  24962. };
  24963. };
  24964. const enrichCustom = (item) => {
  24965. const formatName = isString(item.name) ? item.name : generate$6(item.title);
  24966. const formatNameWithPrefix = `custom-${formatName}`;
  24967. const newItem = {
  24968. ...item,
  24969. type: 'formatter',
  24970. format: formatNameWithPrefix,
  24971. isSelected: isSelectedFor(formatNameWithPrefix),
  24972. getStylePreview: getPreviewFor(formatNameWithPrefix)
  24973. };
  24974. editor.formatter.register(formatName, newItem);
  24975. return newItem;
  24976. };
  24977. const doEnrich = (items) => map$2(items, (item) => {
  24978. // If it is a submenu, enrich all the subitems.
  24979. if (isNestedFormat(item)) {
  24980. return enrichMenu(item);
  24981. }
  24982. else if (isFormatReference(item)) {
  24983. return enrichSupported(item);
  24984. // NOTE: This branch is added from the original StyleFormats in mobile
  24985. }
  24986. else if (isSeparator$1(item)) {
  24987. return { ...item, type: 'separator' };
  24988. }
  24989. else {
  24990. return enrichCustom(item);
  24991. }
  24992. });
  24993. return doEnrich(formats);
  24994. };
  24995. const init$1 = (editor) => {
  24996. const isSelectedFor = (format) => () => editor.formatter.match(format);
  24997. const getPreviewFor = (format) => () => {
  24998. const fmt = editor.formatter.get(format);
  24999. return fmt !== undefined ? Optional.some({
  25000. tag: fmt.length > 0 ? fmt[0].inline || fmt[0].block || 'div' : 'div',
  25001. styles: editor.dom.parseStyle(editor.formatter.getCssText(format))
  25002. }) : Optional.none();
  25003. };
  25004. const settingsFormats = Cell([]);
  25005. const eventsFormats = Cell([]);
  25006. const replaceSettings = Cell(false);
  25007. editor.on('PreInit', (_e) => {
  25008. const formats = getStyleFormats(editor);
  25009. const enriched = register$b(editor, formats, isSelectedFor, getPreviewFor);
  25010. settingsFormats.set(enriched);
  25011. });
  25012. editor.on('addStyleModifications', (e) => {
  25013. // Is there going to be an order issue here?
  25014. const modifications = register$b(editor, e.items, isSelectedFor, getPreviewFor);
  25015. eventsFormats.set(modifications);
  25016. replaceSettings.set(e.replace);
  25017. });
  25018. const getData = () => {
  25019. const fromSettings = replaceSettings.get() ? [] : settingsFormats.get();
  25020. const fromEvents = eventsFormats.get();
  25021. return fromSettings.concat(fromEvents);
  25022. };
  25023. return {
  25024. getData
  25025. };
  25026. };
  25027. const TooltipsBackstage = (getSink) => {
  25028. const tooltipDelay = 300;
  25029. const intervalDelay = tooltipDelay * 0.2; // Arbitrary value
  25030. let numActiveTooltips = 0;
  25031. const alreadyShowingTooltips = () => numActiveTooltips > 0;
  25032. const getComponents = (spec) => {
  25033. return [
  25034. {
  25035. dom: {
  25036. tag: 'div',
  25037. classes: ['tox-tooltip__body']
  25038. },
  25039. components: [
  25040. text$2(spec.tooltipText)
  25041. ]
  25042. }
  25043. ];
  25044. };
  25045. const getConfig = (spec) => {
  25046. return {
  25047. delayForShow: () => alreadyShowingTooltips() ? intervalDelay : tooltipDelay,
  25048. delayForHide: constant$1(tooltipDelay),
  25049. exclusive: true,
  25050. lazySink: getSink,
  25051. tooltipDom: {
  25052. tag: 'div',
  25053. classes: ['tox-tooltip', 'tox-tooltip--up']
  25054. },
  25055. tooltipComponents: getComponents(spec),
  25056. onShow: (comp, tooltip) => {
  25057. numActiveTooltips++;
  25058. if (spec.onShow) {
  25059. spec.onShow(comp, tooltip);
  25060. }
  25061. },
  25062. onHide: (comp, tooltip) => {
  25063. numActiveTooltips--;
  25064. if (spec.onHide) {
  25065. spec.onHide(comp, tooltip);
  25066. }
  25067. },
  25068. onSetup: spec.onSetup,
  25069. };
  25070. };
  25071. return {
  25072. getConfig,
  25073. getComponents
  25074. };
  25075. };
  25076. const isElement = (node) => isNonNullable(node) && node.nodeType === 1;
  25077. const trim = global$2.trim;
  25078. const hasContentEditableState = (value) => {
  25079. return (node) => {
  25080. if (isElement(node)) {
  25081. if (node.contentEditable === value) {
  25082. return true;
  25083. }
  25084. if (node.getAttribute('data-mce-contenteditable') === value) {
  25085. return true;
  25086. }
  25087. }
  25088. return false;
  25089. };
  25090. };
  25091. const isContentEditableTrue = hasContentEditableState('true');
  25092. const isContentEditableFalse = hasContentEditableState('false');
  25093. const create = (type, title, url, level, attach) => ({
  25094. type,
  25095. title,
  25096. url,
  25097. level,
  25098. attach
  25099. });
  25100. const isChildOfContentEditableTrue = (node) => {
  25101. let tempNode = node;
  25102. while ((tempNode = tempNode.parentNode)) {
  25103. const value = tempNode.contentEditable;
  25104. if (value && value !== 'inherit') {
  25105. return isContentEditableTrue(tempNode);
  25106. }
  25107. }
  25108. return false;
  25109. };
  25110. const select = (selector, root) => {
  25111. return map$2(descendants(SugarElement.fromDom(root), selector), (element) => {
  25112. return element.dom;
  25113. });
  25114. };
  25115. const getElementText = (elm) => {
  25116. return elm.innerText || elm.textContent;
  25117. };
  25118. const getOrGenerateId = (elm) => {
  25119. return elm.id ? elm.id : generate$6('h');
  25120. };
  25121. const isAnchor = (elm) => {
  25122. return elm && elm.nodeName === 'A' && (elm.id || elm.name) !== undefined;
  25123. };
  25124. const isValidAnchor = (elm) => {
  25125. return isAnchor(elm) && isEditable(elm);
  25126. };
  25127. const isHeader = (elm) => {
  25128. return elm && /^(H[1-6])$/.test(elm.nodeName);
  25129. };
  25130. const isEditable = (elm) => {
  25131. return isChildOfContentEditableTrue(elm) && !isContentEditableFalse(elm);
  25132. };
  25133. const isValidHeader = (elm) => {
  25134. return isHeader(elm) && isEditable(elm);
  25135. };
  25136. const getLevel = (elm) => {
  25137. return isHeader(elm) ? parseInt(elm.nodeName.substr(1), 10) : 0;
  25138. };
  25139. const headerTarget = (elm) => {
  25140. var _a;
  25141. const headerId = getOrGenerateId(elm);
  25142. const attach = () => {
  25143. elm.id = headerId;
  25144. };
  25145. return create('header', (_a = getElementText(elm)) !== null && _a !== void 0 ? _a : '', '#' + headerId, getLevel(elm), attach);
  25146. };
  25147. const anchorTarget = (elm) => {
  25148. const anchorId = elm.id || elm.name;
  25149. const anchorText = getElementText(elm);
  25150. return create('anchor', anchorText ? anchorText : '#' + anchorId, '#' + anchorId, 0, noop);
  25151. };
  25152. const getHeaderTargets = (elms) => {
  25153. return map$2(filter$2(elms, isValidHeader), headerTarget);
  25154. };
  25155. const getAnchorTargets = (elms) => {
  25156. return map$2(filter$2(elms, isValidAnchor), anchorTarget);
  25157. };
  25158. const getTargetElements = (elm) => {
  25159. const elms = select('h1,h2,h3,h4,h5,h6,a:not([href])', elm);
  25160. return elms;
  25161. };
  25162. const hasTitle = (target) => {
  25163. return trim(target.title).length > 0;
  25164. };
  25165. const find = (elm) => {
  25166. const elms = getTargetElements(elm);
  25167. return filter$2(getHeaderTargets(elms).concat(getAnchorTargets(elms)), hasTitle);
  25168. };
  25169. const LinkTargets = {
  25170. find
  25171. };
  25172. const STORAGE_KEY = 'tinymce-url-history';
  25173. const HISTORY_LENGTH = 5;
  25174. // validation functions
  25175. const isHttpUrl = (url) => isString(url) && /^https?/.test(url);
  25176. const isArrayOfUrl = (a) => isArray(a) && a.length <= HISTORY_LENGTH && forall(a, isHttpUrl);
  25177. const isRecordOfUrlArray = (r) => isObject(r) && find$4(r, (value) => !isArrayOfUrl(value)).isNone();
  25178. const getAllHistory = () => {
  25179. const unparsedHistory = global$5.getItem(STORAGE_KEY);
  25180. if (unparsedHistory === null) {
  25181. return {};
  25182. }
  25183. // parse history
  25184. let history;
  25185. try {
  25186. history = JSON.parse(unparsedHistory);
  25187. }
  25188. catch (e) {
  25189. if (e instanceof SyntaxError) {
  25190. // eslint-disable-next-line no-console
  25191. console.log('Local storage ' + STORAGE_KEY + ' was not valid JSON', e);
  25192. return {};
  25193. }
  25194. throw e;
  25195. }
  25196. // validate the parsed value
  25197. if (!isRecordOfUrlArray(history)) {
  25198. // eslint-disable-next-line no-console
  25199. console.log('Local storage ' + STORAGE_KEY + ' was not valid format', history);
  25200. return {};
  25201. }
  25202. return history;
  25203. };
  25204. const setAllHistory = (history) => {
  25205. if (!isRecordOfUrlArray(history)) {
  25206. throw new Error('Bad format for history:\n' + JSON.stringify(history));
  25207. }
  25208. global$5.setItem(STORAGE_KEY, JSON.stringify(history));
  25209. };
  25210. const getHistory = (fileType) => {
  25211. const history = getAllHistory();
  25212. return get$h(history, fileType).getOr([]);
  25213. };
  25214. const addToHistory = (url, fileType) => {
  25215. if (!isHttpUrl(url)) {
  25216. return;
  25217. }
  25218. const history = getAllHistory();
  25219. const items = get$h(history, fileType).getOr([]);
  25220. const itemsWithoutUrl = filter$2(items, (item) => item !== url);
  25221. history[fileType] = [url].concat(itemsWithoutUrl).slice(0, HISTORY_LENGTH);
  25222. setAllHistory(history);
  25223. };
  25224. const isTruthy = (value) => !!value;
  25225. const makeMap = (value) => map$1(global$2.makeMap(value, /[, ]/), isTruthy);
  25226. const getPicker = (editor) => Optional.from(getFilePickerCallback(editor));
  25227. const getPickerTypes = (editor) => {
  25228. const optFileTypes = Optional.from(getFilePickerTypes(editor)).filter(isTruthy).map(makeMap);
  25229. return getPicker(editor).fold(never, (_picker) => optFileTypes.fold(always, (types) => keys(types).length > 0 ? types : false));
  25230. };
  25231. const getPickerSetting = (editor, filetype) => {
  25232. const pickerTypes = getPickerTypes(editor);
  25233. if (isBoolean(pickerTypes)) {
  25234. return pickerTypes ? getPicker(editor) : Optional.none();
  25235. }
  25236. else {
  25237. return pickerTypes[filetype] ? getPicker(editor) : Optional.none();
  25238. }
  25239. };
  25240. const getUrlPicker = (editor, filetype) => getPickerSetting(editor, filetype).map((picker) => (entry) => Future.nu((completer) => {
  25241. const handler = (value, meta) => {
  25242. if (!isString(value)) {
  25243. throw new Error('Expected value to be string');
  25244. }
  25245. if (meta !== undefined && !isObject(meta)) {
  25246. throw new Error('Expected meta to be a object');
  25247. }
  25248. const r = { value, meta };
  25249. completer(r);
  25250. };
  25251. const meta = {
  25252. filetype,
  25253. fieldname: entry.fieldname,
  25254. ...Optional.from(entry.meta).getOr({})
  25255. };
  25256. // file_picker_callback(callback, currentValue, metaData)
  25257. picker.call(editor, handler, entry.value, meta);
  25258. }));
  25259. const getTextSetting = (value) => Optional.from(value).filter(isString).getOrUndefined();
  25260. const getLinkInformation = (editor) => {
  25261. if (!useTypeaheadUrls(editor)) {
  25262. return Optional.none();
  25263. }
  25264. return Optional.some({
  25265. targets: LinkTargets.find(editor.getBody()),
  25266. anchorTop: getTextSetting(getAnchorTop(editor)),
  25267. anchorBottom: getTextSetting(getAnchorBottom(editor))
  25268. });
  25269. };
  25270. const getValidationHandler = (editor) => Optional.from(getFilePickerValidatorHandler(editor));
  25271. const UrlInputBackstage = (editor) => ({
  25272. getHistory,
  25273. addToHistory,
  25274. getLinkInformation: () => getLinkInformation(editor),
  25275. getValidationHandler: () => getValidationHandler(editor),
  25276. getUrlPicker: (filetype) => getUrlPicker(editor, filetype)
  25277. });
  25278. const init = (lazySinks, editor, lazyAnchorbar, lazyBottomAnchorBar) => {
  25279. const contextMenuState = Cell(false);
  25280. const toolbar = HeaderBackstage(editor);
  25281. const providers = {
  25282. icons: () => editor.ui.registry.getAll().icons,
  25283. menuItems: () => editor.ui.registry.getAll().menuItems,
  25284. translate: global$6.translate,
  25285. isDisabled: () => !editor.ui.isEnabled(),
  25286. getOption: editor.options.get,
  25287. tooltips: TooltipsBackstage(lazySinks.dialog),
  25288. checkUiComponentContext: (specContext) => {
  25289. if (isDisabled(editor)) {
  25290. return {
  25291. contextType: 'disabled',
  25292. shouldDisable: true
  25293. };
  25294. }
  25295. const [key, value = ''] = specContext.split(':');
  25296. const contexts = editor.ui.registry.getAll().contexts;
  25297. const enabledInContext = get$h(contexts, key)
  25298. .fold(
  25299. // Fallback to 'mode:design' if key is not found
  25300. () => get$h(contexts, 'mode').map((pred) => pred('design')).getOr(false), (pred) => value.charAt(0) === '!' ? !pred(value.slice(1)) : pred(value));
  25301. return {
  25302. contextType: key,
  25303. shouldDisable: !enabledInContext
  25304. };
  25305. }
  25306. };
  25307. const urlinput = UrlInputBackstage(editor);
  25308. const styles = init$1(editor);
  25309. const colorinput = ColorInputBackstage(editor);
  25310. const dialogSettings = DialogBackstage(editor);
  25311. const isContextMenuOpen = () => contextMenuState.get();
  25312. const setContextMenuState = (state) => contextMenuState.set(state);
  25313. const commonBackstage = {
  25314. shared: {
  25315. providers,
  25316. anchors: getAnchors(editor, lazyAnchorbar, lazyBottomAnchorBar, toolbar.isPositionedAtTop),
  25317. header: toolbar,
  25318. },
  25319. urlinput,
  25320. styles,
  25321. colorinput,
  25322. dialog: dialogSettings,
  25323. isContextMenuOpen,
  25324. setContextMenuState
  25325. };
  25326. const getCompByName = (_name) => Optional.none();
  25327. const popupBackstage = {
  25328. ...commonBackstage,
  25329. shared: {
  25330. ...commonBackstage.shared,
  25331. interpreter: (s) => interpretWithoutForm(s, {}, popupBackstage, getCompByName),
  25332. getSink: lazySinks.popup
  25333. }
  25334. };
  25335. const dialogBackstage = {
  25336. ...commonBackstage,
  25337. shared: {
  25338. ...commonBackstage.shared,
  25339. interpreter: (s) => interpretWithoutForm(s, {}, dialogBackstage, getCompByName),
  25340. getSink: lazySinks.dialog
  25341. }
  25342. };
  25343. return {
  25344. popup: popupBackstage,
  25345. dialog: dialogBackstage
  25346. };
  25347. };
  25348. const migrationFrom7x = 'https://www.tiny.cloud/docs/tinymce/latest/migration-from-7x/';
  25349. const deprecatedFeatures = {
  25350. skipFocus: `ToggleToolbarDrawer skipFocus is deprecated see migration guide: ${migrationFrom7x}`,
  25351. };
  25352. const logFeatureDeprecationWarning = (feature) => {
  25353. // eslint-disable-next-line no-console
  25354. console.warn(deprecatedFeatures[feature], new Error().stack);
  25355. };
  25356. const setup$b = (editor, mothership, uiMotherships) => {
  25357. const broadcastEvent = (name, evt) => {
  25358. each$1([mothership, ...uiMotherships], (m) => {
  25359. m.broadcastEvent(name, evt);
  25360. });
  25361. };
  25362. const broadcastOn = (channel, message) => {
  25363. each$1([mothership, ...uiMotherships], (m) => {
  25364. m.broadcastOn([channel], message);
  25365. });
  25366. };
  25367. const fireDismissPopups = (evt) => broadcastOn(dismissPopups(), { target: evt.target });
  25368. const fireCloseTooltips = (event) => {
  25369. broadcastOn(closeTooltips(), {
  25370. closedTooltip: () => {
  25371. event.preventDefault();
  25372. }
  25373. });
  25374. };
  25375. // Document touch events
  25376. const doc = getDocument();
  25377. const onTouchstart = bind$1(doc, 'touchstart', fireDismissPopups);
  25378. const onTouchmove = bind$1(doc, 'touchmove', (evt) => broadcastEvent(documentTouchmove(), evt));
  25379. const onTouchend = bind$1(doc, 'touchend', (evt) => broadcastEvent(documentTouchend(), evt));
  25380. // Document mouse events
  25381. const onMousedown = bind$1(doc, 'mousedown', fireDismissPopups);
  25382. const onMouseup = bind$1(doc, 'mouseup', (evt) => {
  25383. if (evt.raw.button === 0) {
  25384. broadcastOn(mouseReleased(), { target: evt.target });
  25385. }
  25386. });
  25387. // Editor content events
  25388. const onContentClick = (raw) => broadcastOn(dismissPopups(), { target: SugarElement.fromDom(raw.target) });
  25389. const onContentMouseup = (raw) => {
  25390. if (raw.button === 0) {
  25391. broadcastOn(mouseReleased(), { target: SugarElement.fromDom(raw.target) });
  25392. }
  25393. };
  25394. const onContentMousedown = () => {
  25395. each$1(editor.editorManager.get(), (loopEditor) => {
  25396. if (editor !== loopEditor) {
  25397. loopEditor.dispatch('DismissPopups', { relatedTarget: editor });
  25398. }
  25399. });
  25400. };
  25401. // Window events
  25402. const onWindowScroll = (evt) => broadcastEvent(windowScroll(), fromRawEvent(evt));
  25403. const onWindowResize = (evt) => {
  25404. broadcastOn(repositionPopups(), {});
  25405. broadcastEvent(windowResize(), fromRawEvent(evt));
  25406. };
  25407. // TINY-9425: At the moment, we are only supporting situations where the scrolling container
  25408. // is *inside* the shadow root - which is why we bind to the root node, instead of just the outer
  25409. // document. However, if we needed to support scrolling containers that *contained* the shadow root,
  25410. // we would need to listen to the outer document (or at the least, the root node of the scrolling div in
  25411. // the case of muliple layers of shadow roots).
  25412. const dos = getRootNode(SugarElement.fromDom(editor.getElement()));
  25413. const onElementScroll = capture(dos, 'scroll', (evt) => {
  25414. requestAnimationFrame(() => {
  25415. const c = editor.getContainer();
  25416. // Because this can fire before the editor is rendered, we need to stop that from happening.
  25417. // Some tests can create this situation, and then we get a Node name null or defined error.
  25418. if (c !== undefined && c !== null) {
  25419. const optScrollingContext = detectWhenSplitUiMode(editor, mothership.element);
  25420. const scrollers = optScrollingContext.map((sc) => [sc.element, ...sc.others]).getOr([]);
  25421. if (exists(scrollers, (s) => eq(s, evt.target))) {
  25422. editor.dispatch('ElementScroll', { target: evt.target.dom });
  25423. broadcastEvent(externalElementScroll(), evt);
  25424. }
  25425. }
  25426. });
  25427. });
  25428. const onEditorResize = () => broadcastOn(repositionPopups(), {});
  25429. const onEditorProgress = (evt) => {
  25430. if (evt.state) {
  25431. broadcastOn(dismissPopups(), { target: SugarElement.fromDom(editor.getContainer()) });
  25432. }
  25433. };
  25434. const onDismissPopups = (event) => {
  25435. broadcastOn(dismissPopups(), { target: SugarElement.fromDom(event.relatedTarget.getContainer()) });
  25436. };
  25437. const onFocusIn = (event) => editor.dispatch('focusin', event);
  25438. const onFocusOut = (event) => editor.dispatch('focusout', event);
  25439. // Don't start listening to events until the UI has rendered
  25440. editor.on('PostRender', () => {
  25441. editor.on('click', onContentClick);
  25442. editor.on('tap', onContentClick);
  25443. editor.on('mouseup', onContentMouseup);
  25444. editor.on('mousedown', onContentMousedown);
  25445. editor.on('ScrollWindow', onWindowScroll);
  25446. editor.on('ResizeWindow', onWindowResize);
  25447. editor.on('ResizeEditor', onEditorResize);
  25448. editor.on('AfterProgressState', onEditorProgress);
  25449. editor.on('DismissPopups', onDismissPopups);
  25450. editor.on('CloseActiveTooltips', fireCloseTooltips);
  25451. each$1([mothership, ...uiMotherships], (gui) => {
  25452. gui.element.dom.addEventListener('focusin', onFocusIn);
  25453. gui.element.dom.addEventListener('focusout', onFocusOut);
  25454. });
  25455. });
  25456. editor.on('remove', () => {
  25457. // We probably don't need these unbinds, but it helps to have them if we move this code out.
  25458. editor.off('click', onContentClick);
  25459. editor.off('tap', onContentClick);
  25460. editor.off('mouseup', onContentMouseup);
  25461. editor.off('mousedown', onContentMousedown);
  25462. editor.off('ScrollWindow', onWindowScroll);
  25463. editor.off('ResizeWindow', onWindowResize);
  25464. editor.off('ResizeEditor', onEditorResize);
  25465. editor.off('AfterProgressState', onEditorProgress);
  25466. editor.off('DismissPopups', onDismissPopups);
  25467. editor.off('CloseActiveTooltips', fireCloseTooltips);
  25468. each$1([mothership, ...uiMotherships], (gui) => {
  25469. gui.element.dom.removeEventListener('focusin', onFocusIn);
  25470. gui.element.dom.removeEventListener('focusout', onFocusOut);
  25471. });
  25472. onMousedown.unbind();
  25473. onTouchstart.unbind();
  25474. onTouchmove.unbind();
  25475. onTouchend.unbind();
  25476. onMouseup.unbind();
  25477. onElementScroll.unbind();
  25478. });
  25479. editor.on('detach', () => {
  25480. each$1([mothership, ...uiMotherships], detachSystem);
  25481. each$1([mothership, ...uiMotherships], (m) => m.destroy());
  25482. });
  25483. };
  25484. const setup$a = noop;
  25485. const isDocked$1 = never;
  25486. const getBehaviours$1 = constant$1([]);
  25487. var StaticHeader = /*#__PURE__*/Object.freeze({
  25488. __proto__: null,
  25489. setup: setup$a,
  25490. isDocked: isDocked$1,
  25491. getBehaviours: getBehaviours$1
  25492. });
  25493. const toolbarHeightChange = constant$1(generate$6('toolbar-height-change'));
  25494. const visibility = {
  25495. fadeInClass: 'tox-editor-dock-fadein',
  25496. fadeOutClass: 'tox-editor-dock-fadeout',
  25497. transitionClass: 'tox-editor-dock-transition'
  25498. };
  25499. const editorStickyOnClass = 'tox-tinymce--toolbar-sticky-on';
  25500. const editorStickyOffClass = 'tox-tinymce--toolbar-sticky-off';
  25501. const scrollFromBehindHeader = (e, containerHeader) => {
  25502. const doc = owner$4(containerHeader);
  25503. const win = defaultView(containerHeader);
  25504. const viewHeight = win.dom.innerHeight;
  25505. const scrollPos = get$b(doc);
  25506. const markerElement = SugarElement.fromDom(e.elm);
  25507. const markerPos = absolute$2(markerElement);
  25508. const markerHeight = get$d(markerElement);
  25509. const markerTop = markerPos.y;
  25510. const markerBottom = markerTop + markerHeight;
  25511. const editorHeaderPos = absolute$3(containerHeader);
  25512. const editorHeaderHeight = get$d(containerHeader);
  25513. const editorHeaderTop = editorHeaderPos.top;
  25514. const editorHeaderBottom = editorHeaderTop + editorHeaderHeight;
  25515. // Check to see if the header is docked to the top/bottom of the page (eg is floating)
  25516. const editorHeaderDockedAtTop = Math.abs(editorHeaderTop - scrollPos.top) < 2;
  25517. const editorHeaderDockedAtBottom = Math.abs(editorHeaderBottom - (scrollPos.top + viewHeight)) < 2;
  25518. // If the element is behind the header at the top of the page, then
  25519. // scroll the element down by the header height
  25520. if (editorHeaderDockedAtTop && markerTop < editorHeaderBottom) {
  25521. to(scrollPos.left, markerTop - editorHeaderHeight, doc);
  25522. // If the element is behind the header at the bottom of the page, then
  25523. // scroll the element up by the header height
  25524. }
  25525. else if (editorHeaderDockedAtBottom && markerBottom > editorHeaderTop) {
  25526. const y = (markerTop - viewHeight) + markerHeight + editorHeaderHeight;
  25527. to(scrollPos.left, y, doc);
  25528. }
  25529. };
  25530. const isDockedMode = (header, mode) => contains$2(Docking.getModes(header), mode);
  25531. const updateIframeContentFlow = (header) => {
  25532. const getOccupiedHeight = (elm) => getOuter$1(elm) +
  25533. (parseInt(get$e(elm, 'margin-top'), 10) || 0) +
  25534. (parseInt(get$e(elm, 'margin-bottom'), 10) || 0);
  25535. const elm = header.element;
  25536. parentElement(elm).each((parentElem) => {
  25537. const padding = 'padding-' + Docking.getModes(header)[0];
  25538. if (Docking.isDocked(header)) {
  25539. const parentWidth = get$c(parentElem);
  25540. set$7(elm, 'width', parentWidth + 'px');
  25541. set$7(parentElem, padding, getOccupiedHeight(elm) + 'px');
  25542. }
  25543. else {
  25544. remove$6(elm, 'width');
  25545. remove$6(parentElem, padding);
  25546. }
  25547. });
  25548. };
  25549. const updateSinkVisibility = (sinkElem, visible) => {
  25550. if (visible) {
  25551. remove$3(sinkElem, visibility.fadeOutClass);
  25552. add$1(sinkElem, [visibility.transitionClass, visibility.fadeInClass]);
  25553. }
  25554. else {
  25555. remove$3(sinkElem, visibility.fadeInClass);
  25556. add$1(sinkElem, [visibility.fadeOutClass, visibility.transitionClass]);
  25557. }
  25558. };
  25559. const updateEditorClasses = (editor, docked) => {
  25560. const editorContainer = SugarElement.fromDom(editor.getContainer());
  25561. if (docked) {
  25562. add$2(editorContainer, editorStickyOnClass);
  25563. remove$3(editorContainer, editorStickyOffClass);
  25564. }
  25565. else {
  25566. add$2(editorContainer, editorStickyOffClass);
  25567. remove$3(editorContainer, editorStickyOnClass);
  25568. }
  25569. };
  25570. const restoreFocus = (headerElem, focusedElem) => {
  25571. // When the header is hidden, then the element that was focused will be lost
  25572. // so we need to restore it if nothing else has already been focused (eg anything other than the body)
  25573. const ownerDoc = owner$4(focusedElem);
  25574. active$1(ownerDoc).filter((activeElm) =>
  25575. // Don't try to refocus the same element
  25576. !eq(focusedElem, activeElm)).filter((activeElm) =>
  25577. // Only attempt to refocus if the current focus is the body or is in the header element
  25578. eq(activeElm, SugarElement.fromDom(ownerDoc.dom.body)) || contains(headerElem, activeElm)).each(() => focus$4(focusedElem));
  25579. };
  25580. const findFocusedElem = (rootElm, lazySink) =>
  25581. // Check to see if an element is focused inside the header or inside the sink
  25582. // and if so store the element so we can restore it later
  25583. search(rootElm).orThunk(() => lazySink().toOptional().bind((sink) => search(sink.element)));
  25584. const setup$9 = (editor, sharedBackstage, lazyHeader) => {
  25585. if (!editor.inline) {
  25586. // If using bottom toolbar then when the editor resizes we need to reset docking
  25587. // otherwise it won't know the original toolbar position has moved
  25588. if (!sharedBackstage.header.isPositionedAtTop()) {
  25589. editor.on('ResizeEditor', () => {
  25590. lazyHeader().each(Docking.reset);
  25591. });
  25592. }
  25593. // No need to update the content flow in inline mode as the header always floats
  25594. editor.on('ResizeWindow ResizeEditor', () => {
  25595. lazyHeader().each(updateIframeContentFlow);
  25596. });
  25597. // Need to reset the docking position on skin loaded as the original position will have
  25598. // changed due the skins styles being applied.
  25599. // Note: Inline handles it's own skin loading, as it needs to do other initial positioning
  25600. editor.on('SkinLoaded', () => {
  25601. lazyHeader().each((comp) => {
  25602. Docking.isDocked(comp) ? Docking.reset(comp) : Docking.refresh(comp);
  25603. });
  25604. });
  25605. // Need to reset when we go fullscreen so that if the header is docked,
  25606. // then it'll undock and viceversa
  25607. editor.on('FullscreenStateChanged', () => {
  25608. lazyHeader().each(Docking.reset);
  25609. });
  25610. }
  25611. // If inline or sticky toolbars is enabled, then when scrolling into view we may still be
  25612. // behind the editor header so we need to adjust the scroll position to account for that
  25613. editor.on('AfterScrollIntoView', (e) => {
  25614. lazyHeader().each((header) => {
  25615. // We need to make sure the header docking has refreshed, otherwise if a large scroll occurred
  25616. // the header may have gone off page and need to be docked before doing calculations
  25617. Docking.refresh(header);
  25618. // If the header element is still visible, then adjust the scroll position if required
  25619. const headerElem = header.element;
  25620. if (isVisible(headerElem)) {
  25621. scrollFromBehindHeader(e, headerElem);
  25622. }
  25623. });
  25624. });
  25625. // Update the editor classes once initial rendering has completed
  25626. editor.on('PostRender', () => {
  25627. updateEditorClasses(editor, false);
  25628. });
  25629. };
  25630. const isDocked = (lazyHeader) => lazyHeader().map(Docking.isDocked).getOr(false);
  25631. const getIframeBehaviours = () => [
  25632. Receiving.config({
  25633. channels: {
  25634. [toolbarHeightChange()]: {
  25635. onReceive: updateIframeContentFlow
  25636. }
  25637. }
  25638. })
  25639. ];
  25640. const getBehaviours = (editor, sharedBackstage) => {
  25641. const focusedElm = value$2();
  25642. const lazySink = sharedBackstage.getSink;
  25643. const runOnSinkElement = (f) => {
  25644. lazySink().each((sink) => f(sink.element));
  25645. };
  25646. const onDockingSwitch = (comp) => {
  25647. if (!editor.inline) {
  25648. updateIframeContentFlow(comp);
  25649. }
  25650. updateEditorClasses(editor, Docking.isDocked(comp));
  25651. // TINY-9223: This will only reposition the popups in the same mothership as the StickyHeader
  25652. // and its sink. If we need to reposition the popups in all motherships (in the two sink
  25653. // model) then we'll need a reference to all motherships here.
  25654. comp.getSystem().broadcastOn([repositionPopups()], {});
  25655. lazySink().each((sink) => sink.getSystem().broadcastOn([repositionPopups()], {}));
  25656. };
  25657. const additionalBehaviours = editor.inline ? [] : getIframeBehaviours();
  25658. return [
  25659. Focusing.config({}),
  25660. Docking.config({
  25661. contextual: {
  25662. lazyContext: (comp) => {
  25663. const headerHeight = getOuter$1(comp.element);
  25664. const container = editor.inline ? editor.getContentAreaContainer() : editor.getContainer();
  25665. return Optional.from(container).map((c) => {
  25666. const box = box$1(SugarElement.fromDom(c));
  25667. const optScrollingContext = detectWhenSplitUiMode(editor, comp.element);
  25668. return optScrollingContext.fold(() => {
  25669. // Force the header to hide before it overflows outside the container
  25670. const boxHeight = box.height - headerHeight;
  25671. const topBound = box.y + (isDockedMode(comp, 'top') ? 0 : headerHeight);
  25672. return bounds(box.x, topBound, box.width, boxHeight);
  25673. }, (scrollEnv) => {
  25674. const constrainedBounds = constrain(box, getBoundsFrom(scrollEnv));
  25675. // When the toolbar location is set to the top, y is the top of the container and height is the available container height minus the header height, as the toolbar will be placed at the top of the container
  25676. // This is so that as you scroll the scrollable container/the page, it will dock at the top and when there's insufficient height/space (that's the reason of deducting the headerHeight for the available height), it will be hidden.
  25677. // When the toolbar location is set to the bottom, y is the top of the container plus the header height, as the toolbar will be placed at the bottom of the container, beyond the container, so that's why we need to add the headerHeight
  25678. // When there's insufficient height/space, it will be hidden, and when you scroll past the editor, it will be hidden
  25679. const constrainedBoundsY = isDockedMode(comp, 'top')
  25680. ? constrainedBounds.y
  25681. : constrainedBounds.y + headerHeight;
  25682. return bounds(constrainedBounds.x,
  25683. // ASSUMPTION: The constrainedBounds removes the need for us to set this to 0px
  25684. // for docked mode. Also, docking in a scrolling environment will often be
  25685. // at the scroller top, not the window top
  25686. constrainedBoundsY, constrainedBounds.width, constrainedBounds.height - headerHeight);
  25687. });
  25688. });
  25689. },
  25690. onShow: () => {
  25691. runOnSinkElement((elem) => updateSinkVisibility(elem, true));
  25692. },
  25693. onShown: (comp) => {
  25694. runOnSinkElement((elem) => remove$2(elem, [visibility.transitionClass, visibility.fadeInClass]));
  25695. // Restore focus and reset the stored focused element
  25696. focusedElm.get().each((elem) => {
  25697. restoreFocus(comp.element, elem);
  25698. focusedElm.clear();
  25699. });
  25700. },
  25701. onHide: (comp) => {
  25702. findFocusedElem(comp.element, lazySink).fold(focusedElm.clear, focusedElm.set);
  25703. runOnSinkElement((elem) => updateSinkVisibility(elem, false));
  25704. },
  25705. onHidden: () => {
  25706. runOnSinkElement((elem) => remove$2(elem, [visibility.transitionClass]));
  25707. },
  25708. ...visibility
  25709. },
  25710. lazyViewport: (comp) => {
  25711. const optScrollingContext = detectWhenSplitUiMode(editor, comp.element);
  25712. return optScrollingContext.fold(() => {
  25713. const boundsWithoutOffset = win();
  25714. const offset = getStickyToolbarOffset(editor);
  25715. const top = boundsWithoutOffset.y + (isDockedMode(comp, 'top') && !isFullscreen(editor) ? offset : 0);
  25716. const height = boundsWithoutOffset.height - (isDockedMode(comp, 'bottom') ? offset : 0);
  25717. // No scrolling context, so just window
  25718. return {
  25719. bounds: bounds(boundsWithoutOffset.x, top, boundsWithoutOffset.width, height),
  25720. optScrollEnv: Optional.none()
  25721. };
  25722. }, (sc) => {
  25723. // TINY-9411: Implement sticky toolbar offsets in scrollable containers
  25724. const combinedBounds = getBoundsFrom(sc);
  25725. return {
  25726. bounds: combinedBounds,
  25727. optScrollEnv: Optional.some({
  25728. currentScrollTop: sc.element.dom.scrollTop,
  25729. scrollElmTop: absolute$3(sc.element).top
  25730. })
  25731. };
  25732. });
  25733. },
  25734. modes: [sharedBackstage.header.getDockingMode()],
  25735. onDocked: onDockingSwitch,
  25736. onUndocked: onDockingSwitch
  25737. }),
  25738. ...additionalBehaviours
  25739. ];
  25740. };
  25741. var StickyHeader = /*#__PURE__*/Object.freeze({
  25742. __proto__: null,
  25743. setup: setup$9,
  25744. isDocked: isDocked,
  25745. getBehaviours: getBehaviours
  25746. });
  25747. const renderHeader = (spec) => {
  25748. const editor = spec.editor;
  25749. const getBehaviours$2 = spec.sticky ? getBehaviours : getBehaviours$1;
  25750. return {
  25751. uid: spec.uid,
  25752. dom: spec.dom,
  25753. components: spec.components,
  25754. behaviours: derive$1(getBehaviours$2(editor, spec.sharedBackstage))
  25755. };
  25756. };
  25757. const factory$3 = (detail, spec) => {
  25758. const setMenus = (comp, menus) => {
  25759. const newMenus = map$2(menus, (m) => {
  25760. const buttonSpec = {
  25761. type: 'menubutton',
  25762. text: m.text,
  25763. fetch: (callback) => {
  25764. callback(m.getItems());
  25765. },
  25766. context: 'any'
  25767. };
  25768. // Convert to an internal bridge spec
  25769. const internal = createMenuButton(buttonSpec).mapError((errInfo) => formatError(errInfo)).getOrDie();
  25770. return renderMenuButton(internal, "tox-mbtn" /* MenuButtonClasses.Button */, spec.backstage,
  25771. // https://www.w3.org/TR/wai-aria-practices/examples/menubar/menubar-2/menubar-2.html
  25772. Optional.some('menuitem'));
  25773. });
  25774. Replacing.set(comp, newMenus);
  25775. };
  25776. const apis = {
  25777. focus: Keying.focusIn,
  25778. setMenus
  25779. };
  25780. return {
  25781. uid: detail.uid,
  25782. dom: detail.dom,
  25783. components: [],
  25784. behaviours: derive$1([
  25785. Replacing.config({}),
  25786. config('menubar-events', [
  25787. runOnAttached((component) => {
  25788. detail.onSetup(component);
  25789. }),
  25790. run$1(mouseover(), (comp, se) => {
  25791. // TODO: Use constants
  25792. descendant(comp.element, '.' + "tox-mbtn--active" /* MenuButtonClasses.Active */).each((activeButton) => {
  25793. closest$3(se.event.target, '.' + "tox-mbtn" /* MenuButtonClasses.Button */).each((hoveredButton) => {
  25794. if (!eq(activeButton, hoveredButton)) {
  25795. // Now, find the components, and expand the hovered one, and close the active one
  25796. comp.getSystem().getByDom(activeButton).each((activeComp) => {
  25797. comp.getSystem().getByDom(hoveredButton).each((hoveredComp) => {
  25798. Dropdown.expand(hoveredComp);
  25799. Dropdown.close(activeComp);
  25800. Focusing.focus(hoveredComp);
  25801. });
  25802. });
  25803. }
  25804. });
  25805. });
  25806. }),
  25807. run$1(focusShifted(), (comp, se) => {
  25808. se.event.prevFocus.bind((prev) => comp.getSystem().getByDom(prev).toOptional()).each((prev) => {
  25809. se.event.newFocus.bind((nu) => comp.getSystem().getByDom(nu).toOptional()).each((nu) => {
  25810. if (Dropdown.isOpen(prev)) {
  25811. Dropdown.expand(nu);
  25812. Dropdown.close(prev);
  25813. }
  25814. });
  25815. });
  25816. })
  25817. ]),
  25818. Keying.config({
  25819. mode: 'flow',
  25820. selector: '.' + "tox-mbtn" /* MenuButtonClasses.Button */,
  25821. onEscape: (comp) => {
  25822. detail.onEscape(comp);
  25823. return Optional.some(true);
  25824. }
  25825. }),
  25826. Tabstopping.config({})
  25827. ]),
  25828. apis,
  25829. domModification: {
  25830. attributes: {
  25831. role: 'menubar'
  25832. }
  25833. }
  25834. };
  25835. };
  25836. var SilverMenubar = single({
  25837. factory: factory$3,
  25838. name: 'silver.Menubar',
  25839. configFields: [
  25840. required$1('dom'),
  25841. required$1('uid'),
  25842. required$1('onEscape'),
  25843. required$1('backstage'),
  25844. defaulted('onSetup', noop)
  25845. ],
  25846. apis: {
  25847. focus: (apis, comp) => {
  25848. apis.focus(comp);
  25849. },
  25850. setMenus: (apis, comp, menus) => {
  25851. apis.setMenus(comp, menus);
  25852. }
  25853. }
  25854. });
  25855. const promotionMessage = '💝 Get all features';
  25856. const promotionLink = 'https://www.tiny.cloud/tinymce-upgrade-to-cloud/?utm_campaign=self_hosted_upgrade_promo&utm_source=tiny&utm_medium=referral';
  25857. const renderPromotion = (spec) => {
  25858. const components = spec.promotionLink ? [
  25859. {
  25860. dom: {
  25861. tag: 'a',
  25862. attributes: {
  25863. 'href': promotionLink,
  25864. 'rel': 'noopener',
  25865. 'target': '_blank',
  25866. 'aria-hidden': 'true'
  25867. },
  25868. classes: ['tox-promotion-link'],
  25869. innerHtml: promotionMessage
  25870. }
  25871. }
  25872. ] : [];
  25873. return {
  25874. uid: spec.uid,
  25875. dom: spec.dom,
  25876. components
  25877. };
  25878. };
  25879. const setup$8 = (editor) => {
  25880. const { sidebars } = editor.ui.registry.getAll();
  25881. // Setup each registered sidebar
  25882. each$1(keys(sidebars), (name) => {
  25883. const spec = sidebars[name];
  25884. const isActive = () => is$1(Optional.from(editor.queryCommandValue('ToggleSidebar')), name);
  25885. editor.ui.registry.addToggleButton(name, {
  25886. icon: spec.icon,
  25887. tooltip: spec.tooltip,
  25888. onAction: (buttonApi) => {
  25889. editor.execCommand('ToggleSidebar', false, name);
  25890. buttonApi.setActive(isActive());
  25891. },
  25892. onSetup: (buttonApi) => {
  25893. buttonApi.setActive(isActive());
  25894. const handleToggle = () => buttonApi.setActive(isActive());
  25895. editor.on('ToggleSidebar', handleToggle);
  25896. return () => {
  25897. editor.off('ToggleSidebar', handleToggle);
  25898. };
  25899. },
  25900. context: 'any'
  25901. });
  25902. });
  25903. };
  25904. const getApi = (comp) => ({
  25905. element: () => comp.element.dom
  25906. });
  25907. const makePanels = (parts, panelConfigs) => {
  25908. const specs = map$2(keys(panelConfigs), (name) => {
  25909. const spec = panelConfigs[name];
  25910. const bridged = getOrDie(createSidebar(spec));
  25911. return {
  25912. name,
  25913. getApi,
  25914. onSetup: bridged.onSetup,
  25915. onShow: bridged.onShow,
  25916. onHide: bridged.onHide
  25917. };
  25918. });
  25919. return map$2(specs, (spec) => {
  25920. const editorOffCell = Cell(noop);
  25921. return parts.slot(spec.name, {
  25922. dom: {
  25923. tag: 'div',
  25924. classes: ['tox-sidebar__pane']
  25925. },
  25926. behaviours: SimpleBehaviours.unnamedEvents([
  25927. onControlAttached(spec, editorOffCell),
  25928. onControlDetached(spec, editorOffCell),
  25929. run$1(slotVisibility(), (sidepanel, se) => {
  25930. const data = se.event;
  25931. const optSidePanelSpec = find$5(specs, (config) => config.name === data.name);
  25932. optSidePanelSpec.each((sidePanelSpec) => {
  25933. const handler = data.visible ? sidePanelSpec.onShow : sidePanelSpec.onHide;
  25934. handler(sidePanelSpec.getApi(sidepanel));
  25935. });
  25936. })
  25937. ])
  25938. });
  25939. });
  25940. };
  25941. const makeSidebar = (panelConfigs) => SlotContainer.sketch((parts) => ({
  25942. dom: {
  25943. tag: 'div',
  25944. classes: ['tox-sidebar__pane-container']
  25945. },
  25946. components: makePanels(parts, panelConfigs),
  25947. slotBehaviours: SimpleBehaviours.unnamedEvents([
  25948. runOnAttached((slotContainer) => SlotContainer.hideAllSlots(slotContainer))
  25949. ])
  25950. }));
  25951. const setSidebar = (sidebar, panelConfigs, showSidebar) => {
  25952. const optSlider = Composing.getCurrent(sidebar);
  25953. optSlider.each((slider) => {
  25954. Replacing.set(slider, [makeSidebar(panelConfigs)]);
  25955. // Show the default sidebar
  25956. const configKey = showSidebar === null || showSidebar === void 0 ? void 0 : showSidebar.toLowerCase();
  25957. if (isString(configKey) && has$2(panelConfigs, configKey)) {
  25958. Composing.getCurrent(slider).each((slotContainer) => {
  25959. SlotContainer.showSlot(slotContainer, configKey);
  25960. Sliding.immediateGrow(slider);
  25961. // TINY-8710: Remove the width as since the skins/styles won't have loaded yet, so it's going to be incorrect
  25962. remove$6(slider.element, 'width');
  25963. updateSidebarRoleOnToggle(sidebar.element, "region" /* SidebarStateRoleAttr.Grown */);
  25964. });
  25965. }
  25966. });
  25967. };
  25968. const updateSidebarRoleOnToggle = (sidebar, sidebarState) => {
  25969. set$9(sidebar, 'role', sidebarState);
  25970. };
  25971. const toggleSidebar = (sidebar, name) => {
  25972. const optSlider = Composing.getCurrent(sidebar);
  25973. optSlider.each((slider) => {
  25974. const optSlotContainer = Composing.getCurrent(slider);
  25975. optSlotContainer.each((slotContainer) => {
  25976. if (Sliding.hasGrown(slider)) {
  25977. if (SlotContainer.isShowing(slotContainer, name)) {
  25978. // close the slider and then hide the slot after the animation finishes
  25979. Sliding.shrink(slider);
  25980. updateSidebarRoleOnToggle(sidebar.element, "presentation" /* SidebarStateRoleAttr.Shrunk */);
  25981. }
  25982. else {
  25983. SlotContainer.hideAllSlots(slotContainer);
  25984. SlotContainer.showSlot(slotContainer, name);
  25985. updateSidebarRoleOnToggle(sidebar.element, "region" /* SidebarStateRoleAttr.Grown */);
  25986. }
  25987. }
  25988. else {
  25989. // Should already be hidden if the animation has finished but if it has not we hide them
  25990. SlotContainer.hideAllSlots(slotContainer);
  25991. SlotContainer.showSlot(slotContainer, name);
  25992. Sliding.grow(slider);
  25993. updateSidebarRoleOnToggle(sidebar.element, "region" /* SidebarStateRoleAttr.Grown */);
  25994. }
  25995. });
  25996. });
  25997. };
  25998. const whichSidebar = (sidebar) => {
  25999. const optSlider = Composing.getCurrent(sidebar);
  26000. return optSlider.bind((slider) => {
  26001. const sidebarOpen = Sliding.isGrowing(slider) || Sliding.hasGrown(slider);
  26002. if (sidebarOpen) {
  26003. const optSlotContainer = Composing.getCurrent(slider);
  26004. return optSlotContainer.bind((slotContainer) => find$5(SlotContainer.getSlotNames(slotContainer), (name) => SlotContainer.isShowing(slotContainer, name)));
  26005. }
  26006. else {
  26007. return Optional.none();
  26008. }
  26009. });
  26010. };
  26011. const fixSize = generate$6('FixSizeEvent');
  26012. const autoSize = generate$6('AutoSizeEvent');
  26013. const renderSidebar = (spec) => ({
  26014. uid: spec.uid,
  26015. dom: {
  26016. tag: 'div',
  26017. classes: ['tox-sidebar'],
  26018. attributes: {
  26019. role: "presentation" /* SidebarStateRoleAttr.Shrunk */
  26020. }
  26021. },
  26022. components: [
  26023. {
  26024. dom: {
  26025. tag: 'div',
  26026. classes: ['tox-sidebar__slider']
  26027. },
  26028. components: [
  26029. // this will be replaced on setSidebar
  26030. ],
  26031. behaviours: derive$1([
  26032. Tabstopping.config({}),
  26033. Focusing.config({}), // TODO use Keying and use focusIn, but need to handle if sidebar contains nothing
  26034. Sliding.config({
  26035. dimension: {
  26036. property: 'width'
  26037. },
  26038. closedClass: 'tox-sidebar--sliding-closed',
  26039. openClass: 'tox-sidebar--sliding-open',
  26040. shrinkingClass: 'tox-sidebar--sliding-shrinking',
  26041. growingClass: 'tox-sidebar--sliding-growing',
  26042. onShrunk: (slider) => {
  26043. const optSlotContainer = Composing.getCurrent(slider);
  26044. optSlotContainer.each(SlotContainer.hideAllSlots);
  26045. emit(slider, autoSize);
  26046. },
  26047. onGrown: (slider) => {
  26048. emit(slider, autoSize);
  26049. },
  26050. onStartGrow: (slider) => {
  26051. emitWith(slider, fixSize, { width: getRaw(slider.element, 'width').getOr('') });
  26052. },
  26053. onStartShrink: (slider) => {
  26054. emitWith(slider, fixSize, { width: get$c(slider.element) + 'px' });
  26055. }
  26056. }),
  26057. Replacing.config({}),
  26058. Composing.config({
  26059. find: (comp) => {
  26060. const children = Replacing.contents(comp);
  26061. return head(children);
  26062. }
  26063. })
  26064. ])
  26065. }
  26066. ],
  26067. behaviours: derive$1([
  26068. ComposingConfigs.childAt(0),
  26069. config('sidebar-sliding-events', [
  26070. run$1(fixSize, (comp, se) => {
  26071. set$7(comp.element, 'width', se.event.width);
  26072. }),
  26073. run$1(autoSize, (comp, _se) => {
  26074. remove$6(comp.element, 'width');
  26075. })
  26076. ])
  26077. ])
  26078. });
  26079. const getBusySpec$1 = (providerBackstage) => (_root, _behaviours) => ({
  26080. dom: {
  26081. tag: 'div',
  26082. attributes: {
  26083. 'aria-label': providerBackstage.translate('Loading...'),
  26084. 'tabindex': '0'
  26085. },
  26086. classes: ['tox-throbber__busy-spinner']
  26087. },
  26088. components: [
  26089. {
  26090. dom: fromHtml('<div class="tox-spinner"><div></div><div></div><div></div></div>')
  26091. }
  26092. ],
  26093. });
  26094. const focusBusyComponent = (throbber) => Composing.getCurrent(throbber).each((comp) => focus$4(comp.element, true));
  26095. // When the throbber is enabled, prevent the iframe from being part of the sequential keyboard navigation when Tabbing
  26096. // TODO: TINY-7500 Only works for iframe mode at this stage
  26097. const toggleEditorTabIndex = (editor, state) => {
  26098. const tabIndexAttr = 'tabindex';
  26099. const dataTabIndexAttr = `data-mce-${tabIndexAttr}`;
  26100. Optional.from(editor.iframeElement)
  26101. .map(SugarElement.fromDom)
  26102. .each((iframe) => {
  26103. if (state) {
  26104. getOpt(iframe, tabIndexAttr).each((tabIndex) => set$9(iframe, dataTabIndexAttr, tabIndex));
  26105. set$9(iframe, tabIndexAttr, -1);
  26106. }
  26107. else {
  26108. remove$8(iframe, tabIndexAttr);
  26109. getOpt(iframe, dataTabIndexAttr).each((tabIndex) => {
  26110. set$9(iframe, tabIndexAttr, tabIndex);
  26111. remove$8(iframe, dataTabIndexAttr);
  26112. });
  26113. }
  26114. });
  26115. };
  26116. /*
  26117. * If the throbber has been toggled on, only focus the throbber if the editor had focus as we don't to steal focus if it is on an input or dialog
  26118. * If the throbber has been toggled off, only put focus back on the editor if the throbber had focus.
  26119. * The next logical focus transition from the throbber is to put it back on the editor
  26120. */
  26121. const toggleThrobber = (editor, comp, state, providerBackstage) => {
  26122. const element = comp.element;
  26123. toggleEditorTabIndex(editor, state);
  26124. if (state) {
  26125. Blocking.block(comp, getBusySpec$1(providerBackstage));
  26126. remove$6(element, 'display');
  26127. remove$8(element, 'aria-hidden');
  26128. if (editor.hasFocus()) {
  26129. focusBusyComponent(comp);
  26130. }
  26131. }
  26132. else {
  26133. // Get the focus of the busy component before it is removed from the DOM
  26134. const throbberFocus = Composing.getCurrent(comp).exists((busyComp) => hasFocus(busyComp.element));
  26135. Blocking.unblock(comp);
  26136. set$7(element, 'display', 'none');
  26137. set$9(element, 'aria-hidden', 'true');
  26138. if (throbberFocus) {
  26139. editor.focus();
  26140. }
  26141. }
  26142. };
  26143. const renderThrobber = (spec) => ({
  26144. uid: spec.uid,
  26145. dom: {
  26146. tag: 'div',
  26147. attributes: {
  26148. 'aria-hidden': 'true'
  26149. },
  26150. classes: ['tox-throbber'],
  26151. styles: {
  26152. display: 'none'
  26153. }
  26154. },
  26155. behaviours: derive$1([
  26156. Replacing.config({}),
  26157. Blocking.config({
  26158. focus: false
  26159. }),
  26160. Composing.config({
  26161. find: (comp) => head(comp.components())
  26162. })
  26163. ]),
  26164. components: []
  26165. });
  26166. const isFocusEvent = (event) => event.type === 'focusin';
  26167. const isPasteBinTarget = (event) => {
  26168. if (isFocusEvent(event)) {
  26169. const node = event.composed ? head(event.composedPath()) : Optional.from(event.target);
  26170. return node
  26171. .map(SugarElement.fromDom)
  26172. .filter(isElement$1)
  26173. .exists((targetElm) => has(targetElm, 'mce-pastebin'));
  26174. }
  26175. else {
  26176. return false;
  26177. }
  26178. };
  26179. const setup$7 = (editor, lazyThrobber, sharedBackstage) => {
  26180. const throbberState = Cell(false);
  26181. const timer = value$2();
  26182. const stealFocus = (e) => {
  26183. if (throbberState.get() && !isPasteBinTarget(e)) {
  26184. e.preventDefault();
  26185. focusBusyComponent(lazyThrobber());
  26186. editor.editorManager.setActive(editor);
  26187. }
  26188. };
  26189. // TODO: TINY-7500 Only worrying about iframe mode at this stage since inline mode has a number of other issues
  26190. if (!editor.inline) {
  26191. editor.on('PreInit', () => {
  26192. // Cover focus when the editor is focused natively
  26193. editor.dom.bind(editor.getWin(), 'focusin', stealFocus);
  26194. // Cover stealing focus when editor.focus() is called
  26195. editor.on('BeforeExecCommand', (e) => {
  26196. // If skipFocus is specified as true in the command, don't focus the Throbber
  26197. if (e.command.toLowerCase() === 'mcefocus' && e.value !== true) {
  26198. stealFocus(e);
  26199. }
  26200. });
  26201. });
  26202. }
  26203. const toggle = (state) => {
  26204. if (state !== throbberState.get()) {
  26205. throbberState.set(state);
  26206. toggleThrobber(editor, lazyThrobber(), state, sharedBackstage.providers);
  26207. fireAfterProgressState(editor, state);
  26208. }
  26209. };
  26210. editor.on('ProgressState', (e) => {
  26211. timer.on(clearTimeout);
  26212. if (isNumber(e.time)) {
  26213. const timerId = global$a.setEditorTimeout(editor, () => toggle(e.state), e.time);
  26214. timer.set(timerId);
  26215. }
  26216. else {
  26217. toggle(e.state);
  26218. timer.clear();
  26219. }
  26220. });
  26221. };
  26222. const renderToolbarGroupCommon = (toolbarGroup) => {
  26223. const attributes = toolbarGroup.label.isNone() ?
  26224. toolbarGroup.title.fold(() => ({}), (title) => ({ attributes: { 'aria-label': title } }))
  26225. : toolbarGroup.label.fold(() => ({}), (label) => ({ attributes: { 'aria-label': label } }));
  26226. return {
  26227. dom: {
  26228. tag: 'div',
  26229. classes: ['tox-toolbar__group'].concat(toolbarGroup.label.isSome() ? ['tox-toolbar__group_with_label'] : []),
  26230. ...attributes
  26231. },
  26232. components: [
  26233. ...(toolbarGroup.label.map((label) => {
  26234. return {
  26235. dom: {
  26236. tag: 'span',
  26237. classes: ['tox-label', 'tox-label--context-toolbar'],
  26238. },
  26239. components: [text$2(label)]
  26240. };
  26241. }).toArray()),
  26242. ToolbarGroup.parts.items({})
  26243. ],
  26244. items: toolbarGroup.items,
  26245. markers: {
  26246. // nav within a group breaks if disabled buttons are first in their group so skip them
  26247. itemSelector: '.tox-tbtn:not([disabled]), ' +
  26248. '.tox-toolbar-nav-item:not([disabled]), ' +
  26249. '.tox-number-input:not([disabled])'
  26250. },
  26251. tgroupBehaviours: derive$1([
  26252. Tabstopping.config({}),
  26253. Focusing.config({
  26254. ignore: true
  26255. })
  26256. ])
  26257. };
  26258. };
  26259. const renderToolbarGroup = (toolbarGroup) => ToolbarGroup.sketch(renderToolbarGroupCommon(toolbarGroup));
  26260. const getToolbarBehaviours = (toolbarSpec, modeName) => {
  26261. const onAttached = runOnAttached((component) => {
  26262. const groups = map$2(toolbarSpec.initGroups, renderToolbarGroup);
  26263. Toolbar.setGroups(component, groups);
  26264. });
  26265. return derive$1([
  26266. DisablingConfigs.toolbarButton(() => toolbarSpec.providers.checkUiComponentContext('any').shouldDisable),
  26267. toggleOnReceive(() => toolbarSpec.providers.checkUiComponentContext('any')),
  26268. Keying.config({
  26269. // Tabs between groups
  26270. mode: modeName,
  26271. onEscape: toolbarSpec.onEscape,
  26272. visibilitySelector: '.tox-toolbar__overflow',
  26273. selector: '.tox-toolbar__group'
  26274. }),
  26275. config('toolbar-events', [onAttached])
  26276. ]);
  26277. };
  26278. const renderMoreToolbarCommon = (toolbarSpec) => {
  26279. const modeName = toolbarSpec.cyclicKeying ? 'cyclic' : 'acyclic';
  26280. return {
  26281. uid: toolbarSpec.uid,
  26282. dom: {
  26283. tag: 'div',
  26284. classes: ['tox-toolbar-overlord']
  26285. },
  26286. parts: {
  26287. // This already knows it is a toolbar group
  26288. 'overflow-group': renderToolbarGroupCommon({
  26289. title: Optional.none(),
  26290. label: Optional.none(),
  26291. items: []
  26292. }),
  26293. 'overflow-button': renderIconButtonSpec({
  26294. context: 'any',
  26295. name: 'more',
  26296. icon: Optional.some('more-drawer'),
  26297. enabled: true,
  26298. tooltip: Optional.some('Reveal or hide additional toolbar items'),
  26299. primary: false,
  26300. buttonType: Optional.none(),
  26301. borderless: false
  26302. }, Optional.none(), toolbarSpec.providers, [], 'overflow-button')
  26303. },
  26304. splitToolbarBehaviours: getToolbarBehaviours(toolbarSpec, modeName)
  26305. };
  26306. };
  26307. const renderFloatingMoreToolbar = (toolbarSpec) => {
  26308. const baseSpec = renderMoreToolbarCommon(toolbarSpec);
  26309. const overflowXOffset = 4;
  26310. const primary = SplitFloatingToolbar.parts.primary({
  26311. dom: {
  26312. tag: 'div',
  26313. classes: ['tox-toolbar__primary']
  26314. }
  26315. });
  26316. return SplitFloatingToolbar.sketch({
  26317. ...baseSpec,
  26318. lazySink: toolbarSpec.getSink,
  26319. getOverflowBounds: () => {
  26320. // Restrict the left/right bounds to the editor header width, but don't restrict the top/bottom
  26321. const headerElem = toolbarSpec.moreDrawerData.lazyHeader().element;
  26322. const headerBounds = absolute$2(headerElem);
  26323. const docElem = documentElement(headerElem);
  26324. const docBounds = absolute$2(docElem);
  26325. const height = Math.max(docElem.dom.scrollHeight, docBounds.height);
  26326. return bounds(headerBounds.x + overflowXOffset, docBounds.y, headerBounds.width - overflowXOffset * 2, height);
  26327. },
  26328. parts: {
  26329. ...baseSpec.parts,
  26330. overflow: {
  26331. dom: {
  26332. tag: 'div',
  26333. classes: ['tox-toolbar__overflow'],
  26334. attributes: toolbarSpec.attributes
  26335. }
  26336. }
  26337. },
  26338. components: [primary],
  26339. markers: {
  26340. overflowToggledClass: "tox-tbtn--enabled" /* ToolbarButtonClasses.Ticked */
  26341. },
  26342. onOpened: (comp) => toolbarSpec.onToggled(comp, true),
  26343. onClosed: (comp) => toolbarSpec.onToggled(comp, false)
  26344. });
  26345. };
  26346. const renderSlidingMoreToolbar = (toolbarSpec) => {
  26347. const primary = SplitSlidingToolbar.parts.primary({
  26348. dom: {
  26349. tag: 'div',
  26350. classes: ['tox-toolbar__primary']
  26351. }
  26352. });
  26353. const overflow = SplitSlidingToolbar.parts.overflow({
  26354. dom: {
  26355. tag: 'div',
  26356. classes: ['tox-toolbar__overflow']
  26357. }
  26358. });
  26359. const baseSpec = renderMoreToolbarCommon(toolbarSpec);
  26360. return SplitSlidingToolbar.sketch({
  26361. ...baseSpec,
  26362. components: [primary, overflow],
  26363. markers: {
  26364. openClass: 'tox-toolbar__overflow--open',
  26365. closedClass: 'tox-toolbar__overflow--closed',
  26366. growingClass: 'tox-toolbar__overflow--growing',
  26367. shrinkingClass: 'tox-toolbar__overflow--shrinking',
  26368. overflowToggledClass: "tox-tbtn--enabled" /* ToolbarButtonClasses.Ticked */
  26369. },
  26370. onOpened: (comp) => {
  26371. // TINY-9223: This will only broadcast to the same mothership as the toolbar
  26372. comp.getSystem().broadcastOn([toolbarHeightChange()], { type: 'opened' });
  26373. toolbarSpec.onToggled(comp, true);
  26374. },
  26375. onClosed: (comp) => {
  26376. // TINY-9223: This will only broadcast to the same mothership as the toolbar
  26377. comp.getSystem().broadcastOn([toolbarHeightChange()], { type: 'closed' });
  26378. toolbarSpec.onToggled(comp, false);
  26379. }
  26380. });
  26381. };
  26382. const renderToolbar = (toolbarSpec) => {
  26383. const modeName = toolbarSpec.cyclicKeying ? 'cyclic' : 'acyclic';
  26384. return Toolbar.sketch({
  26385. uid: toolbarSpec.uid,
  26386. dom: {
  26387. tag: 'div',
  26388. classes: ['tox-toolbar'].concat(toolbarSpec.type === ToolbarMode$1.scrolling ? ['tox-toolbar--scrolling'] : [])
  26389. },
  26390. components: [
  26391. Toolbar.parts.groups({})
  26392. ],
  26393. toolbarBehaviours: getToolbarBehaviours(toolbarSpec, modeName)
  26394. });
  26395. };
  26396. const renderButton = (spec, providers) => {
  26397. var _a, _b;
  26398. const isToggleButton = spec.type === 'togglebutton';
  26399. const optMemIcon = spec.icon
  26400. .map((memIcon) => renderReplaceableIconFromPack(memIcon, providers.icons))
  26401. .map(record);
  26402. const getAction = () => (comp) => {
  26403. const setIcon = (newIcon) => {
  26404. optMemIcon.map((memIcon) => memIcon.getOpt(comp).each((displayIcon) => {
  26405. Replacing.set(displayIcon, [
  26406. renderReplaceableIconFromPack(newIcon, providers.icons)
  26407. ]);
  26408. }));
  26409. };
  26410. const setActive = (state) => {
  26411. const elm = comp.element;
  26412. if (state) {
  26413. add$2(elm, "tox-button--enabled" /* ViewButtonClasses.Ticked */);
  26414. set$9(elm, 'aria-pressed', true);
  26415. }
  26416. else {
  26417. remove$3(elm, "tox-button--enabled" /* ViewButtonClasses.Ticked */);
  26418. remove$8(elm, 'aria-pressed');
  26419. }
  26420. };
  26421. const isActive = () => has(comp.element, "tox-button--enabled" /* ViewButtonClasses.Ticked */);
  26422. const focus = () => focus$4(comp.element);
  26423. if (isToggleButton) {
  26424. return spec.onAction({ setIcon, setActive, isActive, focus });
  26425. }
  26426. if (spec.type === 'button') {
  26427. return spec.onAction({ setIcon });
  26428. }
  26429. };
  26430. const action = getAction();
  26431. const buttonSpec = {
  26432. ...spec,
  26433. name: isToggleButton ? spec.text.getOr(spec.icon.getOr('')) : (_a = spec.text) !== null && _a !== void 0 ? _a : spec.icon.getOr(''),
  26434. primary: spec.buttonType === 'primary',
  26435. buttonType: Optional.from(spec.buttonType),
  26436. tooltip: spec.tooltip,
  26437. icon: spec.icon,
  26438. enabled: true,
  26439. borderless: spec.borderless
  26440. };
  26441. const buttonTypeClasses = calculateClassesFromButtonType((_b = spec.buttonType) !== null && _b !== void 0 ? _b : 'secondary');
  26442. const optTranslatedText = isToggleButton ? spec.text.map(providers.translate) : Optional.some(providers.translate(spec.text));
  26443. const optTranslatedTextComponed = optTranslatedText.map(text$2);
  26444. const ariaLabelAttributes = buttonSpec.tooltip.or(optTranslatedText).map((al) => ({
  26445. 'aria-label': providers.translate(al),
  26446. })).getOr({});
  26447. const optIconSpec = optMemIcon.map((memIcon) => memIcon.asSpec());
  26448. const components = componentRenderPipeline([optIconSpec, optTranslatedTextComponed]);
  26449. const hasIconAndText = spec.icon.isSome() && optTranslatedTextComponed.isSome();
  26450. const dom = {
  26451. tag: 'button',
  26452. classes: buttonTypeClasses
  26453. .concat(...spec.icon.isSome() && !hasIconAndText ? ['tox-button--icon'] : [])
  26454. .concat(...hasIconAndText ? ['tox-button--icon-and-text'] : [])
  26455. .concat(...spec.borderless ? ['tox-button--naked'] : [])
  26456. .concat(...spec.type === 'togglebutton' && spec.active ? ["tox-button--enabled" /* ViewButtonClasses.Ticked */] : []),
  26457. attributes: ariaLabelAttributes
  26458. };
  26459. const extraBehaviours = [];
  26460. const iconButtonSpec = renderCommonSpec(buttonSpec, Optional.some(action), extraBehaviours, dom, components, spec.tooltip, providers);
  26461. return Button.sketch(iconButtonSpec);
  26462. };
  26463. const renderViewButton = (spec, providers) => renderButton(spec, providers);
  26464. const renderButtonsGroup = (spec, providers) => {
  26465. return {
  26466. dom: {
  26467. tag: 'div',
  26468. classes: ['tox-view__toolbar__group'],
  26469. },
  26470. components: map$2(spec.buttons, (button) => renderViewButton(button, providers))
  26471. };
  26472. };
  26473. const deviceDetection = detect$1().deviceType;
  26474. const isPhone = deviceDetection.isPhone();
  26475. const isTablet = deviceDetection.isTablet();
  26476. const renderViewHeader = (spec) => {
  26477. let hasGroups = false;
  26478. const endButtons = map$2(spec.buttons, (btnspec) => {
  26479. if (btnspec.type === 'group') {
  26480. hasGroups = true;
  26481. return renderButtonsGroup(btnspec, spec.providers);
  26482. }
  26483. else {
  26484. return renderViewButton(btnspec, spec.providers);
  26485. }
  26486. });
  26487. return {
  26488. uid: spec.uid,
  26489. dom: {
  26490. tag: 'div',
  26491. classes: [
  26492. !hasGroups ? 'tox-view__header' : 'tox-view__toolbar',
  26493. ...(isPhone || isTablet ? ['tox-view--mobile', 'tox-view--scrolling'] : [])
  26494. ]
  26495. },
  26496. behaviours: derive$1([
  26497. Focusing.config({}),
  26498. Keying.config({
  26499. mode: 'flow',
  26500. selector: 'button, .tox-button',
  26501. focusInside: FocusInsideModes.OnEnterOrSpaceMode
  26502. })
  26503. ]),
  26504. components: hasGroups ?
  26505. endButtons
  26506. : [
  26507. Container.sketch({
  26508. dom: {
  26509. tag: 'div',
  26510. classes: ['tox-view__header-start']
  26511. },
  26512. components: []
  26513. }),
  26514. Container.sketch({
  26515. dom: {
  26516. tag: 'div',
  26517. classes: ['tox-view__header-end']
  26518. },
  26519. components: endButtons
  26520. })
  26521. ]
  26522. };
  26523. };
  26524. const renderViewPane = (spec) => {
  26525. return {
  26526. uid: spec.uid,
  26527. behaviours: derive$1([
  26528. Focusing.config({}),
  26529. Tabstopping.config({})
  26530. ]),
  26531. dom: {
  26532. tag: 'div',
  26533. classes: ['tox-view__pane']
  26534. }
  26535. };
  26536. };
  26537. const factory$2 = (detail, components, _spec, _externals) => {
  26538. const apis = {
  26539. getPane: (comp) => parts$g.getPart(comp, detail, 'pane'),
  26540. getOnShow: (_comp) => detail.viewConfig.onShow,
  26541. getOnHide: (_comp) => detail.viewConfig.onHide,
  26542. };
  26543. return {
  26544. uid: detail.uid,
  26545. dom: detail.dom,
  26546. components,
  26547. behaviours: derive$1([
  26548. Focusing.config({}),
  26549. Keying.config({
  26550. mode: 'cyclic',
  26551. focusInside: FocusInsideModes.OnEnterOrSpaceMode
  26552. })
  26553. ]),
  26554. apis
  26555. };
  26556. };
  26557. var View = composite({
  26558. name: 'silver.View',
  26559. configFields: [
  26560. required$1('viewConfig'),
  26561. ],
  26562. partFields: [
  26563. optional({
  26564. factory: {
  26565. sketch: renderViewHeader
  26566. },
  26567. schema: [
  26568. required$1('buttons'),
  26569. required$1('providers')
  26570. ],
  26571. name: 'header'
  26572. }),
  26573. optional({
  26574. factory: {
  26575. sketch: renderViewPane
  26576. },
  26577. schema: [],
  26578. name: 'pane'
  26579. })
  26580. ],
  26581. factory: factory$2,
  26582. apis: {
  26583. getPane: (apis, comp) => apis.getPane(comp),
  26584. getOnShow: (apis, comp) => apis.getOnShow(comp),
  26585. getOnHide: (apis, comp) => apis.getOnHide(comp)
  26586. }
  26587. });
  26588. const makeViews = (parts, viewConfigs, providers) => {
  26589. return mapToArray(viewConfigs, (config, name) => {
  26590. const internalViewConfig = getOrDie(createView(config));
  26591. return parts.slot(name, View.sketch({
  26592. dom: {
  26593. tag: 'div',
  26594. classes: ['tox-view']
  26595. },
  26596. viewConfig: internalViewConfig,
  26597. components: [
  26598. ...internalViewConfig.buttons.length > 0 ? [
  26599. View.parts.header({
  26600. buttons: internalViewConfig.buttons,
  26601. providers
  26602. })
  26603. ] : [],
  26604. View.parts.pane({})
  26605. ]
  26606. }));
  26607. });
  26608. };
  26609. const makeSlotContainer = (viewConfigs, providers) => SlotContainer.sketch((parts) => ({
  26610. dom: {
  26611. tag: 'div',
  26612. classes: ['tox-view-wrap__slot-container']
  26613. },
  26614. components: makeViews(parts, viewConfigs, providers),
  26615. slotBehaviours: SimpleBehaviours.unnamedEvents([
  26616. runOnAttached((slotContainer) => SlotContainer.hideAllSlots(slotContainer))
  26617. ])
  26618. }));
  26619. const getCurrentName = (slotContainer) => {
  26620. return find$5(SlotContainer.getSlotNames(slotContainer), (name) => SlotContainer.isShowing(slotContainer, name));
  26621. };
  26622. const hideContainer = (comp) => {
  26623. const element = comp.element;
  26624. set$7(element, 'display', 'none');
  26625. set$9(element, 'aria-hidden', 'true');
  26626. };
  26627. const showContainer = (comp) => {
  26628. const element = comp.element;
  26629. remove$6(element, 'display');
  26630. remove$8(element, 'aria-hidden');
  26631. };
  26632. const makeViewInstanceApi = (slot) => ({
  26633. getContainer: constant$1(slot)
  26634. });
  26635. const runOnPaneWithInstanceApi = (slotContainer, name, get) => {
  26636. SlotContainer.getSlot(slotContainer, name).each((view) => {
  26637. View.getPane(view).each((pane) => {
  26638. const onCallback = get(view);
  26639. onCallback(makeViewInstanceApi(pane.element.dom));
  26640. });
  26641. });
  26642. };
  26643. const runOnShow = (slotContainer, name) => runOnPaneWithInstanceApi(slotContainer, name, View.getOnShow);
  26644. const runOnHide = (slotContainer, name) => runOnPaneWithInstanceApi(slotContainer, name, View.getOnHide);
  26645. const factory$1 = (detail, spec) => {
  26646. const setViews = (comp, viewConfigs) => {
  26647. Replacing.set(comp, [makeSlotContainer(viewConfigs, spec.backstage.shared.providers)]);
  26648. };
  26649. const whichView = (comp) => {
  26650. return Composing.getCurrent(comp).bind(getCurrentName);
  26651. };
  26652. const toggleView = (comp, showMainView, hideMainView, name) => {
  26653. return Composing.getCurrent(comp).exists((slotContainer) => {
  26654. const optCurrentSlotName = getCurrentName(slotContainer);
  26655. const isTogglingCurrentView = optCurrentSlotName.exists((current) => name === current);
  26656. const exists = SlotContainer.getSlot(slotContainer, name).isSome();
  26657. if (exists) {
  26658. SlotContainer.hideAllSlots(slotContainer);
  26659. if (!isTogglingCurrentView) {
  26660. hideMainView();
  26661. showContainer(comp);
  26662. SlotContainer.showSlot(slotContainer, name);
  26663. runOnShow(slotContainer, name);
  26664. }
  26665. else {
  26666. hideContainer(comp);
  26667. showMainView();
  26668. }
  26669. optCurrentSlotName.each((prevName) => runOnHide(slotContainer, prevName));
  26670. }
  26671. return exists;
  26672. });
  26673. };
  26674. const apis = {
  26675. setViews,
  26676. whichView,
  26677. toggleView
  26678. };
  26679. return {
  26680. uid: detail.uid,
  26681. dom: {
  26682. tag: 'div',
  26683. classes: ['tox-view-wrap'],
  26684. attributes: { 'aria-hidden': 'true' },
  26685. styles: { display: 'none' }
  26686. },
  26687. components: [
  26688. // this will be replaced on setViews
  26689. ],
  26690. behaviours: derive$1([
  26691. Replacing.config({}),
  26692. Composing.config({
  26693. find: (comp) => {
  26694. const children = Replacing.contents(comp);
  26695. return head(children);
  26696. }
  26697. })
  26698. ]),
  26699. apis
  26700. };
  26701. };
  26702. var ViewWrapper = single({
  26703. factory: factory$1,
  26704. name: 'silver.ViewWrapper',
  26705. configFields: [
  26706. required$1('backstage')
  26707. ],
  26708. apis: {
  26709. setViews: (apis, comp, views) => apis.setViews(comp, views),
  26710. toggleView: (apis, comp, outerContainer, editorCont, name) => apis.toggleView(comp, outerContainer, editorCont, name),
  26711. whichView: (apis, comp) => apis.whichView(comp)
  26712. }
  26713. });
  26714. const factory = (detail, components, _spec) => {
  26715. let toolbarDrawerOpenState = false;
  26716. const toggleStatusbar = (editorContainer) => {
  26717. sibling(editorContainer, '.tox-statusbar').each((statusBar) => {
  26718. if (get$e(statusBar, 'display') === 'none' && get$g(statusBar, 'aria-hidden') === 'true') {
  26719. remove$6(statusBar, 'display');
  26720. remove$8(statusBar, 'aria-hidden');
  26721. }
  26722. else {
  26723. set$7(statusBar, 'display', 'none');
  26724. set$9(statusBar, 'aria-hidden', 'true');
  26725. }
  26726. });
  26727. };
  26728. const apis = {
  26729. getSocket: (comp) => {
  26730. return parts$g.getPart(comp, detail, 'socket');
  26731. },
  26732. setSidebar: (comp, panelConfigs, showSidebar) => {
  26733. parts$g.getPart(comp, detail, 'sidebar').each((sidebar) => setSidebar(sidebar, panelConfigs, showSidebar));
  26734. },
  26735. toggleSidebar: (comp, name) => {
  26736. parts$g.getPart(comp, detail, 'sidebar').each((sidebar) => toggleSidebar(sidebar, name));
  26737. },
  26738. whichSidebar: (comp) => {
  26739. return parts$g.getPart(comp, detail, 'sidebar').bind(whichSidebar).getOrNull();
  26740. },
  26741. getHeader: (comp) => {
  26742. return parts$g.getPart(comp, detail, 'header');
  26743. },
  26744. getToolbar: (comp) => {
  26745. return parts$g.getPart(comp, detail, 'toolbar');
  26746. },
  26747. setToolbar: (comp, groups) => {
  26748. parts$g.getPart(comp, detail, 'toolbar').each((toolbar) => {
  26749. const renderedGroups = map$2(groups, renderToolbarGroup);
  26750. toolbar.getApis().setGroups(toolbar, renderedGroups);
  26751. });
  26752. },
  26753. setToolbars: (comp, toolbars) => {
  26754. parts$g.getPart(comp, detail, 'multiple-toolbar').each((mToolbar) => {
  26755. const renderedToolbars = map$2(toolbars, (g) => map$2(g, renderToolbarGroup));
  26756. CustomList.setItems(mToolbar, renderedToolbars);
  26757. });
  26758. },
  26759. refreshToolbar: (comp) => {
  26760. const toolbar = parts$g.getPart(comp, detail, 'toolbar');
  26761. toolbar.each((toolbar) => toolbar.getApis().refresh(toolbar));
  26762. },
  26763. toggleToolbarDrawer: (comp) => {
  26764. parts$g.getPart(comp, detail, 'toolbar').each((toolbar) => {
  26765. mapFrom(toolbar.getApis().toggle, (toggle) => toggle(toolbar));
  26766. });
  26767. },
  26768. toggleToolbarDrawerWithoutFocusing: (comp) => {
  26769. parts$g.getPart(comp, detail, 'toolbar').each((toolbar) => {
  26770. mapFrom(toolbar.getApis().toggleWithoutFocusing, (toggleWithoutFocusing) => toggleWithoutFocusing(toolbar));
  26771. });
  26772. },
  26773. isToolbarDrawerToggled: (comp) => {
  26774. // isOpen may not be defined on all toolbars e.g. 'scrolling' and 'wrap'
  26775. return parts$g.getPart(comp, detail, 'toolbar')
  26776. .bind((toolbar) => Optional.from(toolbar.getApis().isOpen).map((isOpen) => isOpen(toolbar)))
  26777. .getOr(false);
  26778. },
  26779. getThrobber: (comp) => {
  26780. return parts$g.getPart(comp, detail, 'throbber');
  26781. },
  26782. focusToolbar: (comp) => {
  26783. const optToolbar = parts$g.getPart(comp, detail, 'toolbar').orThunk(() => parts$g.getPart(comp, detail, 'multiple-toolbar'));
  26784. optToolbar.each((toolbar) => {
  26785. Keying.focusIn(toolbar);
  26786. });
  26787. },
  26788. setMenubar: (comp, menus) => {
  26789. parts$g.getPart(comp, detail, 'menubar').each((menubar) => {
  26790. SilverMenubar.setMenus(menubar, menus);
  26791. });
  26792. },
  26793. focusMenubar: (comp) => {
  26794. parts$g.getPart(comp, detail, 'menubar').each((menubar) => {
  26795. SilverMenubar.focus(menubar);
  26796. });
  26797. },
  26798. setViews: (comp, viewConfigs) => {
  26799. parts$g.getPart(comp, detail, 'viewWrapper').each((wrapper) => {
  26800. ViewWrapper.setViews(wrapper, viewConfigs);
  26801. });
  26802. },
  26803. toggleView: (comp, name) => {
  26804. return parts$g.getPart(comp, detail, 'viewWrapper').exists((wrapper) => ViewWrapper.toggleView(wrapper, () => apis.showMainView(comp), () => apis.hideMainView(comp), name));
  26805. },
  26806. whichView: (comp) => {
  26807. return parts$g.getPart(comp, detail, 'viewWrapper').bind(ViewWrapper.whichView).getOrNull();
  26808. },
  26809. hideMainView: (comp) => {
  26810. toolbarDrawerOpenState = apis.isToolbarDrawerToggled(comp);
  26811. if (toolbarDrawerOpenState) {
  26812. apis.toggleToolbarDrawer(comp);
  26813. }
  26814. parts$g.getPart(comp, detail, 'editorContainer').each((editorContainer) => {
  26815. const element = editorContainer.element;
  26816. toggleStatusbar(element);
  26817. set$7(element, 'display', 'none');
  26818. set$9(element, 'aria-hidden', 'true');
  26819. });
  26820. },
  26821. showMainView: (comp) => {
  26822. if (toolbarDrawerOpenState) {
  26823. apis.toggleToolbarDrawer(comp);
  26824. }
  26825. parts$g.getPart(comp, detail, 'editorContainer').each((editorContainer) => {
  26826. const element = editorContainer.element;
  26827. toggleStatusbar(element);
  26828. remove$6(element, 'display');
  26829. remove$8(element, 'aria-hidden');
  26830. });
  26831. }
  26832. };
  26833. return {
  26834. uid: detail.uid,
  26835. dom: detail.dom,
  26836. components,
  26837. apis,
  26838. behaviours: detail.behaviours
  26839. };
  26840. };
  26841. const partMenubar = partType$1.optional({
  26842. factory: SilverMenubar,
  26843. name: 'menubar',
  26844. schema: [
  26845. required$1('backstage')
  26846. ]
  26847. });
  26848. const toolbarFactory = (spec) => {
  26849. if (spec.type === ToolbarMode$1.sliding) {
  26850. return renderSlidingMoreToolbar;
  26851. }
  26852. else if (spec.type === ToolbarMode$1.floating) {
  26853. return renderFloatingMoreToolbar;
  26854. }
  26855. else {
  26856. return renderToolbar;
  26857. }
  26858. };
  26859. const partMultipleToolbar = partType$1.optional({
  26860. factory: {
  26861. sketch: (spec) => CustomList.sketch({
  26862. uid: spec.uid,
  26863. dom: spec.dom,
  26864. listBehaviours: derive$1([
  26865. Keying.config({
  26866. mode: 'acyclic',
  26867. selector: '.tox-toolbar'
  26868. })
  26869. ]),
  26870. makeItem: () => renderToolbar({
  26871. type: spec.type,
  26872. uid: generate$6('multiple-toolbar-item'),
  26873. cyclicKeying: false,
  26874. initGroups: [],
  26875. providers: spec.providers,
  26876. onEscape: () => {
  26877. spec.onEscape();
  26878. return Optional.some(true);
  26879. }
  26880. }),
  26881. setupItem: (_mToolbar, tc, data, _index) => {
  26882. Toolbar.setGroups(tc, data);
  26883. },
  26884. shell: true
  26885. })
  26886. },
  26887. name: 'multiple-toolbar',
  26888. schema: [
  26889. required$1('dom'),
  26890. required$1('onEscape')
  26891. ]
  26892. });
  26893. const partToolbar = partType$1.optional({
  26894. factory: {
  26895. sketch: (spec) => {
  26896. const renderer = toolbarFactory(spec);
  26897. const toolbarSpec = {
  26898. type: spec.type,
  26899. uid: spec.uid,
  26900. onEscape: () => {
  26901. spec.onEscape();
  26902. return Optional.some(true);
  26903. },
  26904. onToggled: (_comp, state) => spec.onToolbarToggled(state),
  26905. cyclicKeying: false,
  26906. initGroups: [],
  26907. getSink: spec.getSink,
  26908. providers: spec.providers,
  26909. moreDrawerData: {
  26910. lazyToolbar: spec.lazyToolbar,
  26911. lazyMoreButton: spec.lazyMoreButton,
  26912. lazyHeader: spec.lazyHeader
  26913. },
  26914. attributes: spec.attributes
  26915. };
  26916. return renderer(toolbarSpec);
  26917. }
  26918. },
  26919. name: 'toolbar',
  26920. schema: [
  26921. required$1('dom'),
  26922. required$1('onEscape'),
  26923. required$1('getSink')
  26924. ]
  26925. });
  26926. const partHeader = partType$1.optional({
  26927. factory: {
  26928. sketch: renderHeader
  26929. },
  26930. name: 'header',
  26931. schema: [
  26932. required$1('dom')
  26933. ]
  26934. });
  26935. const partPromotion = partType$1.optional({
  26936. factory: {
  26937. sketch: renderPromotion
  26938. },
  26939. name: 'promotion',
  26940. schema: [
  26941. required$1('dom'),
  26942. required$1('promotionLink')
  26943. ]
  26944. });
  26945. const partSocket = partType$1.optional({
  26946. // factory: Fun.identity,
  26947. name: 'socket',
  26948. schema: [
  26949. required$1('dom')
  26950. ]
  26951. });
  26952. const partSidebar = partType$1.optional({
  26953. factory: {
  26954. sketch: renderSidebar
  26955. },
  26956. name: 'sidebar',
  26957. schema: [
  26958. required$1('dom')
  26959. ]
  26960. });
  26961. const partThrobber = partType$1.optional({
  26962. factory: {
  26963. sketch: renderThrobber
  26964. },
  26965. name: 'throbber',
  26966. schema: [
  26967. required$1('dom')
  26968. ]
  26969. });
  26970. const partViewWrapper = partType$1.optional({
  26971. factory: ViewWrapper,
  26972. name: 'viewWrapper',
  26973. schema: [
  26974. required$1('backstage')
  26975. ]
  26976. });
  26977. const renderEditorContainer = (spec) => ({
  26978. uid: spec.uid,
  26979. dom: {
  26980. tag: 'div',
  26981. classes: ['tox-editor-container']
  26982. },
  26983. components: spec.components
  26984. });
  26985. const partEditorContainer = partType$1.optional({
  26986. factory: {
  26987. sketch: renderEditorContainer
  26988. },
  26989. name: 'editorContainer',
  26990. schema: []
  26991. });
  26992. var OuterContainer = composite({
  26993. name: 'OuterContainer',
  26994. factory,
  26995. configFields: [
  26996. required$1('dom'),
  26997. required$1('behaviours')
  26998. ],
  26999. partFields: [
  27000. partHeader,
  27001. partMenubar,
  27002. partToolbar,
  27003. partMultipleToolbar,
  27004. partSocket,
  27005. partSidebar,
  27006. partPromotion,
  27007. partThrobber,
  27008. partViewWrapper,
  27009. partEditorContainer
  27010. ],
  27011. apis: {
  27012. getSocket: (apis, comp) => {
  27013. return apis.getSocket(comp);
  27014. },
  27015. setSidebar: (apis, comp, panelConfigs, showSidebar) => {
  27016. apis.setSidebar(comp, panelConfigs, showSidebar);
  27017. },
  27018. toggleSidebar: (apis, comp, name) => {
  27019. apis.toggleSidebar(comp, name);
  27020. },
  27021. whichSidebar: (apis, comp) => {
  27022. return apis.whichSidebar(comp);
  27023. },
  27024. getHeader: (apis, comp) => {
  27025. return apis.getHeader(comp);
  27026. },
  27027. getToolbar: (apis, comp) => {
  27028. return apis.getToolbar(comp);
  27029. },
  27030. setToolbar: (apis, comp, groups) => {
  27031. apis.setToolbar(comp, groups);
  27032. },
  27033. setToolbars: (apis, comp, toolbars) => {
  27034. apis.setToolbars(comp, toolbars);
  27035. },
  27036. refreshToolbar: (apis, comp) => {
  27037. return apis.refreshToolbar(comp);
  27038. },
  27039. toggleToolbarDrawer: (apis, comp) => {
  27040. apis.toggleToolbarDrawer(comp);
  27041. },
  27042. toggleToolbarDrawerWithoutFocusing: (apis, comp) => {
  27043. apis.toggleToolbarDrawerWithoutFocusing(comp);
  27044. },
  27045. isToolbarDrawerToggled: (apis, comp) => {
  27046. return apis.isToolbarDrawerToggled(comp);
  27047. },
  27048. getThrobber: (apis, comp) => {
  27049. return apis.getThrobber(comp);
  27050. },
  27051. // FIX: Dupe
  27052. setMenubar: (apis, comp, menus) => {
  27053. apis.setMenubar(comp, menus);
  27054. },
  27055. focusMenubar: (apis, comp) => {
  27056. apis.focusMenubar(comp);
  27057. },
  27058. focusToolbar: (apis, comp) => {
  27059. apis.focusToolbar(comp);
  27060. },
  27061. setViews: (apis, comp, views) => {
  27062. apis.setViews(comp, views);
  27063. },
  27064. toggleView: (apis, comp, name) => {
  27065. return apis.toggleView(comp, name);
  27066. },
  27067. whichView: (apis, comp) => {
  27068. return apis.whichView(comp);
  27069. },
  27070. }
  27071. });
  27072. const defaultMenubar = 'file edit view insert format tools table help';
  27073. const defaultMenus = {
  27074. file: { title: 'File', items: 'newdocument restoredraft | preview | importword exportpdf exportword | export print | deleteallconversations' },
  27075. edit: { title: 'Edit', items: 'undo redo | cut copy paste pastetext | selectall | searchreplace' },
  27076. view: { title: 'View', items: 'code suggestededits revisionhistory | visualaid visualchars visualblocks | spellchecker | preview fullscreen | showcomments' },
  27077. insert: { title: 'Insert', items: 'image link media addcomment pageembed inserttemplate codesample inserttable accordion math | charmap emoticons hr | pagebreak nonbreaking anchor tableofcontents footnotes | mergetags | insertdatetime' },
  27078. format: { title: 'Format', items: 'bold italic underline strikethrough superscript subscript codeformat | styles blocks fontfamily fontsize align lineheight | forecolor backcolor | language | removeformat' },
  27079. tools: { title: 'Tools', items: 'aidialog aishortcuts | spellchecker spellcheckerlanguage | autocorrect capitalization | a11ycheck code typography wordcount addtemplate' },
  27080. table: { title: 'Table', items: 'inserttable | cell row column | advtablesort | tableprops deletetable' },
  27081. help: { title: 'Help', items: 'help' }
  27082. };
  27083. const make = (menu, registry, editor) => {
  27084. const removedMenuItems = getRemovedMenuItems(editor).split(/[ ,]/);
  27085. return {
  27086. text: menu.title,
  27087. getItems: () => bind$3(menu.items, (i) => {
  27088. const itemName = i.toLowerCase();
  27089. if (itemName.trim().length === 0) {
  27090. return [];
  27091. }
  27092. else if (exists(removedMenuItems, (removedMenuItem) => removedMenuItem === itemName)) {
  27093. return [];
  27094. }
  27095. else if (itemName === 'separator' || itemName === '|') {
  27096. return [{
  27097. type: 'separator'
  27098. }];
  27099. }
  27100. else if (registry.menuItems[itemName]) {
  27101. return [registry.menuItems[itemName]];
  27102. }
  27103. else {
  27104. return [];
  27105. }
  27106. })
  27107. };
  27108. };
  27109. const parseItemsString = (items) => {
  27110. return items.split(' ');
  27111. };
  27112. const identifyMenus = (editor, registry) => {
  27113. const rawMenuData = { ...defaultMenus, ...registry.menus };
  27114. const userDefinedMenus = keys(registry.menus).length > 0;
  27115. const menubar = registry.menubar === undefined || registry.menubar === true ? parseItemsString(defaultMenubar) : parseItemsString(registry.menubar === false ? '' : registry.menubar);
  27116. const validMenus = filter$2(menubar, (menuName) => {
  27117. const isDefaultMenu = has$2(defaultMenus, menuName);
  27118. if (userDefinedMenus) {
  27119. return isDefaultMenu || get$h(registry.menus, menuName).exists((menu) => has$2(menu, 'items'));
  27120. }
  27121. else {
  27122. return isDefaultMenu;
  27123. }
  27124. });
  27125. const menus = map$2(validMenus, (menuName) => {
  27126. const menuData = rawMenuData[menuName];
  27127. return make({ title: menuData.title, items: parseItemsString(menuData.items) }, registry, editor);
  27128. });
  27129. return filter$2(menus, (menu) => {
  27130. // Filter out menus that have no items, or only separators
  27131. const isNotSeparator = (item) => isString(item) || item.type !== 'separator';
  27132. return menu.getItems().length > 0 && exists(menu.getItems(), isNotSeparator);
  27133. });
  27134. };
  27135. const fireSkinLoaded = (editor) => {
  27136. const done = () => {
  27137. editor._skinLoaded = true;
  27138. fireSkinLoaded$1(editor);
  27139. };
  27140. return () => {
  27141. if (editor.initialized) {
  27142. done();
  27143. }
  27144. else {
  27145. editor.on('init', done);
  27146. }
  27147. };
  27148. };
  27149. const fireSkinLoadError = (editor, err) => () => fireSkinLoadError$1(editor, { message: err });
  27150. const getSkinResourceIdentifier = (editor) => {
  27151. const skin = getSkin(editor);
  27152. // Use falsy check to cover false, undefined/null and empty string
  27153. if (!skin) {
  27154. return Optional.none();
  27155. }
  27156. else {
  27157. return Optional.from(skin);
  27158. }
  27159. };
  27160. const loadStylesheet = (editor, stylesheetUrl, styleSheetLoader) => {
  27161. // Ensure the stylesheet is cleaned up when the editor is destroyed
  27162. editor.on('remove', () => styleSheetLoader.unload(stylesheetUrl));
  27163. return styleSheetLoader.load(stylesheetUrl);
  27164. };
  27165. const loadRawCss = (editor, key, css, styleSheetLoader) => {
  27166. // Ensure the stylesheet is cleaned up when the editor is destroyed
  27167. editor.on('remove', () => styleSheetLoader.unloadRawCss(key));
  27168. return styleSheetLoader.loadRawCss(key, css);
  27169. };
  27170. const skinIdentifierToResourceKey = (identifier, filename) => 'ui/' + identifier + '/' + filename;
  27171. const getResourceValue = (resourceKey) => Optional.from(tinymce.Resource.get(resourceKey)).filter(isString);
  27172. const determineCSSDecision = (editor, filenameBase, skinUrl = '') => {
  27173. const resourceKey = getSkinResourceIdentifier(editor)
  27174. .map((identifier) => skinIdentifierToResourceKey(identifier, `${filenameBase}.css`));
  27175. const resourceValue = resourceKey.bind(getResourceValue);
  27176. return lift2(resourceKey, resourceValue, (key, css) => {
  27177. return { _kind: 'load-raw', key, css };
  27178. }).getOrThunk(() => {
  27179. const suffix = editor.editorManager.suffix;
  27180. const skinUiCssUrl = skinUrl + `/${filenameBase}${suffix}.css`;
  27181. return { _kind: 'load-stylesheet', url: skinUiCssUrl };
  27182. });
  27183. };
  27184. const loadUiSkins = (editor, skinUrl) => {
  27185. const loader = editor.ui.styleSheetLoader;
  27186. const decision = determineCSSDecision(editor, 'skin', skinUrl);
  27187. switch (decision._kind) {
  27188. case 'load-raw':
  27189. const { key, css } = decision;
  27190. loadRawCss(editor, key, css, loader);
  27191. return Promise.resolve();
  27192. case 'load-stylesheet':
  27193. const { url } = decision;
  27194. return loadStylesheet(editor, url, loader);
  27195. default:
  27196. return Promise.resolve();
  27197. }
  27198. };
  27199. const loadShadowDomUiSkins = (editor, skinUrl) => {
  27200. const isInShadowRoot$1 = isInShadowRoot(SugarElement.fromDom(editor.getElement()));
  27201. if (!isInShadowRoot$1) {
  27202. return Promise.resolve();
  27203. }
  27204. else {
  27205. const loader = global$9.DOM.styleSheetLoader;
  27206. const decision = determineCSSDecision(editor, 'skin.shadowdom', skinUrl);
  27207. switch (decision._kind) {
  27208. case 'load-raw':
  27209. const { key, css } = decision;
  27210. loadRawCss(editor, key, css, loader);
  27211. return Promise.resolve();
  27212. case 'load-stylesheet':
  27213. const { url } = decision;
  27214. return loadStylesheet(editor, url, loader);
  27215. default:
  27216. return Promise.resolve();
  27217. }
  27218. }
  27219. };
  27220. const loadUiContentCSS = (editor, isInline, skinUrl) => {
  27221. const filenameBase = isInline ? 'content.inline' : 'content';
  27222. const decision = determineCSSDecision(editor, filenameBase, skinUrl);
  27223. switch (decision._kind) {
  27224. case 'load-raw':
  27225. const { key, css } = decision;
  27226. if (isInline) {
  27227. loadRawCss(editor, key, css, editor.ui.styleSheetLoader);
  27228. }
  27229. else {
  27230. // Need to wait until the iframe is in the DOM before trying to load
  27231. // the style into the iframe document
  27232. editor.on('PostRender', () => {
  27233. loadRawCss(editor, key, css, editor.dom.styleSheetLoader);
  27234. });
  27235. }
  27236. return Promise.resolve();
  27237. case 'load-stylesheet':
  27238. const { url } = decision;
  27239. if (skinUrl) {
  27240. editor.contentCSS.push(url);
  27241. }
  27242. return Promise.resolve();
  27243. default:
  27244. return Promise.resolve();
  27245. }
  27246. };
  27247. const loadUrlSkin = async (isInline, editor) => {
  27248. const skinUrl = getSkinUrl(editor);
  27249. await loadUiContentCSS(editor, isInline, skinUrl);
  27250. // In Modern Inline, this is explicitly called in editor.on('focus', ...) as well as in render().
  27251. // Seems to work without, but adding a note in case things break later
  27252. if (!isSkinDisabled(editor) && isString(skinUrl)) {
  27253. return Promise.all([
  27254. loadUiSkins(editor, skinUrl),
  27255. loadShadowDomUiSkins(editor, skinUrl)
  27256. ]).then();
  27257. }
  27258. };
  27259. const loadSkin = (isInline, editor) => {
  27260. return loadUrlSkin(isInline, editor).then(fireSkinLoaded(editor), fireSkinLoadError(editor, 'Skin could not be loaded'));
  27261. };
  27262. const iframe = curry(loadSkin, false);
  27263. const inline = curry(loadSkin, true);
  27264. const getButtonApi = (component) => ({
  27265. isEnabled: () => !Disabling.isDisabled(component),
  27266. setEnabled: (state) => Disabling.set(component, !state),
  27267. setText: (text) => emitWith(component, updateMenuText, {
  27268. text
  27269. }),
  27270. setIcon: (icon) => emitWith(component, updateMenuIcon, {
  27271. icon
  27272. })
  27273. });
  27274. const getToggleApi = (component) => ({
  27275. setActive: (state) => {
  27276. Toggling.set(component, state);
  27277. },
  27278. isActive: () => Toggling.isOn(component),
  27279. isEnabled: () => !Disabling.isDisabled(component),
  27280. setEnabled: (state) => Disabling.set(component, !state),
  27281. setText: (text) => emitWith(component, updateMenuText, {
  27282. text
  27283. }),
  27284. setIcon: (icon) => emitWith(component, updateMenuIcon, {
  27285. icon
  27286. })
  27287. });
  27288. const getTooltipAttributes = (tooltip, providersBackstage) => tooltip.map((tooltip) => ({
  27289. 'aria-label': providersBackstage.translate(tooltip),
  27290. })).getOr({});
  27291. const focusButtonEvent = generate$6('focus-button');
  27292. const renderCommonStructure = (optIcon, optText, tooltip, behaviours, providersBackstage, context, btnName) => {
  27293. const optMemDisplayText = optText.map((text) => record(renderLabel$1(text, "tox-tbtn" /* ToolbarButtonClasses.Button */, providersBackstage)));
  27294. const optMemDisplayIcon = optIcon.map((icon) => record(renderReplaceableIconFromPack(icon, providersBackstage.icons)));
  27295. return {
  27296. dom: {
  27297. tag: 'button',
  27298. classes: ["tox-tbtn" /* ToolbarButtonClasses.Button */].concat(optText.isSome() ? ["tox-tbtn--select" /* ToolbarButtonClasses.MatchWidth */] : []),
  27299. attributes: {
  27300. ...getTooltipAttributes(tooltip, providersBackstage),
  27301. ...(isNonNullable(btnName) ? { 'data-mce-name': btnName } : {})
  27302. }
  27303. },
  27304. components: componentRenderPipeline([
  27305. optMemDisplayIcon.map((mem) => mem.asSpec()),
  27306. optMemDisplayText.map((mem) => mem.asSpec()),
  27307. ]),
  27308. eventOrder: {
  27309. [mousedown()]: [
  27310. 'focusing',
  27311. 'alloy.base.behaviour',
  27312. commonButtonDisplayEvent
  27313. ],
  27314. [attachedToDom()]: [commonButtonDisplayEvent, 'toolbar-group-button-events'],
  27315. [detachedFromDom()]: [commonButtonDisplayEvent, 'toolbar-group-button-events', 'tooltipping']
  27316. },
  27317. buttonBehaviours: derive$1([
  27318. DisablingConfigs.toolbarButton(() => providersBackstage.checkUiComponentContext(context).shouldDisable),
  27319. toggleOnReceive(() => providersBackstage.checkUiComponentContext(context)),
  27320. config(commonButtonDisplayEvent, [
  27321. runOnAttached((comp, _se) => forceInitialSize(comp)),
  27322. run$1(updateMenuText, (comp, se) => {
  27323. optMemDisplayText.bind((mem) => mem.getOpt(comp)).each((displayText) => {
  27324. Replacing.set(displayText, [text$2(providersBackstage.translate(se.event.text))]);
  27325. });
  27326. }),
  27327. run$1(updateMenuIcon, (comp, se) => {
  27328. optMemDisplayIcon.bind((mem) => mem.getOpt(comp)).each((displayIcon) => {
  27329. Replacing.set(displayIcon, [renderReplaceableIconFromPack(se.event.icon, providersBackstage.icons)]);
  27330. });
  27331. }),
  27332. run$1(mousedown(), (button, se) => {
  27333. se.event.prevent();
  27334. emit(button, focusButtonEvent);
  27335. })
  27336. ])
  27337. ].concat(behaviours.getOr([])))
  27338. };
  27339. };
  27340. const renderFloatingToolbarButton = (spec, backstage, identifyButtons, attributes, btnName) => {
  27341. const sharedBackstage = backstage.shared;
  27342. const editorOffCell = Cell(noop);
  27343. const specialisation = {
  27344. toolbarButtonBehaviours: [],
  27345. getApi: getButtonApi,
  27346. onSetup: spec.onSetup
  27347. };
  27348. const behaviours = [
  27349. config('toolbar-group-button-events', [
  27350. onControlAttached(specialisation, editorOffCell),
  27351. onControlDetached(specialisation, editorOffCell)
  27352. ]),
  27353. ...(spec.tooltip.map((t) => Tooltipping.config(backstage.shared.providers.tooltips.getConfig({
  27354. tooltipText: backstage.shared.providers.translate(t),
  27355. })))).toArray()
  27356. ];
  27357. return FloatingToolbarButton.sketch({
  27358. lazySink: sharedBackstage.getSink,
  27359. fetch: () => Future.nu((resolve) => {
  27360. resolve(map$2(identifyButtons(spec.items), renderToolbarGroup));
  27361. }),
  27362. markers: {
  27363. toggledClass: "tox-tbtn--enabled" /* ToolbarButtonClasses.Ticked */
  27364. },
  27365. parts: {
  27366. button: renderCommonStructure(spec.icon, spec.text, spec.tooltip, Optional.some(behaviours), sharedBackstage.providers, spec.context, btnName),
  27367. toolbar: {
  27368. dom: {
  27369. tag: 'div',
  27370. classes: ['tox-toolbar__overflow'],
  27371. attributes
  27372. }
  27373. }
  27374. }
  27375. });
  27376. };
  27377. const renderCommonToolbarButton = (spec, specialisation, providersBackstage, btnName) => {
  27378. var _a;
  27379. const editorOffCell = Cell(noop);
  27380. const structure = renderCommonStructure(spec.icon, spec.text, spec.tooltip, Optional.none(), providersBackstage, spec.context, btnName);
  27381. return Button.sketch({
  27382. dom: structure.dom,
  27383. components: structure.components,
  27384. eventOrder: toolbarButtonEventOrder,
  27385. buttonBehaviours: {
  27386. ...derive$1([
  27387. config('toolbar-button-events', [
  27388. onToolbarButtonExecute({
  27389. onAction: spec.onAction,
  27390. getApi: specialisation.getApi
  27391. }),
  27392. onControlAttached(specialisation, editorOffCell),
  27393. onControlDetached(specialisation, editorOffCell)
  27394. ]),
  27395. ...(spec.tooltip.map((t) => Tooltipping.config(providersBackstage.tooltips.getConfig({
  27396. tooltipText: providersBackstage.translate(t) + spec.shortcut.map((shortcut) => ` (${convertText(shortcut)})`).getOr(''),
  27397. })))).toArray(),
  27398. // Enable toolbar buttons by default
  27399. DisablingConfigs.toolbarButton(() => !spec.enabled || providersBackstage.checkUiComponentContext(spec.context).shouldDisable),
  27400. toggleOnReceive(() => providersBackstage.checkUiComponentContext(spec.context))
  27401. ].concat(specialisation.toolbarButtonBehaviours)),
  27402. // Here we add the commonButtonDisplayEvent behaviour from the structure so we can listen
  27403. // to updateMenuIcon and updateMenuText events and run the defined callbacks as they are
  27404. // defined in the renderCommonStructure function and fix the size of the button onAttached.
  27405. [commonButtonDisplayEvent]: (_a = structure.buttonBehaviours) === null || _a === void 0 ? void 0 : _a[commonButtonDisplayEvent],
  27406. }
  27407. });
  27408. };
  27409. const renderToolbarButton = (spec, providersBackstage, btnName) => renderToolbarButtonWith(spec, providersBackstage, [], btnName);
  27410. const renderToolbarButtonWith = (spec, providersBackstage, bonusEvents, btnName) => renderCommonToolbarButton(spec, {
  27411. toolbarButtonBehaviours: (bonusEvents.length > 0 ? [
  27412. // TODO: May have to pass through eventOrder if events start clashing
  27413. config('toolbarButtonWith', bonusEvents)
  27414. ] : []),
  27415. getApi: getButtonApi,
  27416. onSetup: spec.onSetup
  27417. }, providersBackstage, btnName);
  27418. const renderToolbarToggleButton = (spec, providersBackstage, btnName) => renderToolbarToggleButtonWith(spec, providersBackstage, [], btnName);
  27419. const renderToolbarToggleButtonWith = (spec, providersBackstage, bonusEvents, btnName) => renderCommonToolbarButton(spec, {
  27420. toolbarButtonBehaviours: [
  27421. Replacing.config({}),
  27422. Toggling.config({ toggleClass: "tox-tbtn--enabled" /* ToolbarButtonClasses.Ticked */, aria: { mode: 'pressed' }, toggleOnExecute: false })
  27423. ].concat(bonusEvents.length > 0 ? [
  27424. // TODO: May have to pass through eventOrder if events start clashing
  27425. config('toolbarToggleButtonWith', bonusEvents)
  27426. ] : []),
  27427. getApi: getToggleApi,
  27428. onSetup: spec.onSetup
  27429. }, providersBackstage, btnName);
  27430. const fetchChoices = (getApi, spec, providersBackstage) => (comp) => Future.nu((callback) => spec.fetch(callback))
  27431. .map((items) => Optional.from(createTieredDataFrom(deepMerge(createPartialChoiceMenu(generate$6('menu-value'), items, (value) => {
  27432. spec.onItemAction(getApi(comp), value);
  27433. }, spec.columns, spec.presets, ItemResponse$1.CLOSE_ON_EXECUTE, spec.select.getOr(never), providersBackstage), {
  27434. movement: deriveMenuMovement(spec.columns, spec.presets),
  27435. menuBehaviours: SimpleBehaviours.unnamedEvents(spec.columns !== 'auto' ? [] : [
  27436. runOnAttached((comp, _se) => {
  27437. detectSize(comp, 4, classForPreset(spec.presets)).each(({ numRows, numColumns }) => {
  27438. Keying.setGridSize(comp, numRows, numColumns);
  27439. });
  27440. })
  27441. ])
  27442. }))));
  27443. const makeSplitButtonApi = (tooltipString, sharedBackstage, spec) => (component) => {
  27444. const system = component.getSystem();
  27445. const element = component.element;
  27446. const getComponents = () => {
  27447. const isChevron = has(element, 'tox-split-button__chevron');
  27448. const mainOpt = isChevron ?
  27449. prevSibling(element).bind((el) => system.getByDom(el).toOptional()) :
  27450. Optional.some(component);
  27451. const chevronOpt = isChevron ?
  27452. Optional.some(component) :
  27453. nextSibling(element).bind((el) => system.getByDom(el).toOptional().filter((comp) => has(comp.element, 'tox-split-button__chevron')));
  27454. return { mainOpt, chevronOpt };
  27455. };
  27456. const applyBoth = (f) => {
  27457. const { mainOpt, chevronOpt } = getComponents();
  27458. mainOpt.each(f);
  27459. chevronOpt.each(f);
  27460. };
  27461. return {
  27462. isEnabled: () => {
  27463. const { mainOpt } = getComponents();
  27464. return mainOpt.exists((c) => !Disabling.isDisabled(c));
  27465. },
  27466. setEnabled: (state) => applyBoth((c) => Disabling.set(c, !state)),
  27467. setText: (text) => {
  27468. const { mainOpt } = getComponents();
  27469. mainOpt.each((c) => emitWith(c, updateMenuText, { text }));
  27470. },
  27471. setIcon: (icon) => {
  27472. const { mainOpt } = getComponents();
  27473. mainOpt.each((c) => emitWith(c, updateMenuIcon, { icon }));
  27474. },
  27475. setIconFill: (id, value) => applyBoth((c) => {
  27476. descendant(c.element, `svg path[class="${id}"], rect[class="${id}"]`).each((underlinePath) => {
  27477. set$9(underlinePath, 'fill', value);
  27478. });
  27479. }),
  27480. isActive: () => {
  27481. const { mainOpt } = getComponents();
  27482. return mainOpt.exists((c) => Toggling.isOn(c));
  27483. },
  27484. setActive: (state) => {
  27485. const { mainOpt } = getComponents();
  27486. mainOpt.each((c) => Toggling.set(c, state));
  27487. },
  27488. setTooltip: (tooltip) => {
  27489. tooltipString.set(tooltip);
  27490. const { mainOpt, chevronOpt } = getComponents();
  27491. mainOpt.each((c) => set$9(c.element, 'aria-label', sharedBackstage.providers.translate(tooltip)));
  27492. // For chevron, use the explicit chevronTooltip if provided, otherwise fall back to default behavior
  27493. const chevronTooltipText = spec.chevronTooltip
  27494. .map((chevronTooltip) => sharedBackstage.providers.translate(chevronTooltip))
  27495. .getOr(sharedBackstage.providers.translate(`${tooltip} menu`));
  27496. chevronOpt.each((c) => set$9(c.element, 'aria-label', chevronTooltipText));
  27497. }
  27498. };
  27499. };
  27500. const renderSplitButton = (spec, sharedBackstage, btnName) => {
  27501. const editorOffCell = Cell(noop);
  27502. const tooltipString = Cell(spec.tooltip.getOr(''));
  27503. const getApi = makeSplitButtonApi(tooltipString, sharedBackstage, spec);
  27504. const menuId = generate$6('tox-split-menu');
  27505. const expandedCell = Cell(false);
  27506. const getAriaAttributes = () => ({
  27507. 'aria-haspopup': 'menu',
  27508. 'aria-expanded': String(expandedCell.get()),
  27509. 'aria-controls': menuId
  27510. });
  27511. // Helper to get ARIA label for the main button
  27512. const getMainButtonAriaLabel = () => {
  27513. return spec.tooltip.map((tooltip) => sharedBackstage.providers.translate(tooltip))
  27514. .getOr(sharedBackstage.providers.translate('Text color'));
  27515. };
  27516. // Helper to get ARIA label and tooltip for the chevron/dropdown button
  27517. const getChevronTooltip = () => {
  27518. return spec.chevronTooltip
  27519. .map((tooltip) => sharedBackstage.providers.translate(tooltip))
  27520. .getOrThunk(() => {
  27521. const mainLabel = getMainButtonAriaLabel();
  27522. return sharedBackstage.providers.translate(['{0} menu', mainLabel]);
  27523. });
  27524. };
  27525. const updateAriaExpanded = (expanded, comp) => {
  27526. expandedCell.set(expanded);
  27527. set$9(comp.element, 'aria-expanded', String(expanded));
  27528. };
  27529. const arrow = Dropdown.sketch({
  27530. dom: {
  27531. tag: 'button',
  27532. classes: ["tox-tbtn" /* ToolbarButtonClasses.Button */, 'tox-split-button__chevron'],
  27533. innerHtml: get('chevron-down', sharedBackstage.providers.icons),
  27534. attributes: {
  27535. 'aria-label': getChevronTooltip(),
  27536. ...(isNonNullable(btnName) ? { 'data-mce-name': btnName + '-chevron' } : {}),
  27537. ...getAriaAttributes()
  27538. }
  27539. },
  27540. components: [],
  27541. toggleClass: "tox-tbtn--enabled" /* ToolbarButtonClasses.Ticked */,
  27542. dropdownBehaviours: derive$1([
  27543. config('split-dropdown-events', [
  27544. runOnAttached((comp, _se) => forceInitialSize(comp)),
  27545. onControlAttached({ getApi, onSetup: spec.onSetup }, editorOffCell),
  27546. run$1('alloy-dropdown-open', (comp) => updateAriaExpanded(true, comp)),
  27547. run$1('alloy-dropdown-close', (comp) => updateAriaExpanded(false, comp)),
  27548. ]),
  27549. DisablingConfigs.toolbarButton(() => sharedBackstage.providers.checkUiComponentContext(spec.context).shouldDisable),
  27550. toggleOnReceive(() => sharedBackstage.providers.checkUiComponentContext(spec.context)),
  27551. Unselecting.config({}),
  27552. Tooltipping.config(sharedBackstage.providers.tooltips.getConfig({
  27553. tooltipText: getChevronTooltip(),
  27554. onShow: (comp) => {
  27555. if (tooltipString.get() !== spec.tooltip.getOr('')) {
  27556. const chevronTooltipText = spec.chevronTooltip
  27557. .map((chevronTooltip) => sharedBackstage.providers.translate(chevronTooltip))
  27558. .getOr(`${sharedBackstage.providers.translate(tooltipString.get())} menu`);
  27559. Tooltipping.setComponents(comp, sharedBackstage.providers.tooltips.getComponents({ tooltipText: chevronTooltipText }));
  27560. }
  27561. }
  27562. }))
  27563. ]),
  27564. lazySink: sharedBackstage.getSink,
  27565. fetch: fetchChoices(getApi, spec, sharedBackstage.providers),
  27566. getHotspot: (comp) => prevSibling(comp.element).bind((el) => comp.getSystem().getByDom(el).toOptional()),
  27567. onOpen: (_anchor, _comp, menu) => {
  27568. Highlighting.highlightBy(menu, (item) => has(item.element, 'tox-collection__item--active'));
  27569. Highlighting.getHighlighted(menu).each(Keying.focusIn);
  27570. },
  27571. parts: {
  27572. menu: {
  27573. ...part(false, spec.columns, spec.presets),
  27574. dom: {
  27575. ...part(false, spec.columns, spec.presets).dom,
  27576. tag: 'div',
  27577. attributes: {
  27578. id: menuId
  27579. }
  27580. }
  27581. }
  27582. }
  27583. });
  27584. const mainButton = Button.sketch({
  27585. ...renderCommonStructure(spec.icon, spec.text, Optional.none(), Optional.some([
  27586. Toggling.config({
  27587. toggleClass: "tox-tbtn--enabled" /* ToolbarButtonClasses.Ticked */,
  27588. aria: spec.presets === 'color' ? { mode: 'none' } : { mode: 'pressed' },
  27589. toggleOnExecute: false
  27590. }),
  27591. DisablingConfigs.toolbarButton(() => sharedBackstage.providers.checkUiComponentContext(spec.context).shouldDisable),
  27592. toggleOnReceive(() => sharedBackstage.providers.checkUiComponentContext(spec.context)),
  27593. config('split-main-aria-events', []),
  27594. ...(spec.tooltip.isSome() ? [
  27595. Tooltipping.config(sharedBackstage.providers.tooltips.getConfig({
  27596. tooltipText: sharedBackstage.providers.translate(spec.tooltip.getOr('')),
  27597. onShow: (comp) => {
  27598. if (tooltipString.get() !== spec.tooltip.getOr('')) {
  27599. const translated = sharedBackstage.providers.translate(tooltipString.get());
  27600. Tooltipping.setComponents(comp, sharedBackstage.providers.tooltips.getComponents({ tooltipText: translated }));
  27601. }
  27602. }
  27603. }))
  27604. ] : [])
  27605. ]), sharedBackstage.providers, spec.context, btnName),
  27606. dom: {
  27607. ...renderCommonStructure(spec.icon, spec.text, Optional.none(), Optional.none(), sharedBackstage.providers, spec.context, btnName).dom,
  27608. classes: ["tox-tbtn" /* ToolbarButtonClasses.Button */, 'tox-split-button__main'],
  27609. attributes: {
  27610. 'aria-label': getMainButtonAriaLabel(),
  27611. ...(isNonNullable(btnName) ? { 'data-mce-name': btnName } : {})
  27612. }
  27613. },
  27614. action: (button) => {
  27615. if (spec.onAction) {
  27616. const api = getApi(button);
  27617. if (api.isEnabled()) {
  27618. spec.onAction(api);
  27619. }
  27620. }
  27621. }
  27622. });
  27623. return [mainButton, arrow];
  27624. };
  27625. const contextFormInputSelector = '.tox-toolbar-slider__input,.tox-toolbar-textfield';
  27626. const focusIn = (contextbar) => {
  27627. InlineView.getContent(contextbar).each((comp) => {
  27628. descendant(comp.element, contextFormInputSelector).fold(() => Keying.focusIn(comp), focus$4);
  27629. });
  27630. };
  27631. // TODO: Is this really the best way to move focus out of the input when it gets disabled #TINY-11527
  27632. const focusParent = (comp) => search(comp.element).each((focus) => {
  27633. ancestor$1(focus, '[tabindex="-1"]').each((parent) => {
  27634. focus$4(parent);
  27635. });
  27636. });
  27637. const forwardSlideEvent = generate$6('forward-slide');
  27638. const backSlideEvent = generate$6('backward-slide');
  27639. const changeSlideEvent = generate$6('change-slide-event');
  27640. const resizingClass = 'tox-pop--resizing';
  27641. const renderContextToolbar = (spec) => {
  27642. const stack = Cell([]);
  27643. const sketch = InlineView.sketch({
  27644. dom: {
  27645. tag: 'div',
  27646. classes: ['tox-pop']
  27647. },
  27648. fireDismissalEventInstead: {
  27649. event: 'doNotDismissYet'
  27650. },
  27651. onShow: (comp) => {
  27652. stack.set([]);
  27653. InlineView.getContent(comp).each((c) => {
  27654. remove$6(c.element, 'visibility');
  27655. });
  27656. remove$3(comp.element, resizingClass);
  27657. remove$6(comp.element, 'width');
  27658. },
  27659. onHide: () => {
  27660. stack.set([]);
  27661. spec.onHide();
  27662. },
  27663. inlineBehaviours: derive$1([
  27664. config('context-toolbar-events', [
  27665. runOnSource(transitionend(), (comp, se) => {
  27666. if (se.event.raw.propertyName === 'width') {
  27667. remove$3(comp.element, resizingClass);
  27668. remove$6(comp.element, 'width');
  27669. }
  27670. }),
  27671. run$1(changeSlideEvent, (comp, se) => {
  27672. const elem = comp.element;
  27673. // If it was partially through a slide, clear that and measure afresh
  27674. remove$6(elem, 'width');
  27675. const currentWidth = get$c(elem);
  27676. const hadFocus = search(comp.element).isSome();
  27677. // Remove these so that we can property measure the width of the context form content
  27678. remove$6(elem, 'left');
  27679. remove$6(elem, 'right');
  27680. remove$6(elem, 'max-width');
  27681. InlineView.setContent(comp, se.event.contents);
  27682. add$2(elem, resizingClass);
  27683. const newWidth = get$c(elem);
  27684. // Reposition without transition to avoid it from being animated from previous position
  27685. set$7(elem, 'transition', 'none');
  27686. InlineView.reposition(comp);
  27687. remove$6(elem, 'transition');
  27688. set$7(elem, 'width', currentWidth + 'px');
  27689. se.event.focus.fold(() => {
  27690. if (hadFocus) {
  27691. focusIn(comp);
  27692. }
  27693. }, (f) => {
  27694. active$1(getRootNode(comp.element)).fold(() => focus$4(f), (active) => {
  27695. // We need this extra check since if the focus is aleady on the iframe we don't want to call focus on it again since that closes the context toolbar
  27696. if (!eq(active, f)) {
  27697. spec.focusElement(f);
  27698. }
  27699. });
  27700. });
  27701. setTimeout(() => {
  27702. set$7(comp.element, 'width', newWidth + 'px');
  27703. }, 0);
  27704. }),
  27705. run$1(forwardSlideEvent, (comp, se) => {
  27706. InlineView.getContent(comp).each((oldContents) => {
  27707. stack.set(stack.get().concat([
  27708. {
  27709. bar: oldContents,
  27710. focus: active$1(getRootNode(comp.element))
  27711. }
  27712. ]));
  27713. });
  27714. emitWith(comp, changeSlideEvent, {
  27715. contents: se.event.forwardContents,
  27716. focus: Optional.none()
  27717. });
  27718. }),
  27719. run$1(backSlideEvent, (comp, _se) => {
  27720. spec.onBack();
  27721. last$1(stack.get()).each((last) => {
  27722. stack.set(stack.get().slice(0, stack.get().length - 1));
  27723. emitWith(comp, changeSlideEvent, {
  27724. // Because we are using premade, we should have access to the same element
  27725. // to give focus (although it isn't working)
  27726. contents: premade(last.bar),
  27727. focus: last.focus
  27728. });
  27729. });
  27730. })
  27731. ]),
  27732. Keying.config({
  27733. mode: 'special',
  27734. onEscape: (comp) => last$1(stack.get()).fold(() =>
  27735. // Escape just focuses the content. It no longer closes the toolbar.
  27736. spec.onEscape(), (_) => {
  27737. emit(comp, backSlideEvent);
  27738. return Optional.some(true);
  27739. })
  27740. })
  27741. ]),
  27742. lazySink: () => Result.value(spec.sink)
  27743. });
  27744. return {
  27745. sketch,
  27746. inSubtoolbar: () => stack.get().length > 0
  27747. };
  27748. };
  27749. const createNavigateBackButton = (editor, backstage) => {
  27750. const bridged = getOrDie(createToolbarButton({
  27751. type: 'button',
  27752. icon: 'chevron-left',
  27753. tooltip: 'Back',
  27754. onAction: noop
  27755. }));
  27756. return renderToolbarButtonWith(bridged, backstage.shared.providers, [
  27757. run$1(internalToolbarButtonExecute, (comp) => {
  27758. emit(comp, backSlideEvent);
  27759. })
  27760. ]);
  27761. };
  27762. const makeTooltipText = (editor, labelWithPlaceholder, value) => isEmpty(value) ? editor.translate(labelWithPlaceholder) : editor.translate([labelWithPlaceholder, editor.translate(value)]);
  27763. const generateSelectItems = (backstage, spec) => {
  27764. const generateItem = (rawItem, response, invalid, value) => {
  27765. const translatedText = backstage.shared.providers.translate(rawItem.title);
  27766. if (rawItem.type === 'separator') {
  27767. return Optional.some({
  27768. type: 'separator',
  27769. text: translatedText
  27770. });
  27771. }
  27772. else if (rawItem.type === 'submenu') {
  27773. const items = bind$3(rawItem.getStyleItems(), (si) => validate(si, response, value));
  27774. if (response === 0 /* IrrelevantStyleItemResponse.Hide */ && items.length <= 0) {
  27775. return Optional.none();
  27776. }
  27777. else {
  27778. return Optional.some({
  27779. type: 'nestedmenuitem',
  27780. text: translatedText,
  27781. enabled: items.length > 0,
  27782. getSubmenuItems: () => bind$3(rawItem.getStyleItems(), (si) => validate(si, response, value))
  27783. });
  27784. }
  27785. }
  27786. else {
  27787. return Optional.some({
  27788. // ONLY TOGGLEMENUITEMS HANDLE STYLE META.
  27789. // See ToggleMenuItem and ItemStructure for how it's handled.
  27790. // If this type ever changes, we'll need to change that too
  27791. type: 'togglemenuitem',
  27792. text: translatedText,
  27793. icon: rawItem.icon,
  27794. active: rawItem.isSelected(value),
  27795. enabled: !invalid,
  27796. onAction: spec.onAction(rawItem),
  27797. ...rawItem.getStylePreview().fold(() => ({}), (preview) => ({ meta: { style: preview } }))
  27798. });
  27799. }
  27800. };
  27801. const validate = (item, response, value) => {
  27802. const invalid = item.type === 'formatter' && spec.isInvalid(item);
  27803. // If we are making them disappear based on some setting
  27804. if (response === 0 /* IrrelevantStyleItemResponse.Hide */) {
  27805. return invalid ? [] : generateItem(item, response, false, value).toArray();
  27806. }
  27807. else {
  27808. return generateItem(item, response, invalid, value).toArray();
  27809. }
  27810. };
  27811. const validateItems = (preItems) => {
  27812. const value = spec.getCurrentValue();
  27813. const response = spec.shouldHide ? 0 /* IrrelevantStyleItemResponse.Hide */ : 1 /* IrrelevantStyleItemResponse.Disable */;
  27814. return bind$3(preItems, (item) => validate(item, response, value));
  27815. };
  27816. const getFetch = (backstage, getStyleItems) => (comp, callback) => {
  27817. const preItems = getStyleItems();
  27818. const items = validateItems(preItems);
  27819. const menu = build(items, ItemResponse$1.CLOSE_ON_EXECUTE, backstage, {
  27820. isHorizontalMenu: false,
  27821. search: Optional.none()
  27822. });
  27823. callback(menu);
  27824. };
  27825. return {
  27826. validateItems,
  27827. getFetch
  27828. };
  27829. };
  27830. const createMenuItems = (backstage, spec) => {
  27831. const dataset = spec.dataset; // needs to be a var for tsc to understand the ternary
  27832. const getStyleItems = dataset.type === 'basic' ?
  27833. () => map$2(dataset.data, (d) => processBasic(d, spec.isSelectedFor, spec.getPreviewFor)) :
  27834. dataset.getData;
  27835. return {
  27836. items: generateSelectItems(backstage, spec),
  27837. getStyleItems
  27838. };
  27839. };
  27840. const createSelectButton = (editor, backstage, spec, getTooltip, textUpdateEventName, btnName) => {
  27841. const { items, getStyleItems } = createMenuItems(backstage, spec);
  27842. const tooltipString = Cell(spec.tooltip);
  27843. const getApi = (comp) => ({
  27844. getComponent: constant$1(comp),
  27845. setTooltip: (tooltip) => {
  27846. const translatedTooltip = backstage.shared.providers.translate(tooltip);
  27847. set$9(comp.element, 'aria-label', translatedTooltip);
  27848. tooltipString.set(tooltip);
  27849. }
  27850. });
  27851. // Set the initial text when the component is attached and then update on node changes
  27852. const onSetup = (api) => {
  27853. const handler = (e) => api.setTooltip(makeTooltipText(editor, getTooltip(e.value), e.value));
  27854. editor.on(textUpdateEventName, handler);
  27855. return composeUnbinders(onSetupEvent(editor, 'NodeChange', (api) => {
  27856. const comp = api.getComponent();
  27857. spec.updateText(comp);
  27858. Disabling.set(api.getComponent(), (!editor.selection.isEditable() || getStyleItems().length === 0));
  27859. })(api), () => editor.off(textUpdateEventName, handler));
  27860. };
  27861. return renderCommonDropdown({
  27862. context: 'mode:design',
  27863. text: spec.icon.isSome() ? Optional.none() : spec.text,
  27864. icon: spec.icon,
  27865. ariaLabel: Optional.some(spec.tooltip),
  27866. tooltip: Optional.none(), // TINY-10474 - Using own tooltip config
  27867. role: Optional.none(),
  27868. fetch: items.getFetch(backstage, getStyleItems),
  27869. onSetup,
  27870. getApi,
  27871. columns: 1,
  27872. presets: 'normal',
  27873. classes: spec.icon.isSome() ? [] : ['bespoke'],
  27874. dropdownBehaviours: [
  27875. Tooltipping.config({
  27876. ...backstage.shared.providers.tooltips.getConfig({
  27877. tooltipText: backstage.shared.providers.translate(spec.tooltip),
  27878. onShow: (comp) => {
  27879. if (spec.tooltip !== tooltipString.get()) {
  27880. const translatedTooltip = backstage.shared.providers.translate(tooltipString.get());
  27881. Tooltipping.setComponents(comp, backstage.shared.providers.tooltips.getComponents({ tooltipText: translatedTooltip }));
  27882. }
  27883. }
  27884. }),
  27885. })
  27886. ]
  27887. }, "tox-tbtn" /* ToolbarButtonClasses.Button */, backstage.shared, btnName);
  27888. };
  27889. const process = (rawFormats) => map$2(rawFormats, (item) => {
  27890. let title = item, format = item;
  27891. // Allow text=value block formats
  27892. const values = item.split('=');
  27893. if (values.length > 1) {
  27894. title = values[0];
  27895. format = values[1];
  27896. }
  27897. return { title, format };
  27898. });
  27899. const buildBasicStaticDataset = (data) => ({
  27900. type: 'basic',
  27901. data
  27902. });
  27903. var Delimiter;
  27904. (function (Delimiter) {
  27905. Delimiter[Delimiter["SemiColon"] = 0] = "SemiColon";
  27906. Delimiter[Delimiter["Space"] = 1] = "Space";
  27907. })(Delimiter || (Delimiter = {}));
  27908. const split = (rawFormats, delimiter) => {
  27909. if (delimiter === Delimiter.SemiColon) {
  27910. return rawFormats.replace(/;$/, '').split(';');
  27911. }
  27912. else {
  27913. return rawFormats.split(' ');
  27914. }
  27915. };
  27916. const buildBasicSettingsDataset = (editor, settingName, delimiter) => {
  27917. // eslint-disable-next-line @tinymce/no-direct-editor-options
  27918. const rawFormats = editor.options.get(settingName);
  27919. const data = process(split(rawFormats, delimiter));
  27920. return {
  27921. type: 'basic',
  27922. data
  27923. };
  27924. };
  27925. const menuTitle$4 = 'Align';
  27926. const getTooltipPlaceholder$4 = constant$1('Alignment {0}');
  27927. const fallbackAlignment = 'left';
  27928. const alignMenuItems = [
  27929. { title: 'Left', icon: 'align-left', format: 'alignleft', command: 'JustifyLeft' },
  27930. { title: 'Center', icon: 'align-center', format: 'aligncenter', command: 'JustifyCenter' },
  27931. { title: 'Right', icon: 'align-right', format: 'alignright', command: 'JustifyRight' },
  27932. { title: 'Justify', icon: 'align-justify', format: 'alignjustify', command: 'JustifyFull' }
  27933. ];
  27934. const getSpec$4 = (editor) => {
  27935. const getMatchingValue = () => find$5(alignMenuItems, (item) => editor.formatter.match(item.format));
  27936. const isSelectedFor = (format) => () => editor.formatter.match(format);
  27937. const getPreviewFor = (_format) => Optional.none;
  27938. const updateSelectMenuIcon = (comp) => {
  27939. const match = getMatchingValue();
  27940. const alignment = match.fold(constant$1(fallbackAlignment), (item) => item.title.toLowerCase());
  27941. emitWith(comp, updateMenuIcon, {
  27942. icon: `align-${alignment}`
  27943. });
  27944. fireAlignTextUpdate(editor, { value: alignment });
  27945. };
  27946. const dataset = buildBasicStaticDataset(alignMenuItems);
  27947. const onAction = (rawItem) => () => find$5(alignMenuItems, (item) => item.format === rawItem.format)
  27948. .each((item) => editor.execCommand(item.command));
  27949. return {
  27950. tooltip: makeTooltipText(editor, getTooltipPlaceholder$4(), fallbackAlignment),
  27951. text: Optional.none(),
  27952. icon: Optional.some('align-left'),
  27953. isSelectedFor,
  27954. getCurrentValue: Optional.none,
  27955. getPreviewFor,
  27956. onAction,
  27957. updateText: updateSelectMenuIcon,
  27958. dataset,
  27959. shouldHide: false,
  27960. isInvalid: (item) => !editor.formatter.canApply(item.format)
  27961. };
  27962. };
  27963. const createAlignButton = (editor, backstage) => createSelectButton(editor, backstage, getSpec$4(editor), getTooltipPlaceholder$4, 'AlignTextUpdate', 'align');
  27964. const createAlignMenu = (editor, backstage) => {
  27965. const menuItems = createMenuItems(backstage, getSpec$4(editor));
  27966. editor.ui.registry.addNestedMenuItem('align', {
  27967. text: backstage.shared.providers.translate(menuTitle$4),
  27968. onSetup: onSetupEditableToggle(editor),
  27969. getSubmenuItems: () => menuItems.items.validateItems(menuItems.getStyleItems())
  27970. });
  27971. };
  27972. const findNearest = (editor, getStyles) => {
  27973. const styles = getStyles();
  27974. const formats = map$2(styles, (style) => style.format);
  27975. return Optional.from(editor.formatter.closest(formats)).bind((fmt) => find$5(styles, (data) => data.format === fmt));
  27976. };
  27977. const menuTitle$3 = 'Blocks';
  27978. const getTooltipPlaceholder$3 = constant$1('Block {0}');
  27979. const fallbackFormat = 'Paragraph';
  27980. const getSpec$3 = (editor) => {
  27981. const isSelectedFor = (format) => () => editor.formatter.match(format);
  27982. const getPreviewFor = (format) => () => {
  27983. const fmt = editor.formatter.get(format);
  27984. if (fmt) {
  27985. return Optional.some({
  27986. tag: fmt.length > 0 ? fmt[0].inline || fmt[0].block || 'div' : 'div',
  27987. styles: editor.dom.parseStyle(editor.formatter.getCssText(format))
  27988. });
  27989. }
  27990. else {
  27991. return Optional.none();
  27992. }
  27993. };
  27994. const updateSelectMenuText = (comp) => {
  27995. const detectedFormat = findNearest(editor, () => dataset.data);
  27996. const text = detectedFormat.fold(constant$1(fallbackFormat), (fmt) => fmt.title);
  27997. emitWith(comp, updateMenuText, {
  27998. text
  27999. });
  28000. fireBlocksTextUpdate(editor, { value: text });
  28001. };
  28002. const dataset = buildBasicSettingsDataset(editor, 'block_formats', Delimiter.SemiColon);
  28003. return {
  28004. tooltip: makeTooltipText(editor, getTooltipPlaceholder$3(), fallbackFormat),
  28005. text: Optional.some(fallbackFormat),
  28006. icon: Optional.none(),
  28007. isSelectedFor,
  28008. getCurrentValue: Optional.none,
  28009. getPreviewFor,
  28010. onAction: onActionToggleFormat$1(editor),
  28011. updateText: updateSelectMenuText,
  28012. dataset,
  28013. shouldHide: false,
  28014. isInvalid: (item) => !editor.formatter.canApply(item.format)
  28015. };
  28016. };
  28017. const createBlocksButton = (editor, backstage) => createSelectButton(editor, backstage, getSpec$3(editor), getTooltipPlaceholder$3, 'BlocksTextUpdate', 'blocks');
  28018. // FIX: Test this!
  28019. const createBlocksMenu = (editor, backstage) => {
  28020. const menuItems = createMenuItems(backstage, getSpec$3(editor));
  28021. editor.ui.registry.addNestedMenuItem('blocks', {
  28022. text: menuTitle$3,
  28023. onSetup: onSetupEditableToggle(editor),
  28024. getSubmenuItems: () => menuItems.items.validateItems(menuItems.getStyleItems())
  28025. });
  28026. };
  28027. const menuTitle$2 = 'Fonts';
  28028. const getTooltipPlaceholder$2 = constant$1('Font {0}');
  28029. const systemFont = 'System Font';
  28030. // A list of fonts that must be in a font family for the font to be recognised as the system stack
  28031. // Note: Don't include 'BlinkMacSystemFont', as Chrome on Mac converts it to different names
  28032. // The system font stack will be similar to the following. (Note: each has minor variants)
  28033. // Oxide: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
  28034. // Bootstrap: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
  28035. // Wordpress: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
  28036. const systemStackFonts = ['-apple-system', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'sans-serif'];
  28037. // Split the fonts into an array and strip away any start/end quotes
  28038. const splitFonts = (fontFamily) => {
  28039. const fonts = fontFamily.split(/\s*,\s*/);
  28040. return map$2(fonts, (font) => font.replace(/^['"]+|['"]+$/g, ''));
  28041. };
  28042. const matchesStack = (fonts, stack) => stack.length > 0 && forall(stack, (font) => fonts.indexOf(font.toLowerCase()) > -1);
  28043. const isSystemFontStack = (fontFamily, userStack) => {
  28044. if (fontFamily.indexOf('-apple-system') === 0 || userStack.length > 0) {
  28045. const fonts = splitFonts(fontFamily.toLowerCase());
  28046. return matchesStack(fonts, systemStackFonts) || matchesStack(fonts, userStack);
  28047. }
  28048. else {
  28049. return false;
  28050. }
  28051. };
  28052. const getSpec$2 = (editor) => {
  28053. const getMatchingValue = () => {
  28054. const getFirstFont = (fontFamily) => fontFamily ? splitFonts(fontFamily)[0] : '';
  28055. const fontFamily = editor.queryCommandValue('FontName');
  28056. const items = dataset.data;
  28057. const font = fontFamily ? fontFamily.toLowerCase() : '';
  28058. const userStack = getDefaultFontStack(editor);
  28059. const matchOpt = find$5(items, (item) => {
  28060. const format = item.format;
  28061. return (format.toLowerCase() === font) || (getFirstFont(format).toLowerCase() === getFirstFont(font).toLowerCase());
  28062. }).orThunk(() => {
  28063. return someIf(isSystemFontStack(font, userStack), {
  28064. title: systemFont,
  28065. format: font
  28066. });
  28067. });
  28068. return { matchOpt, font: fontFamily };
  28069. };
  28070. const isSelectedFor = (item) => (valueOpt) => valueOpt.exists((value) => value.format === item);
  28071. const getCurrentValue = () => {
  28072. const { matchOpt } = getMatchingValue();
  28073. return matchOpt;
  28074. };
  28075. const getPreviewFor = (item) => () => Optional.some({
  28076. tag: 'div',
  28077. styles: item.indexOf('dings') === -1 ? { 'font-family': item } : {}
  28078. });
  28079. const onAction = (rawItem) => () => {
  28080. editor.undoManager.transact(() => {
  28081. editor.focus();
  28082. editor.execCommand('FontName', false, rawItem.format);
  28083. });
  28084. };
  28085. const updateSelectMenuText = (comp) => {
  28086. const { matchOpt, font } = getMatchingValue();
  28087. const text = matchOpt.fold(constant$1(font), (item) => item.title);
  28088. emitWith(comp, updateMenuText, {
  28089. text
  28090. });
  28091. fireFontFamilyTextUpdate(editor, { value: text });
  28092. };
  28093. const dataset = buildBasicSettingsDataset(editor, 'font_family_formats', Delimiter.SemiColon);
  28094. return {
  28095. tooltip: makeTooltipText(editor, getTooltipPlaceholder$2(), systemFont),
  28096. text: Optional.some(systemFont),
  28097. icon: Optional.none(),
  28098. isSelectedFor,
  28099. getCurrentValue,
  28100. getPreviewFor,
  28101. onAction,
  28102. updateText: updateSelectMenuText,
  28103. dataset,
  28104. shouldHide: false,
  28105. isInvalid: never
  28106. };
  28107. };
  28108. const createFontFamilyButton = (editor, backstage) => createSelectButton(editor, backstage, getSpec$2(editor), getTooltipPlaceholder$2, 'FontFamilyTextUpdate', 'fontfamily');
  28109. // TODO: Test this!
  28110. const createFontFamilyMenu = (editor, backstage) => {
  28111. const menuItems = createMenuItems(backstage, getSpec$2(editor));
  28112. editor.ui.registry.addNestedMenuItem('fontfamily', {
  28113. text: backstage.shared.providers.translate(menuTitle$2),
  28114. onSetup: onSetupEditableToggle(editor),
  28115. getSubmenuItems: () => menuItems.items.validateItems(menuItems.getStyleItems())
  28116. });
  28117. };
  28118. var global$1 = tinymce.util.Tools.resolve('tinymce.util.VK');
  28119. const createBespokeNumberInput = (editor, backstage, spec, btnName) => {
  28120. let currentComp = Optional.none();
  28121. const getValueFromCurrentComp = (comp) => comp.map((alloyComp) => Representing.getValue(alloyComp)).getOr('');
  28122. const onSetup = onSetupEvent(editor, 'NodeChange SwitchMode DisabledStateChange', (api) => {
  28123. const comp = api.getComponent();
  28124. currentComp = Optional.some(comp);
  28125. spec.updateInputValue(comp);
  28126. Disabling.set(comp, !editor.selection.isEditable() || isDisabled(editor));
  28127. });
  28128. const getApi = (comp) => ({ getComponent: constant$1(comp) });
  28129. const editorOffCell = Cell(noop);
  28130. const customEvents = generate$6('custom-number-input-events');
  28131. const changeValue = (f, fromInput, focusBack) => {
  28132. const text = getValueFromCurrentComp(currentComp);
  28133. const newValue = spec.getNewValue(text, f);
  28134. const lenghtDelta = text.length - `${newValue}`.length;
  28135. const oldStart = currentComp.map((comp) => comp.element.dom.selectionStart - lenghtDelta);
  28136. const oldEnd = currentComp.map((comp) => comp.element.dom.selectionEnd - lenghtDelta);
  28137. spec.onAction(newValue, focusBack);
  28138. currentComp.each((comp) => {
  28139. Representing.setValue(comp, newValue);
  28140. if (fromInput) {
  28141. oldStart.each((oldStart) => comp.element.dom.selectionStart = oldStart);
  28142. oldEnd.each((oldEnd) => comp.element.dom.selectionEnd = oldEnd);
  28143. }
  28144. });
  28145. };
  28146. const decrease = (fromInput, focusBack) => changeValue((n, s) => n - s, fromInput, focusBack);
  28147. const increase = (fromInput, focusBack) => changeValue((n, s) => n + s, fromInput, focusBack);
  28148. const goToParent = (comp) => parentElement(comp.element).fold(Optional.none, (parent) => {
  28149. focus$4(parent);
  28150. return Optional.some(true);
  28151. });
  28152. const focusInput = (comp) => {
  28153. if (hasFocus(comp.element)) {
  28154. firstChild(comp.element).each((input) => focus$4(input));
  28155. return Optional.some(true);
  28156. }
  28157. else {
  28158. return Optional.none();
  28159. }
  28160. };
  28161. const makeStepperButton = (action, title, tooltip, classes) => {
  28162. const editorOffCellStepButton = Cell(noop);
  28163. const translatedTooltip = backstage.shared.providers.translate(tooltip);
  28164. const altExecuting = generate$6('altExecuting');
  28165. const onSetup = onSetupEvent(editor, 'NodeChange SwitchMode DisabledStateChange', (api) => {
  28166. Disabling.set(api.getComponent(), !editor.selection.isEditable() || isDisabled(editor));
  28167. });
  28168. const onClick = (comp) => {
  28169. if (!Disabling.isDisabled(comp)) {
  28170. action(true);
  28171. }
  28172. };
  28173. return Button.sketch({
  28174. dom: {
  28175. tag: 'button',
  28176. attributes: {
  28177. 'aria-label': translatedTooltip,
  28178. 'data-mce-name': title
  28179. },
  28180. classes: classes.concat(title)
  28181. },
  28182. components: [
  28183. renderIconFromPack$1(title, backstage.shared.providers.icons)
  28184. ],
  28185. buttonBehaviours: derive$1([
  28186. Disabling.config({}),
  28187. Tooltipping.config(backstage.shared.providers.tooltips.getConfig({
  28188. tooltipText: translatedTooltip
  28189. })),
  28190. config(altExecuting, [
  28191. onControlAttached({ onSetup, getApi }, editorOffCellStepButton),
  28192. onControlDetached({ getApi }, editorOffCellStepButton),
  28193. run$1(keydown(), (comp, se) => {
  28194. if (se.event.raw.keyCode === global$1.SPACEBAR || se.event.raw.keyCode === global$1.ENTER) {
  28195. if (!Disabling.isDisabled(comp)) {
  28196. action(false);
  28197. }
  28198. }
  28199. }),
  28200. run$1(click(), onClick),
  28201. run$1(touchend(), onClick)
  28202. ])
  28203. ]),
  28204. eventOrder: {
  28205. [keydown()]: [altExecuting, 'keying'],
  28206. [click()]: [altExecuting, 'alloy.base.behaviour'],
  28207. [touchend()]: [altExecuting, 'alloy.base.behaviour'],
  28208. [attachedToDom()]: ['alloy.base.behaviour', altExecuting, 'tooltipping'],
  28209. [detachedFromDom()]: [altExecuting, 'tooltipping']
  28210. }
  28211. });
  28212. };
  28213. const memMinus = record(makeStepperButton((focusBack) => decrease(false, focusBack), 'minus', 'Decrease font size', []));
  28214. const memPlus = record(makeStepperButton((focusBack) => increase(false, focusBack), 'plus', 'Increase font size', []));
  28215. const memInput = record({
  28216. dom: {
  28217. tag: 'div',
  28218. classes: ['tox-input-wrapper']
  28219. },
  28220. components: [
  28221. Input.sketch({
  28222. inputBehaviours: derive$1([
  28223. Disabling.config({}),
  28224. config(customEvents, [
  28225. onControlAttached({ onSetup, getApi }, editorOffCell),
  28226. onControlDetached({ getApi }, editorOffCell)
  28227. ]),
  28228. config('input-update-display-text', [
  28229. run$1(updateMenuText, (comp, se) => {
  28230. Representing.setValue(comp, se.event.text);
  28231. }),
  28232. run$1(focusout(), (comp) => {
  28233. spec.onAction(Representing.getValue(comp));
  28234. }),
  28235. run$1(change(), (comp) => {
  28236. spec.onAction(Representing.getValue(comp));
  28237. })
  28238. ]),
  28239. Keying.config({
  28240. mode: 'special',
  28241. onEnter: (_comp) => {
  28242. changeValue(identity, true, true);
  28243. return Optional.some(true);
  28244. },
  28245. onEscape: goToParent,
  28246. onUp: (_comp) => {
  28247. increase(true, false);
  28248. return Optional.some(true);
  28249. },
  28250. onDown: (_comp) => {
  28251. decrease(true, false);
  28252. return Optional.some(true);
  28253. },
  28254. onLeft: (_comp, se) => {
  28255. se.cut();
  28256. return Optional.none();
  28257. },
  28258. onRight: (_comp, se) => {
  28259. se.cut();
  28260. return Optional.none();
  28261. }
  28262. })
  28263. ])
  28264. })
  28265. ],
  28266. behaviours: derive$1([
  28267. Focusing.config({}),
  28268. Keying.config({
  28269. mode: 'special',
  28270. onEnter: focusInput,
  28271. onSpace: focusInput,
  28272. onEscape: goToParent
  28273. }),
  28274. config('input-wrapper-events', [
  28275. run$1(mouseover(), (comp) => {
  28276. each$1([memMinus, memPlus], (button) => {
  28277. const buttonNode = SugarElement.fromDom(button.get(comp).element.dom);
  28278. if (hasFocus(buttonNode)) {
  28279. blur$1(buttonNode);
  28280. }
  28281. });
  28282. })
  28283. ])
  28284. ])
  28285. });
  28286. return {
  28287. dom: {
  28288. tag: 'div',
  28289. classes: ['tox-number-input'],
  28290. attributes: {
  28291. ...(isNonNullable(btnName) ? { 'data-mce-name': btnName } : {})
  28292. }
  28293. },
  28294. components: [
  28295. memMinus.asSpec(),
  28296. memInput.asSpec(),
  28297. memPlus.asSpec()
  28298. ],
  28299. behaviours: derive$1([
  28300. Focusing.config({}),
  28301. Keying.config({
  28302. mode: 'flow',
  28303. focusInside: FocusInsideModes.OnEnterOrSpaceMode,
  28304. cycles: false,
  28305. selector: 'button, .tox-input-wrapper',
  28306. onEscape: (wrapperComp) => {
  28307. if (hasFocus(wrapperComp.element)) {
  28308. return Optional.none();
  28309. }
  28310. else {
  28311. focus$4(wrapperComp.element);
  28312. return Optional.some(true);
  28313. }
  28314. },
  28315. })
  28316. ])
  28317. };
  28318. };
  28319. const menuTitle$1 = 'Font sizes';
  28320. const getTooltipPlaceholder$1 = constant$1('Font size {0}');
  28321. const fallbackFontSize = '12pt';
  28322. // See https://websemantics.uk/articles/font-size-conversion/ for conversions
  28323. const legacyFontSizes = {
  28324. '8pt': '1',
  28325. '10pt': '2',
  28326. '12pt': '3',
  28327. '14pt': '4',
  28328. '18pt': '5',
  28329. '24pt': '6',
  28330. '36pt': '7'
  28331. };
  28332. // Note: 'xx-small', 'x-small' and 'large' are rounded up to nearest whole pt
  28333. const keywordFontSizes = {
  28334. 'xx-small': '7pt',
  28335. 'x-small': '8pt',
  28336. 'small': '10pt',
  28337. 'medium': '12pt',
  28338. 'large': '14pt',
  28339. 'x-large': '18pt',
  28340. 'xx-large': '24pt'
  28341. };
  28342. const round = (number, precision) => {
  28343. const factor = Math.pow(10, precision);
  28344. return Math.round(number * factor) / factor;
  28345. };
  28346. const toPt = (fontSize, precision) => {
  28347. if (/[0-9.]+px$/.test(fontSize)) {
  28348. // Round to the nearest 0.5
  28349. return round(parseInt(fontSize, 10) * 72 / 96, precision || 0) + 'pt';
  28350. }
  28351. else {
  28352. return get$h(keywordFontSizes, fontSize).getOr(fontSize);
  28353. }
  28354. };
  28355. const toLegacy = (fontSize) => get$h(legacyFontSizes, fontSize).getOr('');
  28356. const getSpec$1 = (editor) => {
  28357. const getMatchingValue = () => {
  28358. let matchOpt = Optional.none();
  28359. const items = dataset.data;
  28360. const fontSize = editor.queryCommandValue('FontSize');
  28361. if (fontSize) {
  28362. // checking for three digits after decimal point, should be precise enough
  28363. for (let precision = 3; matchOpt.isNone() && precision >= 0; precision--) {
  28364. const pt = toPt(fontSize, precision);
  28365. const legacy = toLegacy(pt);
  28366. matchOpt = find$5(items, (item) => item.format === fontSize || item.format === pt || item.format === legacy);
  28367. }
  28368. }
  28369. return { matchOpt, size: fontSize };
  28370. };
  28371. const isSelectedFor = (item) => (valueOpt) => valueOpt.exists((value) => value.format === item);
  28372. const getCurrentValue = () => {
  28373. const { matchOpt } = getMatchingValue();
  28374. return matchOpt;
  28375. };
  28376. const getPreviewFor = constant$1(Optional.none);
  28377. const onAction = (rawItem) => () => {
  28378. editor.undoManager.transact(() => {
  28379. editor.focus();
  28380. editor.execCommand('FontSize', false, rawItem.format);
  28381. });
  28382. };
  28383. const updateSelectMenuText = (comp) => {
  28384. const { matchOpt, size } = getMatchingValue();
  28385. const text = matchOpt.fold(constant$1(size), (match) => match.title);
  28386. emitWith(comp, updateMenuText, {
  28387. text
  28388. });
  28389. fireFontSizeTextUpdate(editor, { value: text });
  28390. };
  28391. const dataset = buildBasicSettingsDataset(editor, 'font_size_formats', Delimiter.Space);
  28392. return {
  28393. tooltip: makeTooltipText(editor, getTooltipPlaceholder$1(), fallbackFontSize),
  28394. text: Optional.some(fallbackFontSize),
  28395. icon: Optional.none(),
  28396. isSelectedFor,
  28397. getPreviewFor,
  28398. getCurrentValue,
  28399. onAction,
  28400. updateText: updateSelectMenuText,
  28401. dataset,
  28402. shouldHide: false,
  28403. isInvalid: never
  28404. };
  28405. };
  28406. const createFontSizeButton = (editor, backstage) => createSelectButton(editor, backstage, getSpec$1(editor), getTooltipPlaceholder$1, 'FontSizeTextUpdate', 'fontsize');
  28407. const getConfigFromUnit = (unit) => {
  28408. var _a;
  28409. const baseConfig = { step: 1 };
  28410. const configs = {
  28411. em: { step: 0.1 },
  28412. cm: { step: 0.1 },
  28413. in: { step: 0.1 },
  28414. pc: { step: 0.1 },
  28415. ch: { step: 0.1 },
  28416. rem: { step: 0.1 }
  28417. };
  28418. return (_a = configs[unit]) !== null && _a !== void 0 ? _a : baseConfig;
  28419. };
  28420. const defaultValue = 16;
  28421. const isValidValue = (value) => value >= 0;
  28422. const getNumberInputSpec = (editor) => {
  28423. const getCurrentValue = () => editor.queryCommandValue('FontSize');
  28424. const updateInputValue = (comp) => emitWith(comp, updateMenuText, {
  28425. text: getCurrentValue()
  28426. });
  28427. return {
  28428. updateInputValue,
  28429. onAction: (format, focusBack) => editor.execCommand('FontSize', false, format, { skip_focus: !focusBack }),
  28430. getNewValue: (text, updateFunction) => {
  28431. parse(text, ['unsupportedLength', 'empty']);
  28432. const currentValue = getCurrentValue();
  28433. const parsedText = parse(text, ['unsupportedLength', 'empty']).or(parse(currentValue, ['unsupportedLength', 'empty']));
  28434. const value = parsedText.map((res) => res.value).getOr(defaultValue);
  28435. const defaultUnit = getFontSizeInputDefaultUnit(editor);
  28436. const unit = parsedText.map((res) => res.unit).filter((u) => u !== '').getOr(defaultUnit);
  28437. const newValue = updateFunction(value, getConfigFromUnit(unit).step);
  28438. const res = `${isValidValue(newValue) ? newValue : value}${unit}`;
  28439. if (res !== currentValue) {
  28440. fireFontSizeInputTextUpdate(editor, { value: res });
  28441. }
  28442. return res;
  28443. }
  28444. };
  28445. };
  28446. const createFontSizeInputButton = (editor, backstage) => createBespokeNumberInput(editor, backstage, getNumberInputSpec(editor), 'fontsizeinput');
  28447. // TODO: Test this!
  28448. const createFontSizeMenu = (editor, backstage) => {
  28449. const menuItems = createMenuItems(backstage, getSpec$1(editor));
  28450. editor.ui.registry.addNestedMenuItem('fontsize', {
  28451. text: menuTitle$1,
  28452. onSetup: onSetupEditableToggle(editor),
  28453. getSubmenuItems: () => menuItems.items.validateItems(menuItems.getStyleItems())
  28454. });
  28455. };
  28456. const menuTitle = 'Formats';
  28457. const getTooltipPlaceholder = (value) => isEmpty(value) ? 'Formats' : 'Format {0}';
  28458. const getSpec = (editor, dataset) => {
  28459. const fallbackFormat = 'Formats';
  28460. const isSelectedFor = (format) => () => editor.formatter.match(format);
  28461. const getPreviewFor = (format) => () => {
  28462. const fmt = editor.formatter.get(format);
  28463. return fmt !== undefined ? Optional.some({
  28464. tag: fmt.length > 0 ? fmt[0].inline || fmt[0].block || 'div' : 'div',
  28465. styles: editor.dom.parseStyle(editor.formatter.getCssText(format))
  28466. }) : Optional.none();
  28467. };
  28468. const updateSelectMenuText = (comp) => {
  28469. const getFormatItems = (fmt) => {
  28470. if (isNestedFormat(fmt)) {
  28471. return bind$3(fmt.items, getFormatItems);
  28472. }
  28473. else if (isFormatReference(fmt)) {
  28474. return [{ title: fmt.title, format: fmt.format }];
  28475. }
  28476. else {
  28477. return [];
  28478. }
  28479. };
  28480. const flattenedItems = bind$3(getStyleFormats(editor), getFormatItems);
  28481. const detectedFormat = findNearest(editor, constant$1(flattenedItems));
  28482. const text = detectedFormat.fold(constant$1({
  28483. title: fallbackFormat,
  28484. tooltipLabel: ''
  28485. }), (fmt) => ({
  28486. title: fmt.title,
  28487. tooltipLabel: fmt.title
  28488. }));
  28489. emitWith(comp, updateMenuText, {
  28490. text: text.title
  28491. });
  28492. fireStylesTextUpdate(editor, { value: text.tooltipLabel });
  28493. };
  28494. return {
  28495. tooltip: makeTooltipText(editor, getTooltipPlaceholder(''), ''),
  28496. text: Optional.some(fallbackFormat),
  28497. icon: Optional.none(),
  28498. isSelectedFor,
  28499. getCurrentValue: Optional.none,
  28500. getPreviewFor,
  28501. onAction: onActionToggleFormat$1(editor),
  28502. updateText: updateSelectMenuText,
  28503. shouldHide: shouldAutoHideStyleFormats(editor),
  28504. isInvalid: (item) => !editor.formatter.canApply(item.format),
  28505. dataset
  28506. };
  28507. };
  28508. const createStylesButton = (editor, backstage) => {
  28509. const dataset = { type: 'advanced', ...backstage.styles };
  28510. return createSelectButton(editor, backstage, getSpec(editor, dataset), getTooltipPlaceholder, 'StylesTextUpdate', 'styles');
  28511. };
  28512. const createStylesMenu = (editor, backstage) => {
  28513. const dataset = { type: 'advanced', ...backstage.styles };
  28514. const menuItems = createMenuItems(backstage, getSpec(editor, dataset));
  28515. editor.ui.registry.addNestedMenuItem('styles', {
  28516. text: menuTitle,
  28517. onSetup: onSetupEditableToggle(editor, () => menuItems.getStyleItems().length > 0),
  28518. getSubmenuItems: () => menuItems.items.validateItems(menuItems.getStyleItems())
  28519. });
  28520. };
  28521. const defaultToolbar = [
  28522. {
  28523. name: 'history', items: ['undo', 'redo']
  28524. },
  28525. {
  28526. name: 'ai', items: ['aidialog', 'aishortcuts']
  28527. },
  28528. {
  28529. name: 'styles', items: ['styles']
  28530. },
  28531. {
  28532. name: 'formatting', items: ['bold', 'italic']
  28533. },
  28534. {
  28535. name: 'alignment', items: ['alignleft', 'aligncenter', 'alignright', 'alignjustify']
  28536. },
  28537. {
  28538. name: 'indentation', items: ['outdent', 'indent']
  28539. },
  28540. {
  28541. name: 'permanent pen', items: ['permanentpen']
  28542. },
  28543. {
  28544. name: 'comments', items: ['addcomment']
  28545. }
  28546. ];
  28547. const renderFromBridge = (bridgeBuilder, render) => (spec, backstage, editor, btnName) => {
  28548. const internal = bridgeBuilder(spec).mapError((errInfo) => formatError(errInfo)).getOrDie();
  28549. return render(internal, backstage, editor, btnName);
  28550. };
  28551. const types = {
  28552. button: renderFromBridge(createToolbarButton, (s, backstage, _, btnName) => renderToolbarButton(s, backstage.shared.providers, btnName)),
  28553. togglebutton: renderFromBridge(createToggleButton, (s, backstage, _, btnName) => renderToolbarToggleButton(s, backstage.shared.providers, btnName)),
  28554. menubutton: renderFromBridge(createMenuButton, (s, backstage, _, btnName) => renderMenuButton(s, "tox-tbtn" /* ToolbarButtonClasses.Button */, backstage, Optional.none(), false, btnName)),
  28555. splitbutton: renderFromBridge(createSplitButton, (s, backstage, _, btnName) => renderSplitButton(s, backstage.shared, btnName)),
  28556. grouptoolbarbutton: renderFromBridge(createGroupToolbarButton, (s, backstage, editor, btnName) => {
  28557. const buttons = editor.ui.registry.getAll().buttons;
  28558. const identify = (toolbar) => identifyButtons(editor, { buttons, toolbar, allowToolbarGroups: false }, backstage, Optional.none());
  28559. const attributes = {
  28560. [Attribute]: backstage.shared.header.isPositionedAtTop() ? AttributeValue.TopToBottom : AttributeValue.BottomToTop
  28561. };
  28562. switch (getToolbarMode(editor)) {
  28563. case ToolbarMode$1.floating:
  28564. return renderFloatingToolbarButton(s, backstage, identify, attributes, btnName);
  28565. default:
  28566. // TODO change this message and add a case when sliding is available
  28567. throw new Error('Toolbar groups are only supported when using floating toolbar mode');
  28568. }
  28569. })
  28570. };
  28571. const extractFrom = (spec, backstage, editor, btnName) => get$h(types, spec.type).fold(() => {
  28572. // eslint-disable-next-line no-console
  28573. console.error('skipping button defined by', spec);
  28574. return Optional.none();
  28575. }, (render) => Optional.some(render(spec, backstage, editor, btnName)));
  28576. const bespokeButtons = {
  28577. styles: createStylesButton,
  28578. fontsize: createFontSizeButton,
  28579. fontsizeinput: createFontSizeInputButton,
  28580. fontfamily: createFontFamilyButton,
  28581. blocks: createBlocksButton,
  28582. align: createAlignButton,
  28583. navigateback: createNavigateBackButton
  28584. };
  28585. const removeUnusedDefaults = (buttons) => {
  28586. const filteredItemGroups = map$2(defaultToolbar, (group) => {
  28587. const items = filter$2(group.items, (subItem) => has$2(buttons, subItem) || has$2(bespokeButtons, subItem));
  28588. return {
  28589. name: group.name,
  28590. items
  28591. };
  28592. });
  28593. return filter$2(filteredItemGroups, (group) => group.items.length > 0);
  28594. };
  28595. const convertStringToolbar = (strToolbar) => {
  28596. const groupsStrings = strToolbar.split('|');
  28597. return map$2(groupsStrings, (g) => ({
  28598. items: g.trim().split(' ')
  28599. }));
  28600. };
  28601. const isToolbarGroupSettingArray = (toolbar) => isArrayOf(toolbar, (t) => (has$2(t, 'name') || has$2(t, 'label')) && has$2(t, 'items'));
  28602. // Toolbar settings
  28603. // false = disabled
  28604. // undefined or true = default
  28605. // string = enabled with specified buttons and groups
  28606. // string array = enabled with specified buttons and groups
  28607. // object array = enabled with specified buttons, groups and group titles
  28608. const createToolbar = (toolbarConfig) => {
  28609. const toolbar = toolbarConfig.toolbar;
  28610. const buttons = toolbarConfig.buttons;
  28611. if (toolbar === false) {
  28612. return [];
  28613. }
  28614. else if (toolbar === undefined || toolbar === true) {
  28615. return removeUnusedDefaults(buttons);
  28616. }
  28617. else if (isString(toolbar)) {
  28618. return convertStringToolbar(toolbar);
  28619. }
  28620. else if (isToolbarGroupSettingArray(toolbar)) {
  28621. return toolbar;
  28622. }
  28623. else {
  28624. // eslint-disable-next-line no-console
  28625. console.error('Toolbar type should be string, string[], boolean or ToolbarGroup[]');
  28626. return [];
  28627. }
  28628. };
  28629. const lookupButton = (editor, buttons, toolbarItem, allowToolbarGroups, backstage, prefixes) => get$h(buttons, toolbarItem.toLowerCase())
  28630. .orThunk(() => prefixes.bind((ps) => findMap(ps, (prefix) => get$h(buttons, prefix + toolbarItem.toLowerCase()))))
  28631. .fold(() => get$h(bespokeButtons, toolbarItem.toLowerCase()).map((r) => r(editor, backstage)),
  28632. // TODO: Add back after TINY-3232 is implemented
  28633. // .orThunk(() => {
  28634. // console.error('No representation for toolbarItem: ' + toolbarItem);
  28635. // return Optional.none();
  28636. // ),
  28637. (spec) => {
  28638. if (spec.type === 'grouptoolbarbutton' && !allowToolbarGroups) {
  28639. // TODO change this message when sliding is available
  28640. // eslint-disable-next-line no-console
  28641. console.warn(`Ignoring the '${toolbarItem}' toolbar button. Group toolbar buttons are only supported when using floating toolbar mode and cannot be nested.`);
  28642. return Optional.none();
  28643. }
  28644. else {
  28645. return extractFrom(spec, backstage, editor, toolbarItem.toLowerCase());
  28646. }
  28647. });
  28648. const identifyButtons = (editor, toolbarConfig, backstage, prefixes) => {
  28649. const toolbarGroups = createToolbar(toolbarConfig);
  28650. const groups = map$2(toolbarGroups, (group) => {
  28651. const items = bind$3(group.items, (toolbarItem) => {
  28652. if (toolbarItem.trim().length === 0) {
  28653. return [];
  28654. }
  28655. return lookupButton(editor, toolbarConfig.buttons, toolbarItem, toolbarConfig.allowToolbarGroups, backstage, prefixes)
  28656. .map((spec) => Array.isArray(spec) ? spec : [spec])
  28657. .getOr([]);
  28658. });
  28659. return {
  28660. title: Optional.from(editor.translate(group.name)),
  28661. label: someIf(group.label !== undefined, editor.translate(group.label)),
  28662. items
  28663. };
  28664. });
  28665. return filter$2(groups, (group) => group.items.length > 0);
  28666. };
  28667. // Set toolbar(s) depending on if multiple toolbars is configured or not
  28668. const setToolbar = (editor, uiRefs, rawUiConfig, backstage) => {
  28669. const outerContainer = uiRefs.mainUi.outerContainer;
  28670. const toolbarConfig = rawUiConfig.toolbar;
  28671. const toolbarButtonsConfig = rawUiConfig.buttons;
  28672. // Check if toolbar type is a non-empty string array
  28673. if (isArrayOf(toolbarConfig, isString)) {
  28674. const toolbars = toolbarConfig.map((t) => {
  28675. const config = { toolbar: t, buttons: toolbarButtonsConfig, allowToolbarGroups: rawUiConfig.allowToolbarGroups };
  28676. return identifyButtons(editor, config, backstage, Optional.none());
  28677. });
  28678. OuterContainer.setToolbars(outerContainer, toolbars);
  28679. }
  28680. else {
  28681. OuterContainer.setToolbar(outerContainer, identifyButtons(editor, rawUiConfig, backstage, Optional.none()));
  28682. }
  28683. };
  28684. const detection = detect$1();
  28685. const isiOS12 = detection.os.isiOS() && detection.os.version.major <= 12;
  28686. const setupEvents$1 = (editor, uiRefs) => {
  28687. const { uiMotherships } = uiRefs;
  28688. const dom = editor.dom;
  28689. let contentWindow = editor.getWin();
  28690. const initialDocEle = editor.getDoc().documentElement;
  28691. const lastWindowDimensions = Cell(SugarPosition(contentWindow.innerWidth, contentWindow.innerHeight));
  28692. const lastDocumentDimensions = Cell(SugarPosition(initialDocEle.offsetWidth, initialDocEle.offsetHeight));
  28693. const resizeWindow = () => {
  28694. // Check if the window dimensions have changed and if so then trigger a content resize event
  28695. const outer = lastWindowDimensions.get();
  28696. if (outer.left !== contentWindow.innerWidth || outer.top !== contentWindow.innerHeight) {
  28697. lastWindowDimensions.set(SugarPosition(contentWindow.innerWidth, contentWindow.innerHeight));
  28698. fireResizeContent(editor);
  28699. }
  28700. };
  28701. const resizeDocument = () => {
  28702. // Don't use the initial doc ele, as there's a small chance it may have changed
  28703. const docEle = editor.getDoc().documentElement;
  28704. // Check if the document dimensions have changed and if so then trigger a content resize event
  28705. const inner = lastDocumentDimensions.get();
  28706. if (inner.left !== docEle.offsetWidth || inner.top !== docEle.offsetHeight) {
  28707. lastDocumentDimensions.set(SugarPosition(docEle.offsetWidth, docEle.offsetHeight));
  28708. fireResizeContent(editor);
  28709. }
  28710. };
  28711. const scroll = (e) => {
  28712. fireScrollContent(editor, e);
  28713. };
  28714. dom.bind(contentWindow, 'resize', resizeWindow);
  28715. dom.bind(contentWindow, 'scroll', scroll);
  28716. // Bind to async load events and trigger a content resize event if the size has changed
  28717. const elementLoad = capture(SugarElement.fromDom(editor.getBody()), 'load', resizeDocument);
  28718. // We want to hide ALL UI motherships here.
  28719. editor.on('hide', () => {
  28720. each$1(uiMotherships, (m) => {
  28721. set$7(m.element, 'display', 'none');
  28722. });
  28723. });
  28724. editor.on('show', () => {
  28725. each$1(uiMotherships, (m) => {
  28726. remove$6(m.element, 'display');
  28727. });
  28728. });
  28729. editor.on('NodeChange', resizeDocument);
  28730. editor.on('remove', () => {
  28731. elementLoad.unbind();
  28732. dom.unbind(contentWindow, 'resize', resizeWindow);
  28733. dom.unbind(contentWindow, 'scroll', scroll);
  28734. // Clean memory for IE
  28735. contentWindow = null;
  28736. });
  28737. };
  28738. // TINY-9226: When set, the `ui_mode: split` option will create two different sinks (one for popups and one for sinks)
  28739. // and the popup sink will be placed adjacent to the editor. This will make it having the same scrolling ancestry.
  28740. const attachUiMotherships = (editor, uiRoot, uiRefs) => {
  28741. if (isSplitUiMode(editor)) {
  28742. attachSystemAfter(uiRefs.mainUi.mothership.element, uiRefs.popupUi.mothership);
  28743. }
  28744. // In UiRefs, dialogUi and popupUi refer to the same thing if ui_mode: combined
  28745. attachSystem(uiRoot, uiRefs.dialogUi.mothership);
  28746. };
  28747. const render$1 = (editor, uiRefs, rawUiConfig, backstage, args) => {
  28748. const { mainUi, uiMotherships } = uiRefs;
  28749. const lastToolbarWidth = Cell(0);
  28750. const outerContainer = mainUi.outerContainer;
  28751. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  28752. iframe(editor);
  28753. const eTargetNode = SugarElement.fromDom(args.targetNode);
  28754. const uiRoot = getContentContainer(getRootNode(eTargetNode));
  28755. attachSystemAfter(eTargetNode, mainUi.mothership);
  28756. attachUiMotherships(editor, uiRoot, uiRefs);
  28757. editor.on('PostRender', () => {
  28758. OuterContainer.setSidebar(outerContainer, rawUiConfig.sidebar, getSidebarShow(editor));
  28759. });
  28760. // TINY-10343: Using `SkinLoaded` instead of `PostRender` because if the skin loading takes too long you run in to rendering problems since things are measured before the CSS is being applied
  28761. editor.on('SkinLoaded', () => {
  28762. // Set the sidebar before the toolbar and menubar
  28763. // - each sidebar has an associated toggle toolbar button that needs to check the
  28764. // sidebar that is set to determine its active state on setup
  28765. setToolbar(editor, uiRefs, rawUiConfig, backstage);
  28766. lastToolbarWidth.set(editor.getWin().innerWidth);
  28767. OuterContainer.setMenubar(outerContainer, identifyMenus(editor, rawUiConfig));
  28768. OuterContainer.setViews(outerContainer, rawUiConfig.views);
  28769. setupEvents$1(editor, uiRefs);
  28770. });
  28771. const socket = OuterContainer.getSocket(outerContainer).getOrDie('Could not find expected socket element');
  28772. if (isiOS12) {
  28773. setAll(socket.element, {
  28774. 'overflow': 'scroll',
  28775. '-webkit-overflow-scrolling': 'touch' // required for ios < 13 content scrolling
  28776. });
  28777. const limit = first$1(() => {
  28778. editor.dispatch('ScrollContent');
  28779. }, 20);
  28780. const unbinder = bind$1(socket.element, 'scroll', limit.throttle);
  28781. editor.on('remove', unbinder.unbind);
  28782. }
  28783. setupEventsForUi(editor, uiRefs);
  28784. editor.addCommand('ToggleSidebar', (_ui, value) => {
  28785. OuterContainer.toggleSidebar(outerContainer, value);
  28786. fireToggleSidebar(editor);
  28787. });
  28788. editor.addQueryValueHandler('ToggleSidebar', () => { var _a; return (_a = OuterContainer.whichSidebar(outerContainer)) !== null && _a !== void 0 ? _a : ''; });
  28789. editor.addCommand('ToggleView', (_ui, value) => {
  28790. if (OuterContainer.toggleView(outerContainer, value)) {
  28791. const target = outerContainer.element;
  28792. mainUi.mothership.broadcastOn([dismissPopups()], { target });
  28793. each$1(uiMotherships, (m) => {
  28794. m.broadcastOn([dismissPopups()], { target });
  28795. });
  28796. // Switching back to main view should focus the editor and update any UIs
  28797. if (isNull(OuterContainer.whichView(outerContainer))) {
  28798. editor.focus();
  28799. editor.nodeChanged();
  28800. OuterContainer.refreshToolbar(outerContainer);
  28801. }
  28802. fireToggleView(editor);
  28803. }
  28804. });
  28805. editor.addQueryValueHandler('ToggleView', () => { var _a; return (_a = OuterContainer.whichView(outerContainer)) !== null && _a !== void 0 ? _a : ''; });
  28806. const toolbarMode = getToolbarMode(editor);
  28807. const refreshDrawer = () => {
  28808. OuterContainer.refreshToolbar(uiRefs.mainUi.outerContainer);
  28809. };
  28810. if (toolbarMode === ToolbarMode$1.sliding || toolbarMode === ToolbarMode$1.floating) {
  28811. editor.on('ResizeWindow ResizeEditor ResizeContent', () => {
  28812. // Check if the width has changed, if so then refresh the toolbar drawer. We don't care if height changes.
  28813. const width = editor.getWin().innerWidth;
  28814. if (width !== lastToolbarWidth.get()) {
  28815. refreshDrawer();
  28816. lastToolbarWidth.set(width);
  28817. }
  28818. });
  28819. }
  28820. const api = {
  28821. setEnabled: (state) => {
  28822. const eventType = state ? 'setEnabled' : 'setDisabled';
  28823. broadcastEvents(uiRefs, eventType);
  28824. },
  28825. isEnabled: () => !Disabling.isDisabled(outerContainer)
  28826. };
  28827. return {
  28828. iframeContainer: socket.element.dom,
  28829. editorContainer: outerContainer.element.dom,
  28830. api
  28831. };
  28832. };
  28833. var Iframe = /*#__PURE__*/Object.freeze({
  28834. __proto__: null,
  28835. render: render$1
  28836. });
  28837. const parseToInt = (val) => {
  28838. // if size is a number or '_px', will return the number
  28839. const re = /^[0-9\.]+(|px)$/i;
  28840. if (re.test('' + val)) {
  28841. return Optional.some(parseInt('' + val, 10));
  28842. }
  28843. return Optional.none();
  28844. };
  28845. const numToPx = (val) => isNumber(val) ? val + 'px' : val;
  28846. const calcCappedSize = (size, minSize, maxSize) => {
  28847. const minOverride = minSize.filter((min) => size < min);
  28848. const maxOverride = maxSize.filter((max) => size > max);
  28849. return minOverride.or(maxOverride).getOr(size);
  28850. };
  28851. const convertValueToPx = (element, value) => {
  28852. if (typeof value === 'number') {
  28853. return Optional.from(value);
  28854. }
  28855. const splitValue = /^([0-9.]+)(pt|em|px)$/.exec(value.trim());
  28856. if (splitValue) {
  28857. const type = splitValue[2];
  28858. const parsed = Number.parseFloat(splitValue[1]);
  28859. if (Number.isNaN(parsed) || parsed < 0) {
  28860. return Optional.none();
  28861. }
  28862. else if (type === 'em') {
  28863. return Optional.from(parsed * Number.parseFloat(window.getComputedStyle(element.dom).fontSize));
  28864. }
  28865. else if (type === 'pt') {
  28866. return Optional.from(parsed * (72 / 96));
  28867. }
  28868. else if (type === 'px') {
  28869. return Optional.from(parsed);
  28870. }
  28871. }
  28872. return Optional.none();
  28873. };
  28874. const getHeight = (editor) => {
  28875. const baseHeight = convertValueToPx(SugarElement.fromDom(editor.targetElm), getHeightOption(editor));
  28876. const minHeight = getMinHeightOption(editor);
  28877. const maxHeight = getMaxHeightOption(editor);
  28878. return baseHeight.map((height) => calcCappedSize(height, minHeight, maxHeight));
  28879. };
  28880. const getHeightWithFallback = (editor) => {
  28881. return getHeight(editor).getOr(getHeightOption(editor)); // If we can't parse, set the height while ignoring min/max values.
  28882. };
  28883. const getWidth = (editor) => {
  28884. const baseWidth = getWidthOption(editor);
  28885. const minWidth = getMinWidthOption(editor);
  28886. const maxWidth = getMaxWidthOption(editor);
  28887. return parseToInt(baseWidth).map((width) => calcCappedSize(width, minWidth, maxWidth));
  28888. };
  28889. const getWidthWithFallback = (editor) => {
  28890. const width = getWidth(editor);
  28891. return width.getOr(getWidthOption(editor));
  28892. };
  28893. const { ToolbarLocation, ToolbarMode } = Options;
  28894. const maximumDistanceToEdge = 40;
  28895. const InlineHeader = (editor, targetElm, uiRefs, backstage, floatContainer) => {
  28896. const { mainUi, uiMotherships } = uiRefs;
  28897. const DOM = global$9.DOM;
  28898. const useFixedToolbarContainer = useFixedContainer(editor);
  28899. const isSticky = isStickyToolbar(editor);
  28900. const editorMaxWidthOpt = getMaxWidthOption(editor).or(getWidth(editor));
  28901. const headerBackstage = backstage.shared.header;
  28902. const isPositionedAtTop = headerBackstage.isPositionedAtTop;
  28903. const minimumToolbarWidth = 150; // Value is arbitrary.
  28904. const toolbarMode = getToolbarMode(editor);
  28905. const isSplitToolbar = toolbarMode === ToolbarMode.sliding || toolbarMode === ToolbarMode.floating;
  28906. const visible = Cell(false);
  28907. const isVisible = () => visible.get() && !editor.removed;
  28908. // Calculate the toolbar offset when using a split toolbar drawer
  28909. const calcToolbarOffset = (toolbar) => isSplitToolbar ?
  28910. toolbar.fold(constant$1(0), (tbar) =>
  28911. // If we have an overflow toolbar, we need to offset the positioning by the height of the overflow toolbar
  28912. tbar.components().length > 1 ? get$d(tbar.components()[1].element) : 0) : 0;
  28913. const calcMode = (container) => {
  28914. switch (getToolbarLocation(editor)) {
  28915. case ToolbarLocation.auto:
  28916. const toolbar = OuterContainer.getToolbar(mainUi.outerContainer);
  28917. const offset = calcToolbarOffset(toolbar);
  28918. const toolbarHeight = get$d(container.element) - offset;
  28919. const targetBounds = box$1(targetElm);
  28920. // Determine if the toolbar has room to render at the top/bottom of the document
  28921. const roomAtTop = targetBounds.y > toolbarHeight;
  28922. if (roomAtTop) {
  28923. return 'top';
  28924. }
  28925. else {
  28926. const doc = documentElement(targetElm);
  28927. const docHeight = Math.max(doc.dom.scrollHeight, get$d(doc));
  28928. const roomAtBottom = targetBounds.bottom < docHeight - toolbarHeight;
  28929. // If there isn't ever room to add the toolbar above the target element, then place the toolbar at the bottom.
  28930. // Likewise if there's no room at the bottom, then we should show at the top. If there's no room at the bottom
  28931. // or top, then prefer the bottom except when it'll prevent accessing the content at the bottom.
  28932. // Make sure to exclude scroll position, as we want to still show at the top if the user can scroll up to undock
  28933. if (roomAtBottom) {
  28934. return 'bottom';
  28935. }
  28936. else {
  28937. const winBounds = win();
  28938. const isRoomAtBottomViewport = winBounds.bottom < targetBounds.bottom - toolbarHeight;
  28939. return isRoomAtBottomViewport ? 'bottom' : 'top';
  28940. }
  28941. }
  28942. case ToolbarLocation.bottom:
  28943. return 'bottom';
  28944. case ToolbarLocation.top:
  28945. default:
  28946. return 'top';
  28947. }
  28948. };
  28949. const setupMode = (mode) => {
  28950. // Update the docking mode
  28951. floatContainer.on((container) => {
  28952. Docking.setModes(container, [mode]);
  28953. headerBackstage.setDockingMode(mode);
  28954. // Update the vertical menu direction
  28955. const verticalDir = isPositionedAtTop() ? AttributeValue.TopToBottom : AttributeValue.BottomToTop;
  28956. set$9(container.element, Attribute, verticalDir);
  28957. });
  28958. };
  28959. const updateChromeWidth = () => {
  28960. floatContainer.on((container) => {
  28961. // Update the max width of the inline toolbar
  28962. const maxWidth = editorMaxWidthOpt.getOrThunk(() => {
  28963. // Adding 10px of margin so that the toolbar won't try to wrap
  28964. return getBounds$1().width - viewport$1(targetElm).left - 10;
  28965. });
  28966. set$7(container.element, 'max-width', maxWidth + 'px');
  28967. });
  28968. };
  28969. const updateChromePosition = (isOuterContainerWidthRestored, prevScroll) => {
  28970. floatContainer.on((container) => {
  28971. const toolbar = OuterContainer.getToolbar(mainUi.outerContainer);
  28972. const offset = calcToolbarOffset(toolbar);
  28973. // The float container/editor may not have been rendered yet, which will cause it to have a non integer based positions
  28974. // so we need to round this to account for that.
  28975. const targetBounds = box$1(targetElm);
  28976. const offsetParent = getOffsetParent$1(editor, mainUi.outerContainer.element);
  28977. const getLeft = () => offsetParent.fold(() => targetBounds.x, (offsetParent) => {
  28978. // Because for ui_mode: split, the main mothership (which includes the toolbar) is moved and added as a sibling
  28979. // If there's any relative position div set as the parent and the offsetParent is no longer the body,
  28980. // the absolute top/left positions would no longer be correct
  28981. // When there's a relative div and the position is the same as the toolbar container
  28982. // then it would produce a negative top as it needs to be positioned on top of the offsetParent
  28983. const offsetBox = box$1(offsetParent);
  28984. const isOffsetParentBody = eq(offsetParent, body());
  28985. return isOffsetParentBody
  28986. ? targetBounds.x
  28987. : targetBounds.x - offsetBox.x;
  28988. });
  28989. const getTop = () => offsetParent.fold(() => isPositionedAtTop()
  28990. ? Math.max(targetBounds.y - get$d(container.element) + offset, 0)
  28991. : targetBounds.bottom, (offsetParent) => {
  28992. var _a;
  28993. // Because for ui_mode: split, the main mothership (which includes the toolbar) is moved and added as a sibling
  28994. // If there's any relative position div set as the parent and the offsetParent is no longer the body,
  28995. // the absolute top/left positions would no longer be correct
  28996. // When there's a relative div and the position is the same as the toolbar container
  28997. // then it would produce a negative top as it needs to be positioned on top of the offsetParent
  28998. const offsetBox = box$1(offsetParent);
  28999. const scrollDelta = (_a = offsetParent.dom.scrollTop) !== null && _a !== void 0 ? _a : 0;
  29000. const isOffsetParentBody = eq(offsetParent, body());
  29001. const topValue = isOffsetParentBody
  29002. ? Math.max(targetBounds.y - get$d(container.element) + offset, 0)
  29003. : targetBounds.y - offsetBox.y + scrollDelta - get$d(container.element) + offset;
  29004. return isPositionedAtTop()
  29005. ? topValue
  29006. : targetBounds.bottom;
  29007. });
  29008. const left = getLeft();
  29009. const widthProperties = someIf(isOuterContainerWidthRestored,
  29010. // This width can be used for calculating the "width" when resolving issues with flex-wrapping being triggered at the window width, despite scroll space being available to the right.
  29011. Math.ceil(mainUi.outerContainer.element.dom.getBoundingClientRect().width))
  29012. // this check is needed because if the toolbar is rendered outside of the `outerContainer` because the toolbar have `position: "fixed"`
  29013. // the calculate width isn't correct
  29014. .filter((w) => w > minimumToolbarWidth).map((toolbarWidth) => {
  29015. const scroll = prevScroll.getOr(get$b());
  29016. /*
  29017. As the editor container can wrap its elements (due to flex-wrap), the width of the container impacts also its height. Adding a minimum width works around two problems:
  29018. a) The docking behaviour (e.g. lazyContext) does not handle the situation of a very thin component near the edge of the screen very well, and actually has no concept of horizontal scroll - it only checks y values.
  29019. b) A very small toolbar is essentially unusable. On scrolling of X, we keep updating the width of the toolbar so that it can grow to fit the available space.
  29020. Note: this is entirely determined on the number of items in the menu and the toolbar, because when they wrap, that's what causes the height. Also, having multiple toolbars can also make it higher.
  29021. */
  29022. const availableWidth = window.innerWidth - (left - scroll.left);
  29023. const width = Math.max(Math.min(toolbarWidth, availableWidth), minimumToolbarWidth);
  29024. if (availableWidth < toolbarWidth) {
  29025. set$7(mainUi.outerContainer.element, 'width', width + 'px');
  29026. }
  29027. return {
  29028. width: width + 'px'
  29029. };
  29030. }).getOr({ width: 'max-content' });
  29031. const baseProperties = {
  29032. position: 'absolute',
  29033. left: Math.round(left) + 'px',
  29034. top: getTop() + 'px'
  29035. };
  29036. setAll(mainUi.outerContainer.element, {
  29037. ...baseProperties,
  29038. ...widthProperties
  29039. });
  29040. });
  29041. };
  29042. // This would return Optional.none, for ui_mode: combined, which will fallback to the default code block
  29043. // For ui_mode: split, the offsetParent would be the body if there were no relative div set as parent
  29044. const getOffsetParent$1 = (editor, element) => isSplitUiMode(editor) ? getOffsetParent(element) : Optional.none();
  29045. const repositionPopups$1 = () => {
  29046. each$1(uiMotherships, (m) => {
  29047. m.broadcastOn([repositionPopups()], {});
  29048. });
  29049. };
  29050. const restoreOuterContainerWidth = () => {
  29051. /*
  29052. Editors can be placed so far to the right that their left position is beyond the window width. This causes problems with flex-wrap. To solve this, set a width style on the container.
  29053. Natural width of the container needs to be calculated first.
  29054. */
  29055. if (!useFixedToolbarContainer) {
  29056. const toolbarCurrentRightsidePosition = absolute$3(mainUi.outerContainer.element).left + getOuter(mainUi.outerContainer.element);
  29057. /*
  29058. Check the width if we are within X number of pixels to the edge ( or above ). Also check if we have the width-value set.
  29059. This helps handling the issue where it goes from having a width set ( because it's too wide ) to going so far from the edge it no longer triggers the problem. Common when the width is changed by test.
  29060. */
  29061. if (toolbarCurrentRightsidePosition >= window.innerWidth - maximumDistanceToEdge || getRaw(mainUi.outerContainer.element, 'width').isSome()) {
  29062. set$7(mainUi.outerContainer.element, 'position', 'absolute');
  29063. set$7(mainUi.outerContainer.element, 'left', '0px');
  29064. remove$6(mainUi.outerContainer.element, 'width');
  29065. return true;
  29066. }
  29067. }
  29068. return false;
  29069. };
  29070. const update = (stickyAction) => {
  29071. // Skip updating the ui if it's hidden
  29072. if (!isVisible()) {
  29073. return;
  29074. }
  29075. // Handles positioning, docking and SplitToolbar (more drawer) behaviour. Modes:
  29076. // 1. Basic inline: does positioning and docking
  29077. // 2. Inline + more drawer: does positioning, docking and SplitToolbar
  29078. // 3. Inline + fixed_toolbar_container: does nothing
  29079. // 4. Inline + fixed_toolbar_container + more drawer: does SplitToolbar
  29080. // Update the max width, as the body width may have changed
  29081. if (!useFixedToolbarContainer) {
  29082. updateChromeWidth();
  29083. }
  29084. const prevScroll = get$b();
  29085. const isOuterContainerWidthRestored = useFixedToolbarContainer ? false : restoreOuterContainerWidth();
  29086. /*
  29087. Refresh split toolbar. Before calling refresh, we need to make sure that we have the full width (through restoreOuterContainerWidth above), otherwise too much will be put in the overflow drawer.
  29088. A split toolbar requires a calculation to see what ends up in the "more drawer". When we don't have a split toolbar, then there is no reason to refresh the toolbar when the size changes.
  29089. */
  29090. if (isSplitToolbar) {
  29091. OuterContainer.refreshToolbar(mainUi.outerContainer);
  29092. }
  29093. // Positioning
  29094. if (!useFixedToolbarContainer) {
  29095. // Getting the current scroll as the previous step may have reset the scroll,
  29096. // We also want calculation based on the previous scroll, then restoring the scroll when everything is set.
  29097. const currentScroll = get$b();
  29098. const optScroll = someIf(prevScroll.left !== currentScroll.left, prevScroll);
  29099. // This will position the container in the right spot.
  29100. updateChromePosition(isOuterContainerWidthRestored, optScroll);
  29101. // Restore scroll left position only if they are different, keeping the current scroll top, that shouldn't be changed
  29102. optScroll.each((scroll) => {
  29103. to(scroll.left, currentScroll.top);
  29104. });
  29105. }
  29106. // Docking
  29107. if (isSticky) {
  29108. floatContainer.on(stickyAction);
  29109. }
  29110. // Floating toolbar
  29111. repositionPopups$1();
  29112. };
  29113. const doUpdateMode = () => {
  29114. // Skip updating the mode if the toolbar is hidden, is
  29115. // using a fixed container or has sticky toolbars disabled
  29116. if (useFixedToolbarContainer || !isSticky || !isVisible()) {
  29117. return false;
  29118. }
  29119. return floatContainer.get().exists((fc) => {
  29120. const currentMode = headerBackstage.getDockingMode();
  29121. const newMode = calcMode(fc);
  29122. // Note: the docking mode will only be able to change when the `toolbar_location`
  29123. // is set to "auto".
  29124. if (newMode !== currentMode) {
  29125. setupMode(newMode);
  29126. return true;
  29127. }
  29128. else {
  29129. return false;
  29130. }
  29131. });
  29132. };
  29133. const show = () => {
  29134. visible.set(true);
  29135. set$7(mainUi.outerContainer.element, 'display', 'flex');
  29136. DOM.addClass(editor.getBody(), 'mce-edit-focus');
  29137. each$1(uiMotherships, (m) => {
  29138. // We remove the display style when showing, because when hiding, we set it to "none"
  29139. remove$6(m.element, 'display');
  29140. });
  29141. doUpdateMode();
  29142. if (isSplitUiMode(editor)) {
  29143. // When the toolbar is shown, then hidden and when the page is then scrolled,
  29144. // the toolbar is set to docked, which shouldn't be as it should be static position
  29145. // calling reset here, to reset the state.
  29146. // Another case would be when the toolbar is shown initially (with location_bottom)
  29147. // we don't want to dock the toolbar, calling Docking.refresh
  29148. update((elem) => Docking.isDocked(elem) ? Docking.reset(elem) : Docking.refresh(elem));
  29149. }
  29150. else {
  29151. // Even if we aren't updating the docking mode, we still want to reposition
  29152. // the Ui. NOTE: We are using Docking.refresh here, rather than Docking.reset. This
  29153. // means it should keep whatever its "previous" coordinates were, and will just
  29154. // behave like the window was scrolled again, and Docking needs to work out if it
  29155. // is going to dock / undock
  29156. update(Docking.refresh);
  29157. }
  29158. };
  29159. const hide = () => {
  29160. visible.set(false);
  29161. set$7(mainUi.outerContainer.element, 'display', 'none');
  29162. DOM.removeClass(editor.getBody(), 'mce-edit-focus');
  29163. each$1(uiMotherships, (m) => {
  29164. set$7(m.element, 'display', 'none');
  29165. });
  29166. };
  29167. const updateMode = () => {
  29168. const changedMode = doUpdateMode();
  29169. // If the docking mode has changed due to the update, we want to reset
  29170. // docking. This will clear any prior stored positions
  29171. if (changedMode) {
  29172. update(Docking.reset);
  29173. }
  29174. };
  29175. return {
  29176. isVisible,
  29177. isPositionedAtTop,
  29178. show,
  29179. hide,
  29180. update,
  29181. updateMode,
  29182. repositionPopups: repositionPopups$1
  29183. };
  29184. };
  29185. const getTargetPosAndBounds = (targetElm, isToolbarTop) => {
  29186. const bounds = box$1(targetElm);
  29187. return {
  29188. pos: isToolbarTop ? bounds.y : bounds.bottom,
  29189. bounds
  29190. };
  29191. };
  29192. const setupEvents = (editor, targetElm, ui, toolbarPersist) => {
  29193. const prevPosAndBounds = Cell(getTargetPosAndBounds(targetElm, ui.isPositionedAtTop()));
  29194. const resizeContent = (e) => {
  29195. const { pos, bounds } = getTargetPosAndBounds(targetElm, ui.isPositionedAtTop());
  29196. const { pos: prevPos, bounds: prevBounds } = prevPosAndBounds.get();
  29197. const hasResized = bounds.height !== prevBounds.height || bounds.width !== prevBounds.width;
  29198. prevPosAndBounds.set({ pos, bounds });
  29199. if (hasResized) {
  29200. fireResizeContent(editor, e);
  29201. }
  29202. if (ui.isVisible()) {
  29203. if (prevPos !== pos) {
  29204. // The proposed toolbar location has moved, so we need to reposition the Ui. This might
  29205. // include things like refreshing any Docking / stickiness for the toolbars
  29206. ui.update(Docking.reset);
  29207. }
  29208. else if (hasResized) {
  29209. // The proposed toolbar location hasn't moved, but the dimensions of the editor have changed.
  29210. // We use "updateMode" here instead of "update". The primary reason is that "updateMode"
  29211. // only repositions the Ui if it has detected that the docking mode needs to change, which
  29212. // will only happen with `toolbar_location` is set to `auto`.
  29213. ui.updateMode();
  29214. // NOTE: This repositionPopups call is going to be a duplicate if "updateMode" identifies
  29215. // that the mode has changed. We probably need to make it a bit more granular .. so
  29216. // that we can just query if the mode has changed. Otherwise, we're going to end up with
  29217. // situations like this where we are doing a potentially expensive operation
  29218. // (repositionPopups) more than once.
  29219. ui.repositionPopups();
  29220. }
  29221. }
  29222. };
  29223. if (!toolbarPersist) {
  29224. editor.on('activate', ui.show);
  29225. editor.on('deactivate', ui.hide);
  29226. }
  29227. // For both the initial load (SkinLoaded) and any resizes (ResizeWindow), we want to
  29228. // update the positions of the Ui elements (and reset Docking / stickiness)
  29229. editor.on('SkinLoaded ResizeWindow', () => ui.update(Docking.reset));
  29230. editor.on('NodeChange keydown', (e) => {
  29231. requestAnimationFrame(() => resizeContent(e));
  29232. });
  29233. // When the page has been scrolled, we need to update any docking positions. We also
  29234. // want to reposition all the Ui elements if required.
  29235. let lastScrollX = 0;
  29236. const updateUi = last(() => ui.update(Docking.refresh), 33);
  29237. editor.on('ScrollWindow', () => {
  29238. const newScrollX = get$b().left;
  29239. if (newScrollX !== lastScrollX) {
  29240. lastScrollX = newScrollX;
  29241. updateUi.throttle();
  29242. }
  29243. ui.updateMode();
  29244. });
  29245. if (isSplitUiMode(editor)) {
  29246. editor.on('ElementScroll', (_args) => {
  29247. // When the scroller containing the editor scrolls, update the Ui positions
  29248. ui.update(Docking.refresh);
  29249. });
  29250. }
  29251. // Bind to async load events and trigger a content resize event if the size has changed
  29252. // This is handling resizing based on anything loading inside the content (e.g. img tags)
  29253. const elementLoad = unbindable();
  29254. elementLoad.set(capture(SugarElement.fromDom(editor.getBody()), 'load', (e) => resizeContent(e.raw)));
  29255. editor.on('remove', () => {
  29256. elementLoad.clear();
  29257. });
  29258. };
  29259. const render = (editor, uiRefs, rawUiConfig, backstage, args) => {
  29260. const { mainUi } = uiRefs;
  29261. // This is used to store the reference to the header part of OuterContainer, which is
  29262. // *not* created by this module. This reference is used to make sure that we only bind
  29263. // events for an inline container *once* ... because our show function is just the
  29264. // InlineHeader's show function if this reference is already set. We pass it through to
  29265. // InlineHeader because InlineHeader will depend on it.
  29266. const floatContainer = value$2();
  29267. const targetElm = SugarElement.fromDom(args.targetNode);
  29268. const ui = InlineHeader(editor, targetElm, uiRefs, backstage, floatContainer);
  29269. const toolbarPersist = isToolbarPersist(editor);
  29270. // eslint-disable-next-line @typescript-eslint/no-floating-promises
  29271. inline(editor);
  29272. const render = () => {
  29273. // Because we set the floatContainer immediately afterwards, this is just telling us
  29274. // if we have already called this code (e.g. show, hide, show) - then don't do anything
  29275. // more than show. It's a pretty messy way of ensuring that all the code that follows
  29276. // this `if` block is only executed once (setting up events etc.). So the first call
  29277. // to `render` will execute it, but the second call won't. This `render` function is
  29278. // used for most of the "show" handlers here, so the function can be invoked either
  29279. // for the first time, or or just because something is being show again, after being
  29280. // toggled to hidden earlier.
  29281. if (floatContainer.isSet()) {
  29282. ui.show();
  29283. return;
  29284. }
  29285. // Set up the header part of OuterContainer. Once configured, the `InlineHeader` code
  29286. // will use it when setting up and updating the Ui. This module uses it mainly just to
  29287. // allow us to call `render` multiple times, but only have it execute the setup code once.
  29288. floatContainer.set(OuterContainer.getHeader(mainUi.outerContainer).getOrDie());
  29289. // `uiContainer` handles *where* the motherhips get added by default. Currently, uiContainer
  29290. // will mostly be the <body> of the document (unless it's a ShadowRoot). When using ui_mode: split,
  29291. // the main mothership (which includes the toolbar) and popup sinks will be added as siblings of
  29292. // the target element, so that they have the same scrolling context / environment
  29293. const uiContainer = getUiContainer(editor);
  29294. // Position the motherships based on the editor Ui options.
  29295. if (isSplitUiMode(editor)) {
  29296. attachSystemAfter(targetElm, mainUi.mothership);
  29297. // Only in ui_mode: split, do we have a separate popup sink
  29298. attachSystemAfter(targetElm, uiRefs.popupUi.mothership);
  29299. }
  29300. else {
  29301. attachSystem(uiContainer, mainUi.mothership);
  29302. }
  29303. // NOTE: In UiRefs, dialogUi and popupUi refer to the same thing if ui_mode: combined
  29304. attachSystem(uiContainer, uiRefs.dialogUi.mothership);
  29305. const setup = () => {
  29306. // Unlike menubar below which uses OuterContainer directly, this level of abstraction is
  29307. // required because of the different types of toolbars available (e.g. multiple vs single)
  29308. setToolbar(editor, uiRefs, rawUiConfig, backstage);
  29309. OuterContainer.setMenubar(mainUi.outerContainer, identifyMenus(editor, rawUiConfig));
  29310. // Initialise the toolbar - set initial positioning then show
  29311. ui.show();
  29312. setupEvents(editor, targetElm, ui, toolbarPersist);
  29313. editor.nodeChanged();
  29314. };
  29315. if (toolbarPersist) {
  29316. // TINY-10482: for `toolbar_persist: true` we need to wait for the skin to be loaded before showing the toolbar/menubar.
  29317. // Without this, there's the occasional chance that the toolbar/menubar could be set/shown before the skin has finished
  29318. // loading, which causes CSS issues.
  29319. editor.once('SkinLoaded', setup);
  29320. }
  29321. else {
  29322. setup();
  29323. }
  29324. };
  29325. editor.on('show', render);
  29326. editor.on('hide', ui.hide);
  29327. if (!toolbarPersist) {
  29328. editor.on('focus', render);
  29329. editor.on('blur', ui.hide);
  29330. }
  29331. editor.on('init', () => {
  29332. if (editor.hasFocus() || toolbarPersist) {
  29333. render();
  29334. }
  29335. });
  29336. setupEventsForUi(editor, uiRefs);
  29337. const api = {
  29338. show: render,
  29339. hide: ui.hide,
  29340. setEnabled: (state) => {
  29341. const eventType = state ? 'setEnabled' : 'setDisabled';
  29342. broadcastEvents(uiRefs, eventType);
  29343. },
  29344. isEnabled: () => !Disabling.isDisabled(mainUi.outerContainer)
  29345. };
  29346. return {
  29347. editorContainer: mainUi.outerContainer.element.dom,
  29348. api
  29349. };
  29350. };
  29351. var Inline = /*#__PURE__*/Object.freeze({
  29352. __proto__: null,
  29353. render: render
  29354. });
  29355. const LazyUiReferences = () => {
  29356. const dialogUi = value$2();
  29357. const popupUi = value$2();
  29358. const mainUi = value$2();
  29359. const lazyGetInOuterOrDie = (label, f) => () => mainUi.get().bind((oc) => f(oc.outerContainer)).getOrDie(`Could not find ${label} element in OuterContainer`);
  29360. // TINY-9226: If the motherships are the same, return just the dialog Ui of them (ui_mode: combined mode)
  29361. const getUiMotherships = () => {
  29362. const optDialogMothership = dialogUi.get().map((ui) => ui.mothership);
  29363. const optPopupMothership = popupUi.get().map((ui) => ui.mothership);
  29364. return optDialogMothership.fold(() => optPopupMothership.toArray(), (dm) => optPopupMothership.fold(() => [dm], (pm) => eq(dm.element, pm.element) ? [dm] : [dm, pm]));
  29365. };
  29366. return {
  29367. dialogUi,
  29368. popupUi,
  29369. mainUi,
  29370. getUiMotherships,
  29371. lazyGetInOuterOrDie
  29372. };
  29373. };
  29374. // TODO: Find a better way of doing this. We probably don't want to just listen to
  29375. // editor events. Having an API available like WindowManager would be the best option
  29376. const showContextToolbarEvent = 'contexttoolbar-show';
  29377. const hideContextToolbarEvent = 'contexttoolbar-hide';
  29378. const getFormApi = (input, valueState, focusfallbackElement) => {
  29379. return ({
  29380. setInputEnabled: (state) => {
  29381. if (!state && focusfallbackElement) {
  29382. focus$4(focusfallbackElement);
  29383. }
  29384. Disabling.set(input, !state);
  29385. },
  29386. isInputEnabled: () => !Disabling.isDisabled(input),
  29387. hide: () => {
  29388. emit(input, sandboxClose());
  29389. },
  29390. back: () => {
  29391. emit(input, backSlideEvent);
  29392. },
  29393. getValue: () => {
  29394. return valueState.get().getOrThunk(() => Representing.getValue(input));
  29395. },
  29396. setValue: (value) => {
  29397. if (input.getSystem().isConnected()) {
  29398. Representing.setValue(input, value);
  29399. }
  29400. else {
  29401. valueState.set(value);
  29402. }
  29403. }
  29404. });
  29405. };
  29406. const getFormParentApi = (comp, valueState, focusfallbackElement) => {
  29407. const parent$1 = parent(comp.element);
  29408. const parentCompOpt = parent$1.bind((parent) => comp.getSystem().getByDom(parent).toOptional());
  29409. return getFormApi(parentCompOpt.getOr(comp), valueState, focusfallbackElement);
  29410. };
  29411. const runOnExecute = (memInput, original, valueState) => run$1(internalToolbarButtonExecute, (comp, se) => {
  29412. const input = memInput.get(comp);
  29413. const formApi = getFormApi(input, valueState, comp.element);
  29414. original.onAction(formApi, se.event.buttonApi);
  29415. });
  29416. const renderContextButton = (memInput, button, providers, valueState) => {
  29417. const { primary, ...rest } = button.original;
  29418. const bridged = getOrDie(createToolbarButton({
  29419. ...rest,
  29420. type: 'button',
  29421. onAction: noop
  29422. }));
  29423. return renderToolbarButtonWith(bridged, providers, [
  29424. runOnExecute(memInput, button, valueState)
  29425. ]);
  29426. };
  29427. const renderContextToggleButton = (memInput, button, providers, valueState) => {
  29428. const { primary, ...rest } = button.original;
  29429. const bridged = getOrDie(createToggleButton({
  29430. ...rest,
  29431. type: 'togglebutton',
  29432. onAction: noop
  29433. }));
  29434. return renderToolbarToggleButtonWith(bridged, providers, [
  29435. runOnExecute(memInput, button, valueState)
  29436. ]);
  29437. };
  29438. const isToggleButton = (button) => button.type === 'contextformtogglebutton';
  29439. const generateOne = (memInput, button, providersBackstage, valueState) => {
  29440. if (isToggleButton(button)) {
  29441. return renderContextToggleButton(memInput, button, providersBackstage, valueState);
  29442. }
  29443. else {
  29444. return renderContextButton(memInput, button, providersBackstage, valueState);
  29445. }
  29446. };
  29447. const generate = (memInput, buttons, providersBackstage, valueState) => {
  29448. const mementos = map$2(buttons, (button) => record(generateOne(memInput, button, providersBackstage, valueState)));
  29449. const asSpecs = () => map$2(mementos, (mem) => mem.asSpec());
  29450. const findPrimary = (compInSystem) => findMap(buttons, (button, i) => {
  29451. if (button.primary) {
  29452. return Optional.from(mementos[i]).bind((mem) => mem.getOpt(compInSystem)).filter(not(Disabling.isDisabled));
  29453. }
  29454. else {
  29455. return Optional.none();
  29456. }
  29457. });
  29458. return {
  29459. asSpecs,
  29460. findPrimary
  29461. };
  29462. };
  29463. const renderContextFormSizeInput = (ctx, providersBackstage, onEnter, valueState) => {
  29464. const { width, height } = ctx.initValue();
  29465. let converter = noSizeConversion;
  29466. const enabled = true;
  29467. const ratioEvent = generate$6('ratio-event');
  29468. const getApi = (comp) => getFormApi(comp, valueState);
  29469. const makeIcon = (iconName) => render$4(iconName, { tag: 'span', classes: ['tox-icon', 'tox-lock-icon__' + iconName] }, providersBackstage.icons);
  29470. const disabled = () => !enabled;
  29471. const label = ctx.label.getOr('Constrain proportions');
  29472. const translatedLabel = providersBackstage.translate(label);
  29473. const pLock = FormCoupledInputs.parts.lock({
  29474. dom: {
  29475. tag: 'button',
  29476. classes: ['tox-lock', 'tox-lock-context-form-size-input', 'tox-button', 'tox-button--naked', 'tox-button--icon'],
  29477. attributes: {
  29478. 'aria-label': translatedLabel,
  29479. 'data-mce-name': label
  29480. }
  29481. },
  29482. components: [
  29483. makeIcon('lock'),
  29484. makeIcon('unlock')
  29485. ],
  29486. buttonBehaviours: derive$1([
  29487. Disabling.config({ disabled }),
  29488. Tabstopping.config({}),
  29489. Tooltipping.config(providersBackstage.tooltips.getConfig({
  29490. tooltipText: translatedLabel
  29491. }))
  29492. ])
  29493. });
  29494. const formGroup = (components) => ({
  29495. dom: {
  29496. tag: 'div',
  29497. classes: ['tox-context-form__group']
  29498. },
  29499. components
  29500. });
  29501. const goToParent = (comp) => {
  29502. const focussableWrapperOpt = ancestor$1(comp.element, 'div.tox-focusable-wrapper');
  29503. return focussableWrapperOpt.fold(Optional.none, (focussableWrapper) => {
  29504. focus$4(focussableWrapper);
  29505. return Optional.some(true);
  29506. });
  29507. };
  29508. const getFieldPart = (isField1) => FormField.parts.field({
  29509. factory: Input,
  29510. inputClasses: ['tox-textfield', 'tox-toolbar-textfield', 'tox-textfield-size'],
  29511. data: isField1 ? width : height,
  29512. inputBehaviours: derive$1([
  29513. Disabling.config({ disabled }),
  29514. Tabstopping.config({}),
  29515. config('size-input-toolbar-events', [
  29516. run$1(focusin(), (component, _simulatedEvent) => {
  29517. emitWith(component, ratioEvent, { isField1 });
  29518. })
  29519. ]),
  29520. Keying.config({ mode: 'special', onEnter, onEscape: goToParent })
  29521. ]),
  29522. selectOnFocus: false
  29523. });
  29524. const getLabel = (label) => ({
  29525. dom: {
  29526. tag: 'label',
  29527. classes: ['tox-label']
  29528. },
  29529. components: [
  29530. text$2(providersBackstage.translate(label))
  29531. ]
  29532. });
  29533. const focusableWrapper = (field) => ({
  29534. dom: {
  29535. tag: 'div',
  29536. classes: ['tox-focusable-wrapper', 'tox-toolbar-nav-item'],
  29537. },
  29538. components: [field],
  29539. behaviours: derive$1([
  29540. Tabstopping.config({}),
  29541. Focusing.config({}),
  29542. Keying.config({
  29543. mode: 'special',
  29544. onEnter: (comp) => {
  29545. const focussableInputOpt = descendant(comp.element, 'input');
  29546. return focussableInputOpt.fold(Optional.none, (focussableInput) => {
  29547. focus$4(focussableInput);
  29548. return Optional.some(true);
  29549. });
  29550. }
  29551. })
  29552. ])
  29553. });
  29554. const widthField = focusableWrapper(FormCoupledInputs.parts.field1(formGroup([FormField.parts.label(getLabel('Width:')), getFieldPart(true)])));
  29555. const heightField = focusableWrapper(FormCoupledInputs.parts.field2(formGroup([FormField.parts.label(getLabel('Height:')), getFieldPart(false)])));
  29556. const editorOffCell = Cell(noop);
  29557. const controlLifecycleHandlers = [
  29558. onControlAttached({
  29559. onBeforeSetup: (comp) => descendant(comp.element, 'input').each(focus$4),
  29560. onSetup: ctx.onSetup,
  29561. getApi
  29562. }, editorOffCell),
  29563. onContextFormControlDetached({ getApi }, editorOffCell, valueState),
  29564. ];
  29565. return FormCoupledInputs.sketch({
  29566. dom: {
  29567. tag: 'div',
  29568. classes: ['tox-context-form__group']
  29569. },
  29570. components: [
  29571. // NOTE: Form coupled inputs to the FormField.sketch themselves.
  29572. widthField,
  29573. formGroup([
  29574. pLock
  29575. ]),
  29576. heightField
  29577. ],
  29578. field1Name: 'width',
  29579. field2Name: 'height',
  29580. locked: true,
  29581. markers: {
  29582. lockClass: 'tox-locked'
  29583. },
  29584. onLockedChange: (current, other, _lock) => {
  29585. parseSize(Representing.getValue(current)).each((size) => {
  29586. converter(size).each((newSize) => {
  29587. Representing.setValue(other, formatSize(newSize));
  29588. });
  29589. });
  29590. },
  29591. onInput: (current) => emit(current, formInputEvent),
  29592. coupledFieldBehaviours: derive$1([
  29593. Focusing.config({}),
  29594. Keying.config({
  29595. mode: 'flow',
  29596. focusInside: FocusInsideModes.OnEnterOrSpaceMode,
  29597. cycles: false,
  29598. selector: 'button, .tox-focusable-wrapper',
  29599. }),
  29600. Disabling.config({
  29601. disabled,
  29602. onDisabled: (comp) => {
  29603. FormCoupledInputs.getField1(comp).bind(FormField.getField).each(Disabling.disable);
  29604. FormCoupledInputs.getField2(comp).bind(FormField.getField).each(Disabling.disable);
  29605. FormCoupledInputs.getLock(comp).each(Disabling.disable);
  29606. },
  29607. onEnabled: (comp) => {
  29608. FormCoupledInputs.getField1(comp).bind(FormField.getField).each(Disabling.enable);
  29609. FormCoupledInputs.getField2(comp).bind(FormField.getField).each(Disabling.enable);
  29610. FormCoupledInputs.getLock(comp).each(Disabling.enable);
  29611. }
  29612. }),
  29613. toggleOnReceive(() => providersBackstage.checkUiComponentContext('mode:design')),
  29614. config('size-input-toolbar-events2', [
  29615. run$1(ratioEvent, (component, simulatedEvent) => {
  29616. const isField1 = simulatedEvent.event.isField1;
  29617. const optCurrent = isField1 ? FormCoupledInputs.getField1(component) : FormCoupledInputs.getField2(component);
  29618. const optOther = isField1 ? FormCoupledInputs.getField2(component) : FormCoupledInputs.getField1(component);
  29619. const value1 = optCurrent.map(Representing.getValue).getOr('');
  29620. const value2 = optOther.map(Representing.getValue).getOr('');
  29621. converter = makeRatioConverter(value1, value2);
  29622. }),
  29623. run$1(formInputEvent, (input) => ctx.onInput(getApi(input))),
  29624. ...controlLifecycleHandlers,
  29625. ])
  29626. ])
  29627. });
  29628. };
  29629. const createContextFormFieldFromParts = (pLabel, pField, providers) => FormField.sketch({
  29630. dom: {
  29631. tag: 'div',
  29632. classes: ['tox-context-form__group']
  29633. },
  29634. components: [...pLabel.toArray(), pField],
  29635. fieldBehaviours: derive$1([
  29636. Disabling.config({
  29637. disabled: () => providers.checkUiComponentContext('mode:design').shouldDisable,
  29638. onDisabled: (comp) => {
  29639. focusParent(comp);
  29640. FormField.getField(comp).each(Disabling.disable);
  29641. },
  29642. onEnabled: (comp) => {
  29643. FormField.getField(comp).each(Disabling.enable);
  29644. }
  29645. }),
  29646. ])
  29647. });
  29648. const renderContextFormSliderInput = (ctx, providers, onEnter, valueState) => {
  29649. const editorOffCell = Cell(noop);
  29650. const getApi = (comp) => getFormParentApi(comp, valueState);
  29651. const pLabel = ctx.label.map((label) => FormField.parts.label({
  29652. dom: { tag: 'label', classes: ['tox-label'] },
  29653. components: [text$2(providers.translate(label))]
  29654. }));
  29655. const pField = FormField.parts.field({
  29656. factory: Input,
  29657. type: 'range',
  29658. inputClasses: ['tox-toolbar-slider__input', 'tox-toolbar-nav-item'],
  29659. inputAttributes: {
  29660. min: String(ctx.min()),
  29661. max: String(ctx.max())
  29662. },
  29663. data: ctx.initValue().toString(),
  29664. fromInputValue: (value) => toFloat(value).getOr(ctx.min()),
  29665. toInputValue: (value) => String(value),
  29666. inputBehaviours: derive$1([
  29667. Disabling.config({
  29668. disabled: () => providers.checkUiComponentContext('mode:design').shouldDisable
  29669. }),
  29670. toggleOnReceive(() => providers.checkUiComponentContext('mode:design')),
  29671. Keying.config({
  29672. mode: 'special',
  29673. onEnter,
  29674. // These two lines need to be tested. They are about left and right bypassing
  29675. // any keyboard handling, and allowing left and right to be processed by the input
  29676. // Maybe this should go in an alloy sketch for Input?
  29677. onLeft: (comp, se) => {
  29678. se.cut();
  29679. return Optional.none();
  29680. },
  29681. onRight: (comp, se) => {
  29682. se.cut();
  29683. return Optional.none();
  29684. }
  29685. }),
  29686. config('slider-events', [
  29687. onControlAttached({
  29688. onSetup: ctx.onSetup,
  29689. getApi,
  29690. onBeforeSetup: Keying.focusIn
  29691. }, editorOffCell),
  29692. onContextFormControlDetached({ getApi }, editorOffCell, valueState),
  29693. run$1(input(), (comp) => {
  29694. ctx.onInput(getApi(comp));
  29695. })
  29696. ])
  29697. ])
  29698. });
  29699. return createContextFormFieldFromParts(pLabel, pField, providers);
  29700. };
  29701. const renderContextFormTextInput = (ctx, providers, onEnter, valueState) => {
  29702. const editorOffCell = Cell(noop);
  29703. const getFormApi = (comp) => getFormParentApi(comp, valueState);
  29704. const pLabel = ctx.label.map((label) => FormField.parts.label({
  29705. dom: { tag: 'label', classes: ['tox-label'] },
  29706. components: [text$2(providers.translate(label))]
  29707. }));
  29708. const placeholder = ctx.placeholder.map((p) => ({ placeholder: providers.translate(p) })).getOr({});
  29709. const inputAttributes = {
  29710. ...placeholder,
  29711. };
  29712. const pField = FormField.parts.field({
  29713. factory: Input,
  29714. inputClasses: ['tox-toolbar-textfield', 'tox-toolbar-nav-item'],
  29715. inputAttributes,
  29716. data: ctx.initValue(),
  29717. selectOnFocus: true,
  29718. inputBehaviours: derive$1([
  29719. Disabling.config({
  29720. disabled: () => providers.checkUiComponentContext('mode:design').shouldDisable
  29721. }),
  29722. toggleOnReceive(() => providers.checkUiComponentContext('mode:design')),
  29723. Keying.config({
  29724. mode: 'special',
  29725. onEnter,
  29726. // These two lines need to be tested. They are about left and right bypassing
  29727. // any keyboard handling, and allowing left and right to be processed by the input
  29728. // Maybe this should go in an alloy sketch for Input?
  29729. onLeft: (comp, se) => {
  29730. se.cut();
  29731. return Optional.none();
  29732. },
  29733. onRight: (comp, se) => {
  29734. se.cut();
  29735. return Optional.none();
  29736. }
  29737. }),
  29738. config('input-events', [
  29739. onControlAttached({
  29740. onSetup: ctx.onSetup,
  29741. getApi: (comp) => {
  29742. const closestFocussableOpt = ancestor$1(comp.element, '.tox-toolbar').bind((toolbar) => descendant(toolbar, 'button:enabled'));
  29743. return closestFocussableOpt.fold(() => getFormParentApi(comp, valueState), (closestFocussable) => getFormParentApi(comp, valueState, closestFocussable));
  29744. },
  29745. onBeforeSetup: Keying.focusIn
  29746. }, editorOffCell),
  29747. onContextFormControlDetached({ getApi: getFormApi }, editorOffCell, valueState),
  29748. run$1(input(), (comp) => {
  29749. ctx.onInput(getFormApi(comp));
  29750. })
  29751. ])
  29752. ])
  29753. });
  29754. return createContextFormFieldFromParts(pLabel, pField, providers);
  29755. };
  29756. const buildInitGroup = (f, ctx, providers) => {
  29757. const valueState = value$2();
  29758. const onEnter = (input) => {
  29759. return startCommands.findPrimary(input).orThunk(() => endCommands.findPrimary(input)).map((primary) => {
  29760. emitExecute(primary);
  29761. return true;
  29762. });
  29763. };
  29764. const memInput = record(f(providers, onEnter, valueState));
  29765. const commandParts = partition$3(ctx.commands, (command) => command.align === 'start');
  29766. const startCommands = generate(memInput, commandParts.pass, providers, valueState);
  29767. const endCommands = generate(memInput, commandParts.fail, providers, valueState);
  29768. return filter$2([
  29769. {
  29770. title: Optional.none(),
  29771. label: Optional.none(),
  29772. items: startCommands.asSpecs()
  29773. },
  29774. {
  29775. title: Optional.none(),
  29776. label: Optional.none(),
  29777. items: [memInput.asSpec()]
  29778. },
  29779. {
  29780. title: Optional.none(),
  29781. label: Optional.none(),
  29782. items: endCommands.asSpecs()
  29783. }
  29784. ], (group) => group.items.length > 0);
  29785. };
  29786. const buildInitGroups = (ctx, providers) => {
  29787. switch (ctx.type) {
  29788. case 'contextform': return buildInitGroup(curry(renderContextFormTextInput, ctx), ctx, providers);
  29789. case 'contextsliderform': return buildInitGroup(curry(renderContextFormSliderInput, ctx), ctx, providers);
  29790. case 'contextsizeinputform': return buildInitGroup(curry(renderContextFormSizeInput, ctx), ctx, providers);
  29791. }
  29792. };
  29793. const renderContextForm = (toolbarType, ctx, providers) => renderToolbar({
  29794. type: toolbarType,
  29795. uid: generate$6('context-toolbar'),
  29796. initGroups: buildInitGroups(ctx, providers),
  29797. onEscape: Optional.none,
  29798. cyclicKeying: true,
  29799. providers
  29800. });
  29801. const ContextForm = {
  29802. renderContextForm,
  29803. buildInitGroups
  29804. };
  29805. // The "threshold" here is the amount of overlap. To make the overlap check
  29806. // be more permissive (return true for 'almost' an overlap), use a negative
  29807. // threshold value
  29808. const isVerticalOverlap = (a, b, threshold) => b.bottom - a.y >= threshold && a.bottom - b.y >= threshold;
  29809. const getRangeRect = (rng) => {
  29810. const rect = rng.getBoundingClientRect();
  29811. // Some ranges (eg <td><br></td>) will return a 0x0 rect, so we'll need to calculate it from the leaf instead
  29812. if (rect.height <= 0 && rect.width <= 0) {
  29813. const leaf$1 = leaf(SugarElement.fromDom(rng.startContainer), rng.startOffset).element;
  29814. const elm = isText(leaf$1) ? parent(leaf$1) : Optional.some(leaf$1);
  29815. return elm.filter(isElement$1)
  29816. .map((e) => e.dom.getBoundingClientRect())
  29817. // We have nothing valid, so just fallback to the original rect
  29818. .getOr(rect);
  29819. }
  29820. else {
  29821. return rect;
  29822. }
  29823. };
  29824. const getSelectionBounds = (editor) => {
  29825. const rng = editor.selection.getRng();
  29826. const rect = getRangeRect(rng);
  29827. if (editor.inline) {
  29828. const scroll = get$b();
  29829. return bounds(scroll.left + rect.left, scroll.top + rect.top, rect.width, rect.height);
  29830. }
  29831. else {
  29832. // Translate to the top level document, as rect is relative to the iframe viewport
  29833. const bodyPos = absolute$2(SugarElement.fromDom(editor.getBody()));
  29834. return bounds(bodyPos.x + rect.left, bodyPos.y + rect.top, rect.width, rect.height);
  29835. }
  29836. };
  29837. const getAnchorElementBounds = (editor, lastElement) => lastElement
  29838. .filter((elem) => inBody(elem) && isHTMLElement(elem))
  29839. .map(absolute$2)
  29840. .getOrThunk(() => getSelectionBounds(editor));
  29841. const getHorizontalBounds = (contentAreaBox, viewportBounds, margin) => {
  29842. const x = Math.max(contentAreaBox.x + margin, viewportBounds.x);
  29843. const right = Math.min(contentAreaBox.right - margin, viewportBounds.right);
  29844. return { x, width: right - x };
  29845. };
  29846. const getVerticalBounds = (editor, contentAreaBox, viewportBounds, isToolbarLocationTop, toolbarType, margin) => {
  29847. const container = SugarElement.fromDom(editor.getContainer());
  29848. const header = descendant(container, '.tox-editor-header').getOr(container);
  29849. const headerBox = box$1(header);
  29850. const isToolbarBelowContentArea = headerBox.y >= contentAreaBox.bottom;
  29851. const isToolbarAbove = isToolbarLocationTop && !isToolbarBelowContentArea;
  29852. // Scenario toolbar top & inline: Bottom of the header -> Bottom of the viewport
  29853. if (editor.inline && isToolbarAbove) {
  29854. return {
  29855. y: Math.max(headerBox.bottom + margin, viewportBounds.y),
  29856. bottom: viewportBounds.bottom
  29857. };
  29858. }
  29859. // Scenario toolbar top & inline: Top of the viewport -> Top of the header
  29860. if (editor.inline && !isToolbarAbove) {
  29861. return {
  29862. y: viewportBounds.y,
  29863. bottom: Math.min(headerBox.y - margin, viewportBounds.bottom)
  29864. };
  29865. }
  29866. // Allow line based context toolbar to overlap the statusbar
  29867. const containerBounds = toolbarType === 'line' ? box$1(container) : contentAreaBox;
  29868. // Scenario toolbar bottom & Iframe: Bottom of the header -> Bottom of the editor container
  29869. if (isToolbarAbove) {
  29870. return {
  29871. y: Math.max(headerBox.bottom + margin, viewportBounds.y),
  29872. bottom: Math.min(containerBounds.bottom - margin, viewportBounds.bottom)
  29873. };
  29874. }
  29875. // Scenario toolbar bottom & Iframe: Top of the editor container -> Top of the header
  29876. return {
  29877. y: Math.max(containerBounds.y + margin, viewportBounds.y),
  29878. bottom: Math.min(headerBox.y - margin, viewportBounds.bottom)
  29879. };
  29880. };
  29881. const getContextToolbarBounds = (editor, sharedBackstage, toolbarType, margin = 0) => {
  29882. const viewportBounds = getBounds$1(window);
  29883. const contentAreaBox = box$1(SugarElement.fromDom(editor.getContentAreaContainer()));
  29884. const toolbarOrMenubarEnabled = isMenubarEnabled(editor) || isToolbarEnabled(editor) || isMultipleToolbars(editor);
  29885. const { x, width } = getHorizontalBounds(contentAreaBox, viewportBounds, margin);
  29886. // Create bounds that lets the context toolbar overflow outside the content area, but remains in the viewport
  29887. if (editor.inline && !toolbarOrMenubarEnabled) {
  29888. return bounds(x, viewportBounds.y, width, viewportBounds.height);
  29889. }
  29890. else {
  29891. const isToolbarTop = sharedBackstage.header.isPositionedAtTop();
  29892. const { y, bottom } = getVerticalBounds(editor, contentAreaBox, viewportBounds, isToolbarTop, toolbarType, margin);
  29893. return bounds(x, y, width, bottom - y);
  29894. }
  29895. };
  29896. const bubbleSize$1 = 12;
  29897. const bubbleAlignments$1 = {
  29898. valignCentre: [],
  29899. alignCentre: [],
  29900. alignLeft: ['tox-pop--align-left'],
  29901. alignRight: ['tox-pop--align-right'],
  29902. right: ['tox-pop--right'],
  29903. left: ['tox-pop--left'],
  29904. bottom: ['tox-pop--bottom'],
  29905. top: ['tox-pop--top'],
  29906. inset: ['tox-pop--inset']
  29907. };
  29908. const anchorOverrides = {
  29909. maxHeightFunction: expandable$1(),
  29910. maxWidthFunction: expandable()
  29911. };
  29912. const isEntireElementSelected = (editor, elem) => {
  29913. const rng = editor.selection.getRng();
  29914. const leaf$1 = leaf(SugarElement.fromDom(rng.startContainer), rng.startOffset);
  29915. return rng.startContainer === rng.endContainer && rng.startOffset === rng.endOffset - 1 && eq(leaf$1.element, elem);
  29916. };
  29917. const preservePosition = (elem, position, f) => {
  29918. const currentPosition = getRaw(elem, 'position');
  29919. set$7(elem, 'position', position);
  29920. const result = f(elem);
  29921. currentPosition.each((pos) => set$7(elem, 'position', pos));
  29922. return result;
  29923. };
  29924. // Don't use an inset layout when using a selection/line based anchors as it'll cover the content and can't be moved out the way
  29925. const shouldUseInsetLayouts = (position) => position === 'node';
  29926. /**
  29927. * This function is designed to attempt to intelligently detect where the contextbar should be anchored when using an inside
  29928. * layout. It will attempt to preserve the previous outside placement when anchoring to the same element. However, when the
  29929. * placement is re-triggered (e.g. not triggered by a reposition) and the current editor selection overlaps with the contextbar,
  29930. * then the anchoring should flip from the previous position to avoid conflicting with the selection.
  29931. */
  29932. const determineInsetLayout = (editor, contextbar, elem, data, bounds) => {
  29933. const selectionBounds = getSelectionBounds(editor);
  29934. const isSameAnchorElement = data.lastElement().exists((prev) => eq(elem, prev));
  29935. if (isEntireElementSelected(editor, elem)) {
  29936. // The entire anchor element is selected so it'll always overlap with the selection, in which case just
  29937. // preserve or show at the top for a new anchor element.
  29938. return isSameAnchorElement ? preserve$1 : north$1;
  29939. }
  29940. else if (isSameAnchorElement) {
  29941. // Preserve the position, get the bounds and then see if we have an overlap.
  29942. // If overlapping and this wasn't triggered by a reposition then flip the placement
  29943. return preservePosition(contextbar, data.getMode(), () => {
  29944. // TINY-8890: The negative 20px threshold here was arrived at by considering the use
  29945. // case of a table with default heights for the rows. The threshold had to be
  29946. // large enough so that the context toolbar would not prevent the user selecting
  29947. // in the row containing the context toolbar.
  29948. const isOverlapping = isVerticalOverlap(selectionBounds, box$1(contextbar), -20);
  29949. return isOverlapping && !data.isReposition() ? flip : preserve$1;
  29950. });
  29951. }
  29952. else {
  29953. // Attempt to find the best layout to use that won't cause an overlap for the new anchor element
  29954. // Note: In fixed positioning mode we need to translate by adding the scroll pos to get the absolute position
  29955. const yBounds = data.getMode() === 'fixed' ? bounds.y + get$b().top : bounds.y;
  29956. const contextbarHeight = get$d(contextbar) + bubbleSize$1;
  29957. return yBounds + contextbarHeight <= selectionBounds.y ? north$1 : south$1;
  29958. }
  29959. };
  29960. const getAnchorSpec$2 = (editor, mobile, data, position) => {
  29961. // IMPORTANT: We lazily determine the layout here so that we only do the calculations if absolutely necessary
  29962. const smartInsetLayout = (elem) => (anchor, element, bubbles, placee, bounds) => {
  29963. const layout = determineInsetLayout(editor, placee, elem, data, bounds);
  29964. // Adjust the anchor box to use the passed y bound coords so that we simulate a "docking" type of behaviour
  29965. const newAnchor = {
  29966. ...anchor,
  29967. y: bounds.y,
  29968. height: bounds.height
  29969. };
  29970. return {
  29971. ...layout(newAnchor, element, bubbles, placee, bounds),
  29972. // Ensure this is always the preferred option if no outside layouts fit
  29973. alwaysFit: true
  29974. };
  29975. };
  29976. const getInsetLayouts = (elem) => shouldUseInsetLayouts(position) ? [smartInsetLayout(elem)] : [];
  29977. // On desktop we prioritise north-then-south because it's cleaner, but on mobile we prioritise south to try to avoid overlapping with native context toolbars
  29978. const desktopAnchorSpecLayouts = {
  29979. onLtr: (elem) => [north$2, south$2, northeast$2, southeast$2, northwest$2, southwest$2].concat(getInsetLayouts(elem)),
  29980. onRtl: (elem) => [north$2, south$2, northwest$2, southwest$2, northeast$2, southeast$2].concat(getInsetLayouts(elem))
  29981. };
  29982. const mobileAnchorSpecLayouts = {
  29983. onLtr: (elem) => [south$2, southeast$2, southwest$2, northeast$2, northwest$2, north$2].concat(getInsetLayouts(elem)),
  29984. onRtl: (elem) => [south$2, southwest$2, southeast$2, northwest$2, northeast$2, north$2].concat(getInsetLayouts(elem))
  29985. };
  29986. return mobile ? mobileAnchorSpecLayouts : desktopAnchorSpecLayouts;
  29987. };
  29988. const getAnchorLayout = (editor, position, isTouch, data) => {
  29989. if (position === 'line') {
  29990. return {
  29991. bubble: nu$6(bubbleSize$1, 0, bubbleAlignments$1),
  29992. layouts: {
  29993. onLtr: () => [east$2],
  29994. onRtl: () => [west$2]
  29995. },
  29996. overrides: anchorOverrides
  29997. };
  29998. }
  29999. else {
  30000. return {
  30001. // Ensure that inset layouts use a 1px bubble since we're hiding the bubble arrow
  30002. bubble: nu$6(0, bubbleSize$1, bubbleAlignments$1, 1 / bubbleSize$1),
  30003. layouts: getAnchorSpec$2(editor, isTouch, data, position),
  30004. overrides: anchorOverrides
  30005. };
  30006. }
  30007. };
  30008. const matchTargetWith = (elem, candidates) => {
  30009. const ctxs = filter$2(candidates, (toolbarApi) => toolbarApi.predicate(elem.dom));
  30010. // TODO: somehow type this properly (Arr.partition can't)
  30011. // e.g. here pass is Toolbar.ContextToolbar and fail is Toolbar.ContextForm
  30012. const { pass, fail } = partition$3(ctxs, (t) => t.type === 'contexttoolbar');
  30013. return {
  30014. contextToolbars: pass,
  30015. contextForms: fail
  30016. };
  30017. };
  30018. const filterByPositionForStartNode = (toolbars) => {
  30019. if (toolbars.length <= 1) {
  30020. return toolbars;
  30021. }
  30022. else {
  30023. const doesPositionExist = (value) => exists(toolbars, (t) => t.position === value);
  30024. const filterToolbarsByPosition = (value) => filter$2(toolbars, (t) => t.position === value);
  30025. const hasSelectionToolbars = doesPositionExist('selection');
  30026. const hasNodeToolbars = doesPositionExist('node');
  30027. if (hasSelectionToolbars || hasNodeToolbars) {
  30028. if (hasNodeToolbars && hasSelectionToolbars) {
  30029. // if there's a mix, change the 'selection' toolbars to 'node' so there's no positioning confusion
  30030. const nodeToolbars = filterToolbarsByPosition('node');
  30031. const selectionToolbars = map$2(filterToolbarsByPosition('selection'), (t) => ({ ...t, position: 'node' }));
  30032. return nodeToolbars.concat(selectionToolbars);
  30033. }
  30034. else {
  30035. return hasSelectionToolbars ? filterToolbarsByPosition('selection') : filterToolbarsByPosition('node');
  30036. }
  30037. }
  30038. else {
  30039. return filterToolbarsByPosition('line');
  30040. }
  30041. }
  30042. };
  30043. const filterByPositionForAncestorNode = (toolbars) => {
  30044. if (toolbars.length <= 1) {
  30045. return toolbars;
  30046. }
  30047. else {
  30048. const findPosition = (value) => find$5(toolbars, (t) => t.position === value);
  30049. // prioritise position by 'selection' -> 'node' -> 'line'
  30050. const basePosition = findPosition('selection')
  30051. .orThunk(() => findPosition('node'))
  30052. .orThunk(() => findPosition('line'))
  30053. .map((t) => t.position);
  30054. return basePosition.fold(() => [], (pos) => filter$2(toolbars, (t) => t.position === pos));
  30055. }
  30056. };
  30057. const matchStartNode = (elem, nodeCandidates, editorCandidates) => {
  30058. // requirements:
  30059. // 1. prioritise context forms over context menus
  30060. // 2. prioritise node scoped over editor scoped context forms
  30061. // 3. only show max 1 context form
  30062. // 4. concatenate all available context toolbars if no context form
  30063. const nodeMatches = matchTargetWith(elem, nodeCandidates);
  30064. if (nodeMatches.contextForms.length > 0) {
  30065. return Optional.some({ elem, toolbars: [nodeMatches.contextForms[0]] });
  30066. }
  30067. else {
  30068. const editorMatches = matchTargetWith(elem, editorCandidates);
  30069. if (editorMatches.contextForms.length > 0) {
  30070. return Optional.some({ elem, toolbars: [editorMatches.contextForms[0]] });
  30071. }
  30072. else if (nodeMatches.contextToolbars.length > 0 || editorMatches.contextToolbars.length > 0) {
  30073. const toolbars = filterByPositionForStartNode(nodeMatches.contextToolbars.concat(editorMatches.contextToolbars));
  30074. return Optional.some({ elem, toolbars });
  30075. }
  30076. else {
  30077. return Optional.none();
  30078. }
  30079. }
  30080. };
  30081. const matchAncestor = (isRoot, startNode, scopes) => {
  30082. // Don't continue to traverse if the start node is the root node
  30083. if (isRoot(startNode)) {
  30084. return Optional.none();
  30085. }
  30086. else {
  30087. return ancestor(startNode, (ancestorElem) => {
  30088. if (isElement$1(ancestorElem)) {
  30089. const { contextToolbars, contextForms } = matchTargetWith(ancestorElem, scopes.inNodeScope);
  30090. const toolbars = contextForms.length > 0 ? contextForms : filterByPositionForAncestorNode(contextToolbars);
  30091. return toolbars.length > 0 ? Optional.some({ elem: ancestorElem, toolbars }) : Optional.none();
  30092. }
  30093. else {
  30094. return Optional.none();
  30095. }
  30096. }, isRoot);
  30097. }
  30098. };
  30099. const lookup$1 = (scopes, editor) => {
  30100. const rootElem = SugarElement.fromDom(editor.getBody());
  30101. const isRoot = (elem) => eq(elem, rootElem);
  30102. const isOutsideRoot = (startNode) => !isRoot(startNode) && !contains(rootElem, startNode);
  30103. const startNode = SugarElement.fromDom(editor.selection.getNode());
  30104. // Ensure the lookup doesn't start on a parent or sibling element of the root node
  30105. if (isOutsideRoot(startNode)) {
  30106. return Optional.none();
  30107. }
  30108. return matchStartNode(startNode, scopes.inNodeScope, scopes.inEditorScope).orThunk(() => matchAncestor(isRoot, startNode, scopes));
  30109. };
  30110. const categorise = (contextToolbars, navigate) => {
  30111. // TODO: Use foldl/foldr and avoid as much mutation.
  30112. const forms = {};
  30113. const inNodeScope = [];
  30114. const inEditorScope = [];
  30115. const formNavigators = {};
  30116. const lookupTable = {};
  30117. const registerForm = (key, toolbarSpec) => {
  30118. const contextForm = getOrDie(createContextForm(toolbarSpec));
  30119. forms[key] = contextForm;
  30120. contextForm.launch.map((launch) => {
  30121. // Use the original here (pre-boulder), because using as a the spec for toolbar buttons
  30122. formNavigators['form:' + key + ''] = {
  30123. ...toolbarSpec.launch,
  30124. type: (launch.type === 'contextformtogglebutton' ? 'togglebutton' : 'button'),
  30125. onAction: () => {
  30126. navigate(contextForm);
  30127. }
  30128. };
  30129. });
  30130. if (contextForm.scope === 'editor') {
  30131. inEditorScope.push(contextForm);
  30132. }
  30133. else {
  30134. inNodeScope.push(contextForm);
  30135. }
  30136. lookupTable[key] = contextForm;
  30137. };
  30138. const registerToolbar = (key, toolbarSpec) => {
  30139. createContextToolbar(toolbarSpec).each((contextToolbar) => {
  30140. if (contextToolbar.launch.isSome()) {
  30141. formNavigators['toolbar:' + key + ''] = {
  30142. ...toolbarSpec.launch,
  30143. type: 'button',
  30144. onAction: () => {
  30145. navigate(contextToolbar);
  30146. }
  30147. };
  30148. }
  30149. if (toolbarSpec.scope === 'editor') {
  30150. inEditorScope.push(contextToolbar);
  30151. }
  30152. else {
  30153. inNodeScope.push(contextToolbar);
  30154. }
  30155. lookupTable[key] = contextToolbar;
  30156. });
  30157. };
  30158. const keys$1 = keys(contextToolbars);
  30159. each$1(keys$1, (key) => {
  30160. const toolbarApi = contextToolbars[key];
  30161. if (toolbarApi.type === 'contextform' || toolbarApi.type === 'contextsliderform' || toolbarApi.type === 'contextsizeinputform') {
  30162. registerForm(key, toolbarApi);
  30163. }
  30164. else if (toolbarApi.type === 'contexttoolbar') {
  30165. registerToolbar(key, toolbarApi);
  30166. }
  30167. });
  30168. return {
  30169. forms,
  30170. inNodeScope,
  30171. inEditorScope,
  30172. lookupTable,
  30173. formNavigators
  30174. };
  30175. };
  30176. const transitionClass = 'tox-pop--transition';
  30177. const isToolbarActionKey = (keyCode) => keyCode === global$1.ENTER || keyCode === global$1.SPACEBAR;
  30178. const register$a = (editor, registryContextToolbars, sink, extras) => {
  30179. const backstage = extras.backstage;
  30180. const sharedBackstage = backstage.shared;
  30181. const isTouch = detect$1().deviceType.isTouch;
  30182. const lastElement = value$2();
  30183. const lastTrigger = value$2();
  30184. const lastContextPosition = value$2();
  30185. const contextToolbarResult = renderContextToolbar({
  30186. sink,
  30187. onEscape: () => {
  30188. editor.focus();
  30189. fireContextToolbarClose(editor);
  30190. return Optional.some(true);
  30191. },
  30192. onHide: () => {
  30193. fireContextToolbarClose(editor);
  30194. },
  30195. onBack: () => {
  30196. fireContextFormSlideBack(editor);
  30197. },
  30198. focusElement: (el) => {
  30199. if (editor.getBody().contains(el.dom)) {
  30200. editor.focus();
  30201. }
  30202. else {
  30203. focus$4(el);
  30204. }
  30205. }
  30206. });
  30207. const contextbar = build$1(contextToolbarResult.sketch);
  30208. const getBounds = () => {
  30209. const position = lastContextPosition.get().getOr('node');
  30210. // Use a 1px margin for the bounds to keep the context toolbar from butting directly against
  30211. // the header, etc... when switching to inset layouts
  30212. const margin = shouldUseInsetLayouts(position) ? 1 : 0;
  30213. return getContextToolbarBounds(editor, sharedBackstage, position, margin);
  30214. };
  30215. const canLaunchToolbar = () => {
  30216. // If a mobile context menu is open, don't launch else they'll probably overlap. For android, specifically.
  30217. return !editor.removed && !(isTouch() && backstage.isContextMenuOpen());
  30218. };
  30219. const isSameLaunchElement = (elem) => is$1(lift2(elem, lastElement.get(), eq), true);
  30220. const shouldContextToolbarHide = () => {
  30221. if (!canLaunchToolbar()) {
  30222. return true;
  30223. }
  30224. else {
  30225. const contextToolbarBounds = getBounds();
  30226. // Get the anchor bounds. For node anchors we should always try to use the last element bounds
  30227. const anchorBounds = is$1(lastContextPosition.get(), 'node') ?
  30228. getAnchorElementBounds(editor, lastElement.get()) :
  30229. getSelectionBounds(editor);
  30230. // If the anchor bounds aren't overlapping with the context toolbar bounds, then the context toolbar
  30231. // should hide. We want the threshold to require some overlap here (+.01), so that as soon as the
  30232. // anchor is off-screen, the context toolbar disappers.
  30233. return contextToolbarBounds.height <= 0 || !isVerticalOverlap(anchorBounds, contextToolbarBounds, 0.01);
  30234. }
  30235. };
  30236. const close = () => {
  30237. lastElement.clear();
  30238. lastTrigger.clear();
  30239. lastContextPosition.clear();
  30240. InlineView.hide(contextbar);
  30241. };
  30242. const hideOrRepositionIfNecessary = () => {
  30243. if (InlineView.isOpen(contextbar)) {
  30244. const contextBarEle = contextbar.element;
  30245. remove$6(contextBarEle, 'display');
  30246. if (shouldContextToolbarHide()) {
  30247. set$7(contextBarEle, 'display', 'none');
  30248. }
  30249. else {
  30250. lastTrigger.set(0 /* TriggerCause.Reposition */);
  30251. InlineView.reposition(contextbar);
  30252. }
  30253. }
  30254. };
  30255. const wrapInPopDialog = (toolbarSpec) => ({
  30256. dom: {
  30257. tag: 'div',
  30258. classes: ['tox-pop__dialog']
  30259. },
  30260. components: [toolbarSpec],
  30261. behaviours: derive$1([
  30262. Keying.config({
  30263. mode: 'acyclic'
  30264. }),
  30265. config('pop-dialog-wrap-events', [
  30266. runOnAttached((comp) => {
  30267. editor.shortcuts.add('ctrl+F9', 'focus statusbar', () => Keying.focusIn(comp));
  30268. }),
  30269. runOnDetached((_comp) => {
  30270. editor.shortcuts.remove('ctrl+F9');
  30271. })
  30272. ])
  30273. ])
  30274. });
  30275. const navigate = (toolbarApi) => {
  30276. // ASSUMPTION: This should only ever show one context toolbar since it's used for context forms hence [toolbarApi]
  30277. const alloySpec = buildToolbar([toolbarApi]);
  30278. emitWith(contextbar, forwardSlideEvent, {
  30279. forwardContents: wrapInPopDialog(alloySpec)
  30280. });
  30281. };
  30282. const getScopes = cached(() => categorise(registryContextToolbars, navigate));
  30283. const buildContextToolbarGroups = (allButtons, ctx) => {
  30284. return identifyButtons(editor, { buttons: allButtons, toolbar: ctx.items, allowToolbarGroups: false }, extras.backstage, Optional.some(['form:', 'toolbar:']));
  30285. };
  30286. const buildContextFormGroups = (ctx, providers) => ContextForm.buildInitGroups(ctx, providers);
  30287. const buildToolbar = (toolbars) => {
  30288. const { buttons } = editor.ui.registry.getAll();
  30289. const scopes = getScopes();
  30290. const allButtons = { ...buttons, ...scopes.formNavigators };
  30291. // For context toolbars we don't want to use floating or sliding, so just restrict this
  30292. // to scrolling or wrapping (default)
  30293. const toolbarType = getToolbarMode(editor) === ToolbarMode$1.scrolling ? ToolbarMode$1.scrolling : ToolbarMode$1.default;
  30294. const initGroups = flatten(map$2(toolbars, (ctx) => ctx.type === 'contexttoolbar' ? buildContextToolbarGroups(allButtons, contextToolbarToSpec(ctx)) : buildContextFormGroups(ctx, sharedBackstage.providers)));
  30295. return renderToolbar({
  30296. type: toolbarType,
  30297. uid: generate$6('context-toolbar'),
  30298. initGroups,
  30299. onEscape: Optional.none,
  30300. cyclicKeying: true,
  30301. providers: sharedBackstage.providers
  30302. });
  30303. };
  30304. const getAnchor = (position, element) => {
  30305. const anchorage = position === 'node' ? sharedBackstage.anchors.node(element) : sharedBackstage.anchors.cursor();
  30306. const anchorLayout = getAnchorLayout(editor, position, isTouch(), {
  30307. lastElement: lastElement.get,
  30308. isReposition: () => is$1(lastTrigger.get(), 0 /* TriggerCause.Reposition */),
  30309. getMode: () => Positioning.getMode(sink)
  30310. });
  30311. return deepMerge(anchorage, anchorLayout);
  30312. };
  30313. const launchContext = (toolbarApi, elem) => {
  30314. launchContextToolbar.cancel();
  30315. // Don't launch if the editor has something else open that would conflict
  30316. if (!canLaunchToolbar()) {
  30317. return;
  30318. }
  30319. const toolbarSpec = buildToolbar(toolbarApi);
  30320. // TINY-4495 ASSUMPTION: Can only do toolbarApi[0].position because ContextToolbarLookup.filterToolbarsByPosition
  30321. // ensures all toolbars returned by ContextToolbarLookup have the same position.
  30322. // And everything else that gets toolbars from elsewhere only returns maximum 1 toolbar
  30323. const position = toolbarApi[0].position;
  30324. const anchor = getAnchor(position, elem);
  30325. lastContextPosition.set(position);
  30326. lastTrigger.set(1 /* TriggerCause.NewAnchor */);
  30327. const contextBarEle = contextbar.element;
  30328. remove$6(contextBarEle, 'display');
  30329. // Reset placement and transitions when moving to different elements
  30330. if (!isSameLaunchElement(elem)) {
  30331. remove$3(contextBarEle, transitionClass);
  30332. Positioning.reset(sink, contextbar);
  30333. }
  30334. // Place the element
  30335. InlineView.showWithinBounds(contextbar, wrapInPopDialog(toolbarSpec), {
  30336. anchor,
  30337. transition: {
  30338. classes: [transitionClass],
  30339. mode: 'placement'
  30340. }
  30341. }, () => Optional.some(getBounds()));
  30342. // IMPORTANT: This must be stored after the initial render, otherwise the lookup of the last element in the
  30343. // anchor placement will be incorrect as it'll reuse the new element as the anchor point.
  30344. elem.fold(lastElement.clear, lastElement.set);
  30345. // It's possible we may have launched offscreen, if so then hide
  30346. if (shouldContextToolbarHide()) {
  30347. set$7(contextBarEle, 'display', 'none');
  30348. }
  30349. };
  30350. const instantReposition = () => {
  30351. // Sometimes when we reposition the toolbar it might be in a transitioning state and
  30352. // if we try to reposition while that happens the computed position/width will be incorrect.
  30353. set$7(contextbar.element, 'transition', 'none');
  30354. hideOrRepositionIfNecessary();
  30355. remove$6(contextbar.element, 'transition');
  30356. };
  30357. let isDragging = false;
  30358. const launchContextToolbar = last(() => {
  30359. // Don't launch if the editor doesn't have focus or has been destroyed
  30360. if (!editor.hasFocus() || editor.removed || isDragging) {
  30361. return;
  30362. }
  30363. // If currently transitioning then throttle again so we don't interrupt the transition
  30364. if (has(contextbar.element, transitionClass)) {
  30365. launchContextToolbar.throttle();
  30366. }
  30367. else {
  30368. const scopes = getScopes();
  30369. lookup$1(scopes, editor).fold(close, (info) => {
  30370. launchContext(info.toolbars, Optional.some(info.elem));
  30371. });
  30372. }
  30373. }, 17); // 17ms is used as that's about 1 frame at 60fps
  30374. editor.on('init', () => {
  30375. editor.on('remove', close);
  30376. editor.on('ScrollContent ScrollWindow ObjectResized ResizeEditor longpress', hideOrRepositionIfNecessary);
  30377. // FIX: Make it go away when the action makes it go away. E.g. deleting a column deletes the table.
  30378. editor.on('click focus SetContent', launchContextToolbar.throttle);
  30379. editor.on('keyup', (e) => {
  30380. // If you use keyboard to press a button in a subtoolbar then the keyup will happen inside the editor and that should not re-render the toolbar
  30381. if (!isToolbarActionKey(e.keyCode) || !contextToolbarResult.inSubtoolbar()) {
  30382. launchContextToolbar.throttle();
  30383. }
  30384. });
  30385. editor.on(hideContextToolbarEvent, close);
  30386. editor.on(showContextToolbarEvent, (e) => {
  30387. const scopes = getScopes();
  30388. // TODO: Have this stored in a better structure
  30389. get$h(scopes.lookupTable, e.toolbarKey).each((ctx) => {
  30390. // ASSUMPTION: this is only used to open one specific toolbar at a time, hence [ctx]
  30391. launchContext([ctx], someIf(e.target !== editor, e.target));
  30392. focusIn(contextbar);
  30393. });
  30394. });
  30395. editor.on('focusout', (_e) => {
  30396. global$a.setEditorTimeout(editor, () => {
  30397. if (search(sink.element).isNone() && search(contextbar.element).isNone() && !editor.hasFocus()) {
  30398. close();
  30399. }
  30400. }, 0);
  30401. });
  30402. editor.on('SwitchMode', () => {
  30403. if (editor.mode.isReadOnly()) {
  30404. close();
  30405. }
  30406. });
  30407. editor.on('DisabledStateChange', (e) => {
  30408. if (e.state) {
  30409. close();
  30410. }
  30411. });
  30412. // TINY-10640: Firefox was flaking in tests and was not properly dismissing the toolbar could not reproduce it manually but adding this seems to resolve it.
  30413. editor.on('ExecCommand', ({ command }) => {
  30414. if (command.toLowerCase() === 'toggleview') {
  30415. close();
  30416. }
  30417. });
  30418. editor.on('AfterProgressState', (event) => {
  30419. if (event.state) {
  30420. close();
  30421. }
  30422. else if (editor.hasFocus()) {
  30423. launchContextToolbar.throttle();
  30424. }
  30425. });
  30426. editor.on('dragstart', () => {
  30427. isDragging = true;
  30428. });
  30429. editor.on('dragend drop', () => {
  30430. isDragging = false;
  30431. });
  30432. editor.on('NodeChange', (_e) => {
  30433. if (!contextToolbarResult.inSubtoolbar()) {
  30434. search(contextbar.element).fold(launchContextToolbar.throttle, noop);
  30435. }
  30436. else {
  30437. instantReposition();
  30438. }
  30439. });
  30440. });
  30441. };
  30442. const register$9 = (editor) => {
  30443. const alignToolbarButtons = [
  30444. { name: 'alignleft', text: 'Align left', cmd: 'JustifyLeft', icon: 'align-left' },
  30445. { name: 'aligncenter', text: 'Align center', cmd: 'JustifyCenter', icon: 'align-center' },
  30446. { name: 'alignright', text: 'Align right', cmd: 'JustifyRight', icon: 'align-right' },
  30447. { name: 'alignjustify', text: 'Justify', cmd: 'JustifyFull', icon: 'align-justify' }
  30448. ];
  30449. each$1(alignToolbarButtons, (item) => {
  30450. editor.ui.registry.addToggleButton(item.name, {
  30451. tooltip: item.text,
  30452. icon: item.icon,
  30453. onAction: onActionExecCommand(editor, item.cmd),
  30454. onSetup: onSetupStateToggle(editor, item.name)
  30455. });
  30456. });
  30457. editor.ui.registry.addButton('alignnone', {
  30458. tooltip: 'No alignment',
  30459. icon: 'align-none',
  30460. onSetup: onSetupEditableToggle(editor),
  30461. onAction: onActionExecCommand(editor, 'JustifyNone')
  30462. });
  30463. };
  30464. const registerController = (editor, spec) => {
  30465. const getMenuItems = () => {
  30466. const options = spec.getOptions(editor);
  30467. const initial = spec.getCurrent(editor).map(spec.hash);
  30468. const current = value$2();
  30469. return map$2(options, (value) => ({
  30470. type: 'togglemenuitem',
  30471. text: spec.display(value),
  30472. onSetup: (api) => {
  30473. const setActive = (active) => {
  30474. if (active) {
  30475. current.on((oldApi) => oldApi.setActive(false));
  30476. current.set(api);
  30477. }
  30478. api.setActive(active);
  30479. };
  30480. setActive(is$1(initial, spec.hash(value)));
  30481. const unbindWatcher = spec.watcher(editor, value, setActive);
  30482. return () => {
  30483. current.clear();
  30484. unbindWatcher();
  30485. };
  30486. },
  30487. onAction: () => spec.setCurrent(editor, value)
  30488. }));
  30489. };
  30490. editor.ui.registry.addMenuButton(spec.name, {
  30491. tooltip: spec.text,
  30492. icon: spec.icon,
  30493. fetch: (callback) => callback(getMenuItems()),
  30494. onSetup: spec.onToolbarSetup
  30495. });
  30496. editor.ui.registry.addNestedMenuItem(spec.name, {
  30497. type: 'nestedmenuitem',
  30498. text: spec.text,
  30499. getSubmenuItems: getMenuItems,
  30500. onSetup: spec.onMenuSetup
  30501. });
  30502. };
  30503. const lineHeightSpec = (editor) => ({
  30504. name: 'lineheight',
  30505. text: 'Line height',
  30506. icon: 'line-height',
  30507. getOptions: getLineHeightFormats,
  30508. hash: (input) => normalise(input, ['fixed', 'relative', 'empty']).getOr(input),
  30509. display: identity,
  30510. watcher: (editor, value, callback) => editor.formatter.formatChanged('lineheight', callback, false, { value }).unbind,
  30511. getCurrent: (editor) => Optional.from(editor.queryCommandValue('LineHeight')),
  30512. setCurrent: (editor, value) => editor.execCommand('LineHeight', false, value),
  30513. onToolbarSetup: onSetupEditableToggle(editor),
  30514. onMenuSetup: onSetupEditableToggle(editor)
  30515. });
  30516. const languageSpec = (editor) => {
  30517. const settingsOpt = Optional.from(getContentLanguages(editor));
  30518. return settingsOpt.map((settings) => ({
  30519. name: 'language',
  30520. text: 'Language',
  30521. icon: 'language',
  30522. getOptions: constant$1(settings),
  30523. hash: (input) => isUndefined(input.customCode) ? input.code : `${input.code}/${input.customCode}`,
  30524. display: (input) => input.title,
  30525. watcher: (editor, value, callback) => { var _a; return editor.formatter.formatChanged('lang', callback, false, { value: value.code, customValue: (_a = value.customCode) !== null && _a !== void 0 ? _a : null }).unbind; },
  30526. getCurrent: (editor) => {
  30527. const node = SugarElement.fromDom(editor.selection.getNode());
  30528. return closest(node, (n) => Optional.some(n)
  30529. .filter(isElement$1)
  30530. .bind((ele) => {
  30531. const codeOpt = getOpt(ele, 'lang');
  30532. return codeOpt.map((code) => {
  30533. const customCode = getOpt(ele, 'data-mce-lang').getOrUndefined();
  30534. return { code, customCode, title: '' };
  30535. });
  30536. }));
  30537. },
  30538. setCurrent: (editor, lang) => editor.execCommand('Lang', false, lang),
  30539. onToolbarSetup: (api) => {
  30540. const unbinder = unbindable();
  30541. api.setActive(editor.formatter.match('lang', {}, undefined, true));
  30542. unbinder.set(editor.formatter.formatChanged('lang', api.setActive, true));
  30543. return composeUnbinders(unbinder.clear, onSetupEditableToggle(editor)(api));
  30544. },
  30545. onMenuSetup: onSetupEditableToggle(editor)
  30546. }));
  30547. };
  30548. const register$8 = (editor) => {
  30549. registerController(editor, lineHeightSpec(editor));
  30550. languageSpec(editor).each((spec) => registerController(editor, spec));
  30551. };
  30552. const register$7 = (editor, backstage) => {
  30553. createAlignMenu(editor, backstage);
  30554. createFontFamilyMenu(editor, backstage);
  30555. createStylesMenu(editor, backstage);
  30556. createBlocksMenu(editor, backstage);
  30557. createFontSizeMenu(editor, backstage);
  30558. };
  30559. const register$6 = (editor) => {
  30560. editor.ui.registry.addContext('editable', () => {
  30561. return editor.selection.isEditable();
  30562. });
  30563. editor.ui.registry.addContext('mode', (mode) => {
  30564. return editor.mode.get() === mode;
  30565. });
  30566. editor.ui.registry.addContext('any', always);
  30567. editor.ui.registry.addContext('formatting', (format) => {
  30568. return editor.formatter.canApply(format);
  30569. });
  30570. editor.ui.registry.addContext('insert', (child) => {
  30571. return editor.schema.isValidChild(editor.selection.getNode().tagName, child);
  30572. });
  30573. };
  30574. const onSetupOutdentState = (editor) => onSetupEvent(editor, 'NodeChange', (api) => {
  30575. api.setEnabled(editor.queryCommandState('outdent') && editor.selection.isEditable());
  30576. });
  30577. const registerButtons$2 = (editor) => {
  30578. editor.ui.registry.addButton('outdent', {
  30579. tooltip: 'Decrease indent',
  30580. icon: 'outdent',
  30581. onSetup: onSetupOutdentState(editor),
  30582. onAction: onActionExecCommand(editor, 'outdent')
  30583. });
  30584. editor.ui.registry.addButton('indent', {
  30585. tooltip: 'Increase indent',
  30586. icon: 'indent',
  30587. onSetup: onSetupEditableToggle(editor, () => editor.queryCommandState('indent')),
  30588. onAction: onActionExecCommand(editor, 'indent')
  30589. });
  30590. };
  30591. const register$5 = (editor) => {
  30592. registerButtons$2(editor);
  30593. };
  30594. const makeSetupHandler = (editor, pasteAsText) => (api) => {
  30595. api.setActive(pasteAsText.get());
  30596. const pastePlainTextToggleHandler = (e) => {
  30597. pasteAsText.set(e.state);
  30598. api.setActive(e.state);
  30599. };
  30600. editor.on('PastePlainTextToggle', pastePlainTextToggleHandler);
  30601. return composeUnbinders(() => editor.off('PastePlainTextToggle', pastePlainTextToggleHandler), onSetupEditableToggle(editor)(api));
  30602. };
  30603. const register$4 = (editor) => {
  30604. const pasteAsText = Cell(getPasteAsText(editor));
  30605. const onAction = () => editor.execCommand('mceTogglePlainTextPaste');
  30606. editor.ui.registry.addToggleButton('pastetext', {
  30607. active: false,
  30608. icon: 'paste-text',
  30609. tooltip: 'Paste as text',
  30610. onAction,
  30611. onSetup: makeSetupHandler(editor, pasteAsText)
  30612. });
  30613. editor.ui.registry.addToggleMenuItem('pastetext', {
  30614. text: 'Paste as text',
  30615. icon: 'paste-text',
  30616. onAction,
  30617. onSetup: makeSetupHandler(editor, pasteAsText)
  30618. });
  30619. };
  30620. const onActionToggleFormat = (editor, fmt) => () => {
  30621. editor.execCommand('mceToggleFormat', false, fmt);
  30622. };
  30623. const registerFormatButtons = (editor) => {
  30624. global$2.each([
  30625. { name: 'bold', text: 'Bold', icon: 'bold', shortcut: 'Meta+B' },
  30626. { name: 'italic', text: 'Italic', icon: 'italic', shortcut: 'Meta+I' },
  30627. { name: 'underline', text: 'Underline', icon: 'underline', shortcut: 'Meta+U' },
  30628. { name: 'strikethrough', text: 'Strikethrough', icon: 'strike-through' },
  30629. { name: 'subscript', text: 'Subscript', icon: 'subscript' },
  30630. { name: 'superscript', text: 'Superscript', icon: 'superscript' }
  30631. ], (btn, _idx) => {
  30632. editor.ui.registry.addToggleButton(btn.name, {
  30633. tooltip: btn.text,
  30634. icon: btn.icon,
  30635. onSetup: onSetupStateToggle(editor, btn.name),
  30636. onAction: onActionToggleFormat(editor, btn.name),
  30637. shortcut: btn.shortcut
  30638. });
  30639. });
  30640. for (let i = 1; i <= 6; i++) {
  30641. const name = 'h' + i;
  30642. const shortcut = `Access+${i}`;
  30643. editor.ui.registry.addToggleButton(name, {
  30644. text: name.toUpperCase(),
  30645. tooltip: 'Heading ' + i,
  30646. onSetup: onSetupStateToggle(editor, name),
  30647. onAction: onActionToggleFormat(editor, name),
  30648. shortcut
  30649. });
  30650. }
  30651. };
  30652. const registerCommandButtons = (editor) => {
  30653. global$2.each([
  30654. { name: 'copy', text: 'Copy', action: 'Copy', icon: 'copy', context: 'any' },
  30655. { name: 'help', text: 'Help', action: 'mceHelp', icon: 'help', shortcut: 'Alt+0', context: 'any' },
  30656. { name: 'selectall', text: 'Select all', action: 'SelectAll', icon: 'select-all', shortcut: 'Meta+A', context: 'any' },
  30657. { name: 'newdocument', text: 'New document', action: 'mceNewDocument', icon: 'new-document' },
  30658. { name: 'print', text: 'Print', action: 'mcePrint', icon: 'print', shortcut: 'Meta+P', context: 'any' },
  30659. ], (btn) => {
  30660. editor.ui.registry.addButton(btn.name, {
  30661. tooltip: btn.text,
  30662. icon: btn.icon,
  30663. onAction: onActionExecCommand(editor, btn.action),
  30664. shortcut: btn.shortcut,
  30665. context: btn.context
  30666. });
  30667. });
  30668. global$2.each([
  30669. { name: 'cut', text: 'Cut', action: 'Cut', icon: 'cut' },
  30670. { name: 'paste', text: 'Paste', action: 'Paste', icon: 'paste' },
  30671. // visualaid was here but also exists in VisualAid.ts?
  30672. { name: 'removeformat', text: 'Clear formatting', action: 'RemoveFormat', icon: 'remove-formatting' },
  30673. { name: 'remove', text: 'Remove', action: 'Delete', icon: 'remove' },
  30674. { name: 'hr', text: 'Horizontal line', action: 'InsertHorizontalRule', icon: 'horizontal-rule' }
  30675. ], (btn) => {
  30676. editor.ui.registry.addButton(btn.name, {
  30677. tooltip: btn.text,
  30678. icon: btn.icon,
  30679. onSetup: onSetupEditableToggle(editor),
  30680. onAction: onActionExecCommand(editor, btn.action)
  30681. });
  30682. });
  30683. };
  30684. const registerCommandToggleButtons = (editor) => {
  30685. global$2.each([
  30686. { name: 'blockquote', text: 'Blockquote', action: 'mceBlockQuote', icon: 'quote' }
  30687. ], (btn) => {
  30688. editor.ui.registry.addToggleButton(btn.name, {
  30689. tooltip: btn.text,
  30690. icon: btn.icon,
  30691. onAction: onActionExecCommand(editor, btn.action),
  30692. onSetup: onSetupStateToggle(editor, btn.name)
  30693. });
  30694. });
  30695. };
  30696. const registerButtons$1 = (editor) => {
  30697. registerFormatButtons(editor);
  30698. registerCommandButtons(editor);
  30699. registerCommandToggleButtons(editor);
  30700. };
  30701. const registerMenuItems$2 = (editor) => {
  30702. global$2.each([
  30703. { name: 'newdocument', text: 'New document', action: 'mceNewDocument', icon: 'new-document' },
  30704. { name: 'copy', text: 'Copy', action: 'Copy', icon: 'copy', shortcut: 'Meta+C', context: 'any' },
  30705. { name: 'selectall', text: 'Select all', action: 'SelectAll', icon: 'select-all', shortcut: 'Meta+A', context: 'any' },
  30706. { name: 'print', text: 'Print...', action: 'mcePrint', icon: 'print', shortcut: 'Meta+P', context: 'any' }
  30707. ], (menuitem) => {
  30708. editor.ui.registry.addMenuItem(menuitem.name, {
  30709. text: menuitem.text,
  30710. icon: menuitem.icon,
  30711. shortcut: menuitem.shortcut,
  30712. onAction: onActionExecCommand(editor, menuitem.action),
  30713. context: menuitem.context
  30714. });
  30715. });
  30716. global$2.each([
  30717. { name: 'bold', text: 'Bold', action: 'Bold', icon: 'bold', shortcut: 'Meta+B' },
  30718. { name: 'italic', text: 'Italic', action: 'Italic', icon: 'italic', shortcut: 'Meta+I' },
  30719. { name: 'underline', text: 'Underline', action: 'Underline', icon: 'underline', shortcut: 'Meta+U' },
  30720. { name: 'strikethrough', text: 'Strikethrough', action: 'Strikethrough', icon: 'strike-through' },
  30721. { name: 'subscript', text: 'Subscript', action: 'Subscript', icon: 'subscript' },
  30722. { name: 'superscript', text: 'Superscript', action: 'Superscript', icon: 'superscript' },
  30723. { name: 'removeformat', text: 'Clear formatting', action: 'RemoveFormat', icon: 'remove-formatting' },
  30724. { name: 'cut', text: 'Cut', action: 'Cut', icon: 'cut', shortcut: 'Meta+X' },
  30725. { name: 'paste', text: 'Paste', action: 'Paste', icon: 'paste', shortcut: 'Meta+V' },
  30726. { name: 'hr', text: 'Horizontal line', action: 'InsertHorizontalRule', icon: 'horizontal-rule' }
  30727. ], (menuitem) => {
  30728. editor.ui.registry.addMenuItem(menuitem.name, {
  30729. text: menuitem.text,
  30730. icon: menuitem.icon,
  30731. shortcut: menuitem.shortcut,
  30732. onSetup: onSetupEditableToggle(editor),
  30733. onAction: onActionExecCommand(editor, menuitem.action)
  30734. });
  30735. });
  30736. editor.ui.registry.addMenuItem('codeformat', {
  30737. text: 'Code',
  30738. icon: 'sourcecode',
  30739. onSetup: onSetupEditableToggle(editor),
  30740. onAction: onActionToggleFormat(editor, 'code')
  30741. });
  30742. };
  30743. const register$3 = (editor) => {
  30744. registerButtons$1(editor);
  30745. registerMenuItems$2(editor);
  30746. };
  30747. const onSetupUndoRedoState = (editor, type) => onSetupEvent(editor, 'Undo Redo AddUndo TypingUndo ClearUndos SwitchMode', (api) => {
  30748. api.setEnabled(!editor.mode.isReadOnly() && editor.undoManager[type]());
  30749. });
  30750. const registerMenuItems$1 = (editor) => {
  30751. editor.ui.registry.addMenuItem('undo', {
  30752. text: 'Undo',
  30753. icon: 'undo',
  30754. shortcut: 'Meta+Z',
  30755. onSetup: onSetupUndoRedoState(editor, 'hasUndo'),
  30756. onAction: onActionExecCommand(editor, 'undo')
  30757. });
  30758. editor.ui.registry.addMenuItem('redo', {
  30759. text: 'Redo',
  30760. icon: 'redo',
  30761. shortcut: 'Meta+Y',
  30762. onSetup: onSetupUndoRedoState(editor, 'hasRedo'),
  30763. onAction: onActionExecCommand(editor, 'redo')
  30764. });
  30765. };
  30766. // Note: The undo/redo buttons are disabled by default here, as they'll be rendered
  30767. // on init generally and it won't have any undo levels at that stage.
  30768. const registerButtons = (editor) => {
  30769. editor.ui.registry.addButton('undo', {
  30770. tooltip: 'Undo',
  30771. icon: 'undo',
  30772. enabled: false,
  30773. onSetup: onSetupUndoRedoState(editor, 'hasUndo'),
  30774. onAction: onActionExecCommand(editor, 'undo'),
  30775. shortcut: 'Meta+Z'
  30776. });
  30777. editor.ui.registry.addButton('redo', {
  30778. tooltip: 'Redo',
  30779. icon: 'redo',
  30780. enabled: false,
  30781. onSetup: onSetupUndoRedoState(editor, 'hasRedo'),
  30782. onAction: onActionExecCommand(editor, 'redo'),
  30783. shortcut: 'Meta+Y'
  30784. });
  30785. };
  30786. const register$2 = (editor) => {
  30787. registerMenuItems$1(editor);
  30788. registerButtons(editor);
  30789. };
  30790. const onSetupVisualAidState = (editor) => onSetupEvent(editor, 'VisualAid', (api) => {
  30791. api.setActive(editor.hasVisual);
  30792. });
  30793. const registerMenuItems = (editor) => {
  30794. editor.ui.registry.addToggleMenuItem('visualaid', {
  30795. text: 'Visual aids',
  30796. onSetup: onSetupVisualAidState(editor),
  30797. onAction: onActionExecCommand(editor, 'mceToggleVisualAid'),
  30798. context: 'any'
  30799. });
  30800. };
  30801. const registerToolbarButton = (editor) => {
  30802. editor.ui.registry.addButton('visualaid', {
  30803. tooltip: 'Visual aids',
  30804. text: 'Visual aids',
  30805. onAction: onActionExecCommand(editor, 'mceToggleVisualAid'),
  30806. context: 'any'
  30807. });
  30808. };
  30809. const register$1 = (editor) => {
  30810. registerToolbarButton(editor);
  30811. registerMenuItems(editor);
  30812. };
  30813. const setup$6 = (editor, backstage) => {
  30814. register$9(editor);
  30815. register$3(editor);
  30816. register$7(editor, backstage);
  30817. register$2(editor);
  30818. register$d(editor);
  30819. register$1(editor);
  30820. register$5(editor);
  30821. register$8(editor);
  30822. register$4(editor);
  30823. register$6(editor);
  30824. };
  30825. const patchPipeConfig = (config) => isString(config) ? config.split(/[ ,]/) : config;
  30826. const option = (name) => (editor) => editor.options.get(name);
  30827. const register = (editor) => {
  30828. const registerOption = editor.options.register;
  30829. registerOption('contextmenu_avoid_overlap', {
  30830. processor: 'string',
  30831. default: ''
  30832. });
  30833. registerOption('contextmenu_never_use_native', {
  30834. processor: 'boolean',
  30835. default: false
  30836. });
  30837. registerOption('contextmenu', {
  30838. processor: (value) => {
  30839. if (value === false) {
  30840. return { value: [], valid: true };
  30841. }
  30842. else if (isString(value) || isArrayOf(value, isString)) {
  30843. return { value: patchPipeConfig(value), valid: true };
  30844. }
  30845. else {
  30846. return { valid: false, message: 'Must be false or a string.' };
  30847. }
  30848. },
  30849. default: 'link linkchecker image editimage table spellchecker configurepermanentpen'
  30850. });
  30851. };
  30852. const shouldNeverUseNative = option('contextmenu_never_use_native');
  30853. const getAvoidOverlapSelector = option('contextmenu_avoid_overlap');
  30854. const isContextMenuDisabled = (editor) => getContextMenu(editor).length === 0;
  30855. const getContextMenu = (editor) => {
  30856. const contextMenus = editor.ui.registry.getAll().contextMenus;
  30857. const contextMenu = editor.options.get('contextmenu');
  30858. if (editor.options.isSet('contextmenu')) {
  30859. return contextMenu;
  30860. }
  30861. else {
  30862. // Filter default context menu items when they are not in the registry (e.g. when the plugin is not loaded)
  30863. return filter$2(contextMenu, (item) => has$2(contextMenus, item));
  30864. }
  30865. };
  30866. const nu = (x, y) => ({
  30867. type: 'makeshift',
  30868. x,
  30869. y
  30870. });
  30871. const transpose = (pos, dx, dy) => {
  30872. return nu(pos.x + dx, pos.y + dy);
  30873. };
  30874. const isTouchEvent$1 = (e) => e.type === 'longpress' || e.type.indexOf('touch') === 0;
  30875. const fromPageXY = (e) => {
  30876. if (isTouchEvent$1(e)) {
  30877. const touch = e.touches[0];
  30878. return nu(touch.pageX, touch.pageY);
  30879. }
  30880. else {
  30881. return nu(e.pageX, e.pageY);
  30882. }
  30883. };
  30884. const fromClientXY = (e) => {
  30885. if (isTouchEvent$1(e)) {
  30886. const touch = e.touches[0];
  30887. return nu(touch.clientX, touch.clientY);
  30888. }
  30889. else {
  30890. return nu(e.clientX, e.clientY);
  30891. }
  30892. };
  30893. const transposeContentAreaContainer = (element, pos) => {
  30894. const containerPos = global$9.DOM.getPos(element);
  30895. return transpose(pos, containerPos.x, containerPos.y);
  30896. };
  30897. const getPointAnchor = (editor, e) => {
  30898. // If the contextmenu event is fired via the editor.dispatch() API or some other means, fall back to selection anchor
  30899. if (e.type === 'contextmenu' || e.type === 'longpress') {
  30900. if (editor.inline) {
  30901. return fromPageXY(e);
  30902. }
  30903. else {
  30904. return transposeContentAreaContainer(editor.getContentAreaContainer(), fromClientXY(e));
  30905. }
  30906. }
  30907. else {
  30908. return getSelectionAnchor(editor);
  30909. }
  30910. };
  30911. const getSelectionAnchor = (editor) => {
  30912. return {
  30913. type: 'selection',
  30914. root: SugarElement.fromDom(editor.selection.getNode())
  30915. };
  30916. };
  30917. const getNodeAnchor = (editor) => ({
  30918. type: 'node',
  30919. node: Optional.some(SugarElement.fromDom(editor.selection.getNode())),
  30920. root: SugarElement.fromDom(editor.getBody())
  30921. });
  30922. const getAnchorSpec$1 = (editor, e, anchorType) => {
  30923. switch (anchorType) {
  30924. case 'node':
  30925. return getNodeAnchor(editor);
  30926. case 'point':
  30927. return getPointAnchor(editor, e);
  30928. case 'selection':
  30929. return getSelectionAnchor(editor);
  30930. }
  30931. };
  30932. const initAndShow$1 = (editor, e, buildMenu, backstage, contextmenu, anchorType) => {
  30933. const items = buildMenu();
  30934. const anchorSpec = getAnchorSpec$1(editor, e, anchorType);
  30935. build(items, ItemResponse$1.CLOSE_ON_EXECUTE, backstage, {
  30936. isHorizontalMenu: false,
  30937. search: Optional.none()
  30938. }).map((menuData) => {
  30939. e.preventDefault();
  30940. // show the context menu, with items set to close on click
  30941. InlineView.showMenuAt(contextmenu, { anchor: anchorSpec }, {
  30942. menu: {
  30943. markers: markers('normal')
  30944. },
  30945. data: menuData
  30946. });
  30947. });
  30948. };
  30949. const layouts = {
  30950. onLtr: () => [south$2, southeast$2, southwest$2, northeast$2, northwest$2, north$2,
  30951. north$1, south$1, northeast$1, southeast$1, northwest$1, southwest$1],
  30952. onRtl: () => [south$2, southwest$2, southeast$2, northwest$2, northeast$2, north$2,
  30953. north$1, south$1, northwest$1, southwest$1, northeast$1, southeast$1]
  30954. };
  30955. const bubbleSize = 12;
  30956. const bubbleAlignments = {
  30957. valignCentre: [],
  30958. alignCentre: [],
  30959. alignLeft: ['tox-pop--align-left'],
  30960. alignRight: ['tox-pop--align-right'],
  30961. right: ['tox-pop--right'],
  30962. left: ['tox-pop--left'],
  30963. bottom: ['tox-pop--bottom'],
  30964. top: ['tox-pop--top']
  30965. };
  30966. const isTouchWithinSelection = (editor, e) => {
  30967. const selection = editor.selection;
  30968. if (selection.isCollapsed() || e.touches.length < 1) {
  30969. return false;
  30970. }
  30971. else {
  30972. const touch = e.touches[0];
  30973. const rng = selection.getRng();
  30974. const rngRectOpt = getFirstRect(editor.getWin(), SimSelection.domRange(rng));
  30975. return rngRectOpt.exists((rngRect) => rngRect.left <= touch.clientX &&
  30976. rngRect.right >= touch.clientX &&
  30977. rngRect.top <= touch.clientY &&
  30978. rngRect.bottom >= touch.clientY);
  30979. }
  30980. };
  30981. const setupiOSOverrides = (editor) => {
  30982. // iOS will change the selection due to longpress also being a range selection gesture. As such we
  30983. // need to reset the selection back to the original selection after the touchend event has fired
  30984. const originalSelection = editor.selection.getRng();
  30985. const selectionReset = () => {
  30986. global$a.setEditorTimeout(editor, () => {
  30987. editor.selection.setRng(originalSelection);
  30988. }, 10);
  30989. unbindEventListeners();
  30990. };
  30991. editor.once('touchend', selectionReset);
  30992. // iPadOS will trigger a mousedown after the longpress which will close open context menus
  30993. // so we want to prevent that from running
  30994. const preventMousedown = (e) => {
  30995. e.preventDefault();
  30996. e.stopImmediatePropagation();
  30997. };
  30998. editor.on('mousedown', preventMousedown, true);
  30999. // If a longpresscancel is fired, then a touchmove has occurred so we shouldn't do any overrides
  31000. const clearSelectionReset = () => unbindEventListeners();
  31001. editor.once('longpresscancel', clearSelectionReset);
  31002. const unbindEventListeners = () => {
  31003. editor.off('touchend', selectionReset);
  31004. editor.off('longpresscancel', clearSelectionReset);
  31005. editor.off('mousedown', preventMousedown);
  31006. };
  31007. };
  31008. const getAnchorSpec = (editor, e, anchorType) => {
  31009. const anchorSpec = getAnchorSpec$1(editor, e, anchorType);
  31010. const bubbleYOffset = anchorType === 'point' ? bubbleSize : 0;
  31011. return {
  31012. bubble: nu$6(0, bubbleYOffset, bubbleAlignments),
  31013. layouts,
  31014. overrides: {
  31015. maxWidthFunction: expandable(),
  31016. maxHeightFunction: expandable$1()
  31017. },
  31018. ...anchorSpec
  31019. };
  31020. };
  31021. const show = (editor, e, items, backstage, contextmenu, anchorType, highlightImmediately) => {
  31022. const anchorSpec = getAnchorSpec(editor, e, anchorType);
  31023. build(items, ItemResponse$1.CLOSE_ON_EXECUTE, backstage, {
  31024. // MobileContextMenus are the *only* horizontal menus currently (2022-08-16)
  31025. isHorizontalMenu: true,
  31026. search: Optional.none()
  31027. }).map((menuData) => {
  31028. e.preventDefault();
  31029. // If we are highlighting immediately, then we want to highlight the menu
  31030. // and the item. Otherwise, we don't want to highlight anything.
  31031. const highlightOnOpen = highlightImmediately
  31032. ? HighlightOnOpen.HighlightMenuAndItem
  31033. : HighlightOnOpen.HighlightNone;
  31034. // Show the context menu, with items set to close on click
  31035. InlineView.showMenuWithinBounds(contextmenu, { anchor: anchorSpec }, {
  31036. menu: {
  31037. markers: markers('normal'),
  31038. highlightOnOpen
  31039. },
  31040. data: menuData,
  31041. type: 'horizontal'
  31042. }, () => Optional.some(getContextToolbarBounds(editor, backstage.shared, anchorType === 'node' ? 'node' : 'selection')));
  31043. // Ensure the context toolbar is hidden
  31044. editor.dispatch(hideContextToolbarEvent);
  31045. });
  31046. };
  31047. const initAndShow = (editor, e, buildMenu, backstage, contextmenu, anchorType) => {
  31048. const detection = detect$1();
  31049. const isiOS = detection.os.isiOS();
  31050. const isMacOS = detection.os.isMacOS();
  31051. const isAndroid = detection.os.isAndroid();
  31052. const isTouch = detection.deviceType.isTouch();
  31053. const shouldHighlightImmediately = () => !(isAndroid || isiOS || (isMacOS && isTouch));
  31054. const open = () => {
  31055. const items = buildMenu();
  31056. show(editor, e, items, backstage, contextmenu, anchorType, shouldHighlightImmediately());
  31057. };
  31058. // On iOS/iPadOS if we've long pressed on a ranged selection then we've already selected the content
  31059. // and just need to open the menu. Otherwise we need to wait for a selection change to occur as long
  31060. // press triggers a ranged selection on iOS.
  31061. if ((isMacOS || isiOS) && anchorType !== 'node') {
  31062. const openiOS = () => {
  31063. setupiOSOverrides(editor);
  31064. open();
  31065. };
  31066. if (isTouchWithinSelection(editor, e)) {
  31067. openiOS();
  31068. }
  31069. else {
  31070. editor.once('selectionchange', openiOS);
  31071. editor.once('touchend', () => editor.off('selectionchange', openiOS));
  31072. }
  31073. }
  31074. else {
  31075. open();
  31076. }
  31077. };
  31078. const isSeparator = (item) => isString(item) ? item === '|' : item.type === 'separator';
  31079. const separator = {
  31080. type: 'separator'
  31081. };
  31082. const makeContextItem = (item) => {
  31083. const commonMenuItem = (item) => ({
  31084. text: item.text,
  31085. icon: item.icon,
  31086. enabled: item.enabled,
  31087. shortcut: item.shortcut,
  31088. });
  31089. if (isString(item)) {
  31090. return item;
  31091. }
  31092. else {
  31093. switch (item.type) {
  31094. case 'separator':
  31095. return separator;
  31096. case 'submenu':
  31097. return {
  31098. type: 'nestedmenuitem',
  31099. ...commonMenuItem(item),
  31100. getSubmenuItems: () => {
  31101. const items = item.getSubmenuItems();
  31102. if (isString(items)) {
  31103. return items;
  31104. }
  31105. else {
  31106. return map$2(items, makeContextItem);
  31107. }
  31108. }
  31109. };
  31110. default:
  31111. // case 'item', or anything else really
  31112. const commonItem = item;
  31113. return {
  31114. type: 'menuitem',
  31115. ...commonMenuItem(commonItem),
  31116. // disconnect the function from the menu item API bridge defines
  31117. onAction: noarg(commonItem.onAction)
  31118. };
  31119. }
  31120. }
  31121. };
  31122. const addContextMenuGroup = (xs, groupItems) => {
  31123. // Skip if there are no items
  31124. if (groupItems.length === 0) {
  31125. return xs;
  31126. }
  31127. // Only add a separator at the beginning if the last item isn't a separator
  31128. const lastMenuItem = last$1(xs).filter((item) => !isSeparator(item));
  31129. const before = lastMenuItem.fold(() => [], (_) => [separator]);
  31130. return xs.concat(before).concat(groupItems).concat([separator]);
  31131. };
  31132. const generateContextMenu = (contextMenus, menuConfig, selectedElement) => {
  31133. const sections = foldl(menuConfig, (acc, name) => {
  31134. // Either read and convert the list of items out of the plugin, or assume it's a standard menu item reference
  31135. return get$h(contextMenus, name.toLowerCase()).map((menu) => {
  31136. const items = menu.update(selectedElement);
  31137. if (isString(items) && isNotEmpty(trim$1(items))) {
  31138. return addContextMenuGroup(acc, items.split(' '));
  31139. }
  31140. else if (isArray(items) && items.length > 0) {
  31141. // TODO: Should we add a StructureSchema check here?
  31142. const allItems = map$2(items, makeContextItem);
  31143. return addContextMenuGroup(acc, allItems);
  31144. }
  31145. else {
  31146. return acc;
  31147. }
  31148. }).getOrThunk(() => acc.concat([name]));
  31149. }, []);
  31150. // Strip off any trailing separator
  31151. if (sections.length > 0 && isSeparator(sections[sections.length - 1])) {
  31152. sections.pop();
  31153. }
  31154. return sections;
  31155. };
  31156. const isNativeOverrideKeyEvent = (editor, e) => e.ctrlKey && !shouldNeverUseNative(editor);
  31157. const isTouchEvent = (e) => e.type === 'longpress' || has$2(e, 'touches');
  31158. const isTriggeredByKeyboard = (editor, e) =>
  31159. // Different browsers trigger the context menu from keyboards differently, so need to check various different things here.
  31160. // If a longpress touch event, always treat it as a pointer event
  31161. // Chrome: button = 0, pointerType = undefined & target = the selection range node
  31162. // Firefox: button = 0, pointerType = undefined & target = body
  31163. // Safari: N/A (Mac's don't expose a contextmenu keyboard shortcut)
  31164. !isTouchEvent(e) && (e.button !== 2 || e.target === editor.getBody() && e.pointerType === '');
  31165. const getSelectedElement = (editor, e) => isTriggeredByKeyboard(editor, e) ? editor.selection.getStart(true) : e.target;
  31166. const getAnchorType = (editor, e) => {
  31167. const selector = getAvoidOverlapSelector(editor);
  31168. const anchorType = isTriggeredByKeyboard(editor, e) ? 'selection' : 'point';
  31169. if (isNotEmpty(selector)) {
  31170. const target = getSelectedElement(editor, e);
  31171. const selectorExists = closest$1(SugarElement.fromDom(target), selector);
  31172. return selectorExists ? 'node' : anchorType;
  31173. }
  31174. else {
  31175. return anchorType;
  31176. }
  31177. };
  31178. const setup$5 = (editor, lazySink, backstage) => {
  31179. const detection = detect$1();
  31180. const isTouch = detection.deviceType.isTouch;
  31181. const contextmenu = build$1(InlineView.sketch({
  31182. dom: {
  31183. tag: 'div'
  31184. },
  31185. lazySink,
  31186. onEscape: () => editor.focus(),
  31187. onShow: () => backstage.setContextMenuState(true),
  31188. onHide: () => backstage.setContextMenuState(false),
  31189. fireDismissalEventInstead: {},
  31190. inlineBehaviours: derive$1([
  31191. config('dismissContextMenu', [
  31192. run$1(dismissRequested(), (comp, _se) => {
  31193. Sandboxing.close(comp);
  31194. editor.focus();
  31195. })
  31196. ])
  31197. ])
  31198. }));
  31199. const hideContextMenu = () => InlineView.hide(contextmenu);
  31200. const showContextMenu = (e) => {
  31201. // Prevent the default if we should never use native
  31202. if (shouldNeverUseNative(editor)) {
  31203. e.preventDefault();
  31204. }
  31205. if (isNativeOverrideKeyEvent(editor, e) || isContextMenuDisabled(editor)) {
  31206. return;
  31207. }
  31208. const anchorType = getAnchorType(editor, e);
  31209. const buildMenu = () => {
  31210. // Use the event target element for touch events, otherwise fallback to the current selection
  31211. const selectedElement = getSelectedElement(editor, e);
  31212. const registry = editor.ui.registry.getAll();
  31213. const menuConfig = getContextMenu(editor);
  31214. return generateContextMenu(registry.contextMenus, menuConfig, selectedElement);
  31215. };
  31216. const initAndShow$2 = isTouch() ? initAndShow : initAndShow$1;
  31217. initAndShow$2(editor, e, buildMenu, backstage, contextmenu, anchorType);
  31218. };
  31219. editor.on('init', () => {
  31220. // Hide the context menu when scrolling or resizing
  31221. // Except ResizeWindow on mobile which fires when the keyboard appears/disappears
  31222. const hideEvents = 'ResizeEditor ScrollContent ScrollWindow longpresscancel' + (isTouch() ? '' : ' ResizeWindow');
  31223. editor.on(hideEvents, hideContextMenu);
  31224. editor.on('longpress contextmenu', showContextMenu);
  31225. });
  31226. };
  31227. const snapWidth = 40;
  31228. const snapOffset = snapWidth / 2;
  31229. // const insertDebugDiv = (left, top, width, height, color, clazz) => {
  31230. // const debugArea = SugarElement.fromHtml(`<div class="${clazz}"></div>`);
  31231. // Css.setAll(debugArea, {
  31232. // 'left': left.toString() + 'px',
  31233. // 'top': top.toString() + 'px',
  31234. // 'background-color': color,
  31235. // 'position': 'absolute',
  31236. // 'width': width.toString() + 'px',
  31237. // 'height': height.toString() + 'px',
  31238. // 'opacity': '0.2'
  31239. // });
  31240. // Insert.append(SugarBody.body(), debugArea);
  31241. // };
  31242. const calcSnap = (selectorOpt, td, x, y, width, height) => selectorOpt.fold(() => Dragging.snap({
  31243. sensor: absolute$1(x - snapOffset, y - snapOffset),
  31244. range: SugarPosition(width, height),
  31245. output: absolute$1(Optional.some(x), Optional.some(y)),
  31246. extra: {
  31247. td
  31248. }
  31249. }), (selectorHandle) => {
  31250. const sensorLeft = x - snapOffset;
  31251. const sensorTop = y - snapOffset;
  31252. const sensorWidth = snapWidth; // box.width();
  31253. const sensorHeight = snapWidth; // box.height();
  31254. const rect = selectorHandle.element.dom.getBoundingClientRect();
  31255. // insertDebugDiv(sensorLeft, sensorTop, sensorWidth, sensorHeight, 'green', 'top-left-snap-debug');
  31256. return Dragging.snap({
  31257. sensor: absolute$1(sensorLeft, sensorTop),
  31258. range: SugarPosition(sensorWidth, sensorHeight),
  31259. output: absolute$1(Optional.some(x - (rect.width / 2)), Optional.some(y - (rect.height / 2))),
  31260. extra: {
  31261. td
  31262. }
  31263. });
  31264. });
  31265. const getSnapsConfig = (getSnapPoints, cell, onChange) => {
  31266. // Can't use Optional.is() here since we need to do a dom compare, not an equality compare
  31267. const isSameCell = (cellOpt, td) => cellOpt.exists((currentTd) => eq(currentTd, td));
  31268. return {
  31269. getSnapPoints,
  31270. leftAttr: 'data-drag-left',
  31271. topAttr: 'data-drag-top',
  31272. onSensor: (component, extra) => {
  31273. const td = extra.td;
  31274. if (!isSameCell(cell.get(), td)) {
  31275. cell.set(td);
  31276. onChange(td);
  31277. }
  31278. },
  31279. mustSnap: true
  31280. };
  31281. };
  31282. const createSelector = (snaps) => record(Button.sketch({
  31283. dom: {
  31284. tag: 'div',
  31285. classes: ['tox-selector']
  31286. },
  31287. buttonBehaviours: derive$1([
  31288. Dragging.config({
  31289. mode: 'mouseOrTouch',
  31290. blockerClass: 'blocker',
  31291. snaps
  31292. }),
  31293. Unselecting.config({})
  31294. ]),
  31295. eventOrder: {
  31296. // Because this is a button, allow dragging. It will stop clicking.
  31297. mousedown: ['dragging', 'alloy.base.behaviour'],
  31298. touchstart: ['dragging', 'alloy.base.behaviour']
  31299. }
  31300. }));
  31301. const setup$4 = (editor, sink) => {
  31302. const tlTds = Cell([]);
  31303. const brTds = Cell([]);
  31304. const isVisible = Cell(false);
  31305. const startCell = value$2();
  31306. const finishCell = value$2();
  31307. const getTopLeftSnap = (td) => {
  31308. const box = absolute$2(td);
  31309. return calcSnap(memTopLeft.getOpt(sink), td, box.x, box.y, box.width, box.height);
  31310. };
  31311. const getTopLeftSnaps = () =>
  31312. // const body = SugarBody.body();
  31313. // const debugs = SelectorFilter.descendants(body, '.top-left-snap-debug');
  31314. // Arr.each(debugs, (debugArea) => {
  31315. // Remove.remove(debugArea);
  31316. // });
  31317. map$2(tlTds.get(), (td) => getTopLeftSnap(td));
  31318. const getBottomRightSnap = (td) => {
  31319. const box = absolute$2(td);
  31320. return calcSnap(memBottomRight.getOpt(sink), td, box.right, box.bottom, box.width, box.height);
  31321. };
  31322. const getBottomRightSnaps = () =>
  31323. // const body = SugarBody.body();
  31324. // const debugs = SelectorFilter.descendants(body, '.bottom-right-snap-debug');
  31325. // Arr.each(debugs, (debugArea) => {
  31326. // Remove.remove(debugArea);
  31327. // });
  31328. map$2(brTds.get(), (td) => getBottomRightSnap(td));
  31329. const topLeftSnaps = getSnapsConfig(getTopLeftSnaps, startCell, (start) => {
  31330. finishCell.get().each((finish) => {
  31331. editor.dispatch('TableSelectorChange', { start, finish });
  31332. });
  31333. });
  31334. const bottomRightSnaps = getSnapsConfig(getBottomRightSnaps, finishCell, (finish) => {
  31335. startCell.get().each((start) => {
  31336. editor.dispatch('TableSelectorChange', { start, finish });
  31337. });
  31338. });
  31339. const memTopLeft = createSelector(topLeftSnaps);
  31340. const memBottomRight = createSelector(bottomRightSnaps);
  31341. const topLeft = build$1(memTopLeft.asSpec());
  31342. const bottomRight = build$1(memBottomRight.asSpec());
  31343. const showOrHideHandle = (selector, cell, isAbove, isBelow) => {
  31344. const cellRect = cell.dom.getBoundingClientRect();
  31345. remove$6(selector.element, 'display');
  31346. const viewportHeight = defaultView(SugarElement.fromDom(editor.getBody())).dom.innerHeight;
  31347. const aboveViewport = isAbove(cellRect);
  31348. const belowViewport = isBelow(cellRect, viewportHeight);
  31349. if (aboveViewport || belowViewport) {
  31350. set$7(selector.element, 'display', 'none');
  31351. }
  31352. };
  31353. const snapTo = (selector, cell, getSnapConfig, pos) => {
  31354. const snap = getSnapConfig(cell);
  31355. Dragging.snapTo(selector, snap);
  31356. const isAbove = (rect) => rect[pos] < 0;
  31357. const isBelow = (rect, viewportHeight) => rect[pos] > viewportHeight;
  31358. showOrHideHandle(selector, cell, isAbove, isBelow);
  31359. };
  31360. const snapTopLeft = (cell) => snapTo(topLeft, cell, getTopLeftSnap, 'top');
  31361. const snapLastTopLeft = () => startCell.get().each(snapTopLeft);
  31362. const snapBottomRight = (cell) => snapTo(bottomRight, cell, getBottomRightSnap, 'bottom');
  31363. const snapLastBottomRight = () => finishCell.get().each(snapBottomRight);
  31364. // TODO: Make this work for desktop maybe?
  31365. if (detect$1().deviceType.isTouch()) {
  31366. const domToSugar = (arr) => map$2(arr, SugarElement.fromDom);
  31367. editor.on('TableSelectionChange', (e) => {
  31368. if (!isVisible.get()) {
  31369. attach(sink, topLeft);
  31370. attach(sink, bottomRight);
  31371. isVisible.set(true);
  31372. }
  31373. const start = SugarElement.fromDom(e.start);
  31374. const finish = SugarElement.fromDom(e.finish);
  31375. startCell.set(start);
  31376. finishCell.set(finish);
  31377. Optional.from(e.otherCells).each((otherCells) => {
  31378. tlTds.set(domToSugar(otherCells.upOrLeftCells));
  31379. brTds.set(domToSugar(otherCells.downOrRightCells));
  31380. snapTopLeft(start);
  31381. snapBottomRight(finish);
  31382. });
  31383. });
  31384. editor.on('ResizeEditor ResizeWindow ScrollContent', () => {
  31385. snapLastTopLeft();
  31386. snapLastBottomRight();
  31387. });
  31388. editor.on('TableSelectionClear', () => {
  31389. if (isVisible.get()) {
  31390. detach(topLeft);
  31391. detach(bottomRight);
  31392. isVisible.set(false);
  31393. }
  31394. startCell.clear();
  31395. finishCell.clear();
  31396. });
  31397. }
  31398. };
  31399. var Logo = "<svg height=\"16\" viewBox=\"0 0 80 16\" width=\"80\" xmlns=\"http://www.w3.org/2000/svg\"><g opacity=\".8\"><path d=\"m80 3.537v-2.202h-7.976v11.585h7.976v-2.25h-5.474v-2.621h4.812v-2.069h-4.812v-2.443zm-10.647 6.929c-.493.217-1.13.337-1.864.337s-1.276-.156-1.805-.47a3.732 3.732 0 0 1 -1.3-1.298c-.324-.554-.48-1.191-.48-1.877s.156-1.335.48-1.877a3.635 3.635 0 0 1 1.3-1.299 3.466 3.466 0 0 1 1.805-.481c.65 0 .914.06 1.263.18.36.12.698.277.986.47.289.192.578.384.842.6l.12.085v-2.586l-.023-.024c-.385-.35-.855-.614-1.384-.818-.53-.205-1.155-.313-1.877-.313-.721 0-1.6.144-2.333.445a5.773 5.773 0 0 0 -1.937 1.251 5.929 5.929 0 0 0 -1.324 1.9c-.324.735-.48 1.565-.48 2.455s.156 1.72.48 2.454c.325.734.758 1.383 1.324 1.913.553.53 1.215.938 1.937 1.25a6.286 6.286 0 0 0 2.333.434c.819 0 1.384-.108 1.961-.313.59-.216 1.083-.505 1.468-.866l.024-.024v-2.49l-.12.096c-.41.337-.878.626-1.396.866zm-14.869-4.15-4.8-5.04-.024-.025h-.902v11.67h2.502v-6.847l2.827 3.08.385.409.397-.41 2.791-3.067v6.845h2.502v-11.679h-.902l-4.788 5.052z\"/><path clip-rule=\"evenodd\" d=\"m15.543 5.137c0-3.032-2.466-5.113-4.957-5.137-.36 0-.745.024-1.094.096-.157.024-3.85.758-3.85.758-3.032.602-4.62 2.466-4.704 4.788-.024.89-.024 4.27-.024 4.27.036 3.165 2.406 5.138 5.017 5.126.337 0 1.119-.109 1.287-.145.144-.024.385-.084.746-.144.661-.12 1.684-.325 3.067-.602 2.37-.409 4.103-2.009 4.44-4.33.156-1.023.084-4.692.084-4.692zm-3.213 3.308-2.346.457v2.31l-5.859 1.143v-5.75l2.346-.458v3.441l3.513-.686v-3.44l-3.513.685v-2.297l5.859-1.143v5.75zm20.09-3.296-.083-1.023h-2.13v8.794h2.346v-4.884c0-1.107.95-1.985 2.057-1.997 1.095 0 1.901.89 1.901 1.997v4.884h2.346v-5.245c-.012-2.105-1.588-3.777-3.67-3.765a3.764 3.764 0 0 0 -2.778 1.25l.012-.011zm-6.014-4.102 2.346-.458v2.298l-2.346.457z\" fill-rule=\"evenodd\"/><path d=\"m28.752 4.126h-2.346v8.794h2.346z\"/><path clip-rule=\"evenodd\" d=\"m43.777 15.483 4.043-11.357h-2.418l-1.54 4.355-.445 1.324-.36-1.324-1.54-4.355h-2.418l3.151 8.794-1.083 3.08zm-21.028-5.51c0 .722.541 1.034.878 1.034s.638-.048.95-.144l.518 1.708c-.217.145-.879.518-2.13.518a2.565 2.565 0 0 1 -2.562-2.587c-.024-1.082-.024-2.49 0-4.21h-1.54v-2.142h1.54v-1.912l2.346-.458v2.37h2.201v2.142h-2.2v3.693-.012z\" fill-rule=\"evenodd\"/></g></svg>\n";
  31400. const isHidden = (elm) => elm.nodeName === 'BR' || !!elm.getAttribute('data-mce-bogus') || elm.getAttribute('data-mce-type') === 'bookmark';
  31401. const renderElementPath = (editor, settings, providersBackstage) => {
  31402. var _a;
  31403. const delimiter = (_a = settings.delimiter) !== null && _a !== void 0 ? _a : '\u203A';
  31404. const renderElement = (name, element, index) => Button.sketch({
  31405. dom: {
  31406. tag: 'div',
  31407. classes: ['tox-statusbar__path-item'],
  31408. attributes: {
  31409. 'data-index': index,
  31410. }
  31411. },
  31412. components: [
  31413. text$2(name)
  31414. ],
  31415. action: (_btn) => {
  31416. editor.focus();
  31417. editor.selection.select(element);
  31418. editor.nodeChanged();
  31419. },
  31420. buttonBehaviours: derive$1([
  31421. Tooltipping.config({
  31422. ...providersBackstage.tooltips.getConfig({
  31423. tooltipText: providersBackstage.translate(['Select the {0} element', element.nodeName.toLowerCase()]),
  31424. onShow: (comp, tooltip) => {
  31425. describedBy(comp.element, tooltip.element);
  31426. },
  31427. onHide: (comp) => {
  31428. remove$1(comp.element);
  31429. }
  31430. }),
  31431. }),
  31432. DisablingConfigs.button(providersBackstage.isDisabled),
  31433. toggleOnReceive(() => providersBackstage.checkUiComponentContext('any'))
  31434. ])
  31435. });
  31436. const renderDivider = () => ({
  31437. dom: {
  31438. tag: 'div',
  31439. classes: ['tox-statusbar__path-divider'],
  31440. attributes: {
  31441. 'aria-hidden': true
  31442. }
  31443. },
  31444. components: [
  31445. text$2(` ${delimiter} `)
  31446. ]
  31447. });
  31448. const renderPathData = (data) => foldl(data, (acc, path, index) => {
  31449. const element = renderElement(path.name, path.element, index);
  31450. if (index === 0) {
  31451. return acc.concat([element]);
  31452. }
  31453. else {
  31454. return acc.concat([renderDivider(), element]);
  31455. }
  31456. }, []);
  31457. const updatePath = (parents) => {
  31458. const newPath = [];
  31459. let i = parents.length;
  31460. while (i-- > 0) {
  31461. const parent = parents[i];
  31462. if (parent.nodeType === 1 && !isHidden(parent)) {
  31463. const args = fireResolveName(editor, parent);
  31464. if (!args.isDefaultPrevented()) {
  31465. newPath.push({ name: args.name, element: parent });
  31466. }
  31467. if (args.isPropagationStopped()) {
  31468. break;
  31469. }
  31470. }
  31471. }
  31472. return newPath;
  31473. };
  31474. return {
  31475. dom: {
  31476. tag: 'div',
  31477. classes: ['tox-statusbar__path'],
  31478. attributes: {
  31479. role: 'navigation'
  31480. }
  31481. },
  31482. behaviours: derive$1([
  31483. Keying.config({
  31484. mode: 'flow',
  31485. selector: 'div[role=button]'
  31486. }),
  31487. Disabling.config({
  31488. disabled: providersBackstage.isDisabled
  31489. }),
  31490. toggleOnReceive(() => providersBackstage.checkUiComponentContext('any')),
  31491. Tabstopping.config({}),
  31492. Replacing.config({}),
  31493. config('elementPathEvents', [
  31494. runOnAttached((comp, _e) => {
  31495. // NOTE: If statusbar ever gets re-rendered, we will need to free this.
  31496. editor.shortcuts.add('alt+F11', 'focus statusbar elementpath', () => Keying.focusIn(comp));
  31497. editor.on('NodeChange', (e) => {
  31498. const newPath = updatePath(e.parents);
  31499. const newChildren = newPath.length > 0 ? renderPathData(newPath) : [];
  31500. Replacing.set(comp, newChildren);
  31501. });
  31502. })
  31503. ])
  31504. ]),
  31505. components: []
  31506. };
  31507. };
  31508. var ResizeTypes;
  31509. (function (ResizeTypes) {
  31510. ResizeTypes[ResizeTypes["None"] = 0] = "None";
  31511. ResizeTypes[ResizeTypes["Both"] = 1] = "Both";
  31512. ResizeTypes[ResizeTypes["Vertical"] = 2] = "Vertical";
  31513. })(ResizeTypes || (ResizeTypes = {}));
  31514. const getOriginalDimensions = (editor) => {
  31515. const container = SugarElement.fromDom(editor.getContainer());
  31516. const originalHeight = get$d(container);
  31517. const originalWidth = get$c(container);
  31518. return {
  31519. height: originalHeight,
  31520. width: originalWidth,
  31521. };
  31522. };
  31523. const getDimensions = (editor, deltas, resizeType, originalDimentions) => {
  31524. const dimensions = {
  31525. height: calcCappedSize(originalDimentions.height + deltas.top, getMinHeightOption(editor), getMaxHeightOption(editor)),
  31526. width: resizeType === ResizeTypes.Both
  31527. ? calcCappedSize(originalDimentions.width + deltas.left, getMinWidthOption(editor), getMaxWidthOption(editor))
  31528. : originalDimentions.width,
  31529. };
  31530. return dimensions;
  31531. };
  31532. const resize = (editor, deltas, resizeType) => {
  31533. const container = SugarElement.fromDom(editor.getContainer());
  31534. const originalDimentions = getOriginalDimensions(editor);
  31535. const dimensions = getDimensions(editor, deltas, resizeType, originalDimentions);
  31536. each(dimensions, (val, dim) => {
  31537. if (isNumber(val)) {
  31538. set$7(container, dim, numToPx(val));
  31539. }
  31540. });
  31541. fireResizeEditor(editor);
  31542. return dimensions;
  31543. };
  31544. const getResizeType = (editor) => {
  31545. const resize = getResize(editor);
  31546. if (resize === false) {
  31547. return ResizeTypes.None;
  31548. }
  31549. else if (resize === 'both') {
  31550. return ResizeTypes.Both;
  31551. }
  31552. else {
  31553. return ResizeTypes.Vertical;
  31554. }
  31555. };
  31556. const getAriaValuetext = (dimensions, resizeType) => {
  31557. return resizeType === ResizeTypes.Both
  31558. ? global$6.translate([`Editor's height: {0} pixels, Editor's width: {1} pixels`, dimensions.height, dimensions.width])
  31559. : global$6.translate([`Editor's height: {0} pixels`, dimensions.height]);
  31560. };
  31561. const setAriaValuetext = (comp, dimensions, resizeType) => {
  31562. set$9(comp.element, 'aria-valuetext', getAriaValuetext(dimensions, resizeType));
  31563. };
  31564. const keyboardHandler = (editor, comp, resizeType, x, y) => {
  31565. const scale = 20;
  31566. const delta = SugarPosition(x * scale, y * scale);
  31567. const newDimentions = resize(editor, delta, resizeType);
  31568. setAriaValuetext(comp, newDimentions, resizeType);
  31569. return Optional.some(true);
  31570. };
  31571. const renderResizeHandler = (editor, providersBackstage) => {
  31572. const resizeType = getResizeType(editor);
  31573. if (resizeType === ResizeTypes.None) {
  31574. return Optional.none();
  31575. }
  31576. const resizeLabel = resizeType === ResizeTypes.Both
  31577. ? global$6.translate('Press the arrow keys to resize the editor.')
  31578. : global$6.translate('Press the Up and Down arrow keys to resize the editor.');
  31579. const cursorClass = resizeType === ResizeTypes.Both
  31580. ? 'tox-statusbar__resize-cursor-both'
  31581. : 'tox-statusbar__resize-cursor-default';
  31582. return Optional.some(render$4('resize-handle', {
  31583. tag: 'div',
  31584. classes: ['tox-statusbar__resize-handle', cursorClass],
  31585. attributes: {
  31586. 'aria-label': providersBackstage.translate(resizeLabel),
  31587. 'data-mce-name': 'resize-handle',
  31588. 'role': 'separator'
  31589. },
  31590. behaviours: [
  31591. Dragging.config({
  31592. mode: 'mouse',
  31593. repositionTarget: false,
  31594. onDrag: (comp, _target, delta) => {
  31595. const newDimentions = resize(editor, delta, resizeType);
  31596. setAriaValuetext(comp, newDimentions, resizeType);
  31597. },
  31598. blockerClass: 'tox-blocker'
  31599. }),
  31600. Keying.config({
  31601. mode: 'special',
  31602. onLeft: (comp) => keyboardHandler(editor, comp, resizeType, -1, 0),
  31603. onRight: (comp) => keyboardHandler(editor, comp, resizeType, 1, 0),
  31604. onUp: (comp) => keyboardHandler(editor, comp, resizeType, 0, -1),
  31605. onDown: (comp) => keyboardHandler(editor, comp, resizeType, 0, 1),
  31606. }),
  31607. Tabstopping.config({}),
  31608. Focusing.config({}),
  31609. Tooltipping.config(providersBackstage.tooltips.getConfig({
  31610. tooltipText: providersBackstage.translate('Resize')
  31611. })),
  31612. config('set-aria-valuetext', [
  31613. runOnAttached((comp) => {
  31614. const setInitialValuetext = () => {
  31615. setAriaValuetext(comp, getOriginalDimensions(editor), resizeType);
  31616. };
  31617. if (editor._skinLoaded) {
  31618. setInitialValuetext();
  31619. }
  31620. else {
  31621. editor.once('SkinLoaded', setInitialValuetext);
  31622. }
  31623. })
  31624. ])
  31625. ],
  31626. eventOrder: {
  31627. [attachedToDom()]: ['add-focusable', 'set-aria-valuetext']
  31628. }
  31629. }, providersBackstage.icons));
  31630. };
  31631. const renderWordCount = (editor, providersBackstage) => {
  31632. const replaceCountText = (comp, count, mode) => Replacing.set(comp, [text$2(providersBackstage.translate(['{0} ' + mode, count[mode]]))]);
  31633. return Button.sketch({
  31634. dom: {
  31635. // The tag for word count was changed to 'button' as Jaws does not read out spans.
  31636. // Word count is just a toggle and changes modes between words and characters.
  31637. tag: 'button',
  31638. classes: ['tox-statusbar__wordcount']
  31639. },
  31640. components: [],
  31641. buttonBehaviours: derive$1([
  31642. DisablingConfigs.button(providersBackstage.isDisabled),
  31643. toggleOnReceive(() => providersBackstage.checkUiComponentContext('any')),
  31644. Tabstopping.config({}),
  31645. Replacing.config({}),
  31646. Representing.config({
  31647. store: {
  31648. mode: 'memory',
  31649. initialValue: {
  31650. mode: "words" /* WordCountMode.Words */,
  31651. count: { words: 0, characters: 0 }
  31652. }
  31653. }
  31654. }),
  31655. config('wordcount-events', [
  31656. runOnExecute$1((comp) => {
  31657. const currentVal = Representing.getValue(comp);
  31658. const newMode = currentVal.mode === "words" /* WordCountMode.Words */ ? "characters" /* WordCountMode.Characters */ : "words" /* WordCountMode.Words */;
  31659. Representing.setValue(comp, { mode: newMode, count: currentVal.count });
  31660. replaceCountText(comp, currentVal.count, newMode);
  31661. }),
  31662. runOnAttached((comp) => {
  31663. editor.on('wordCountUpdate', (e) => {
  31664. const { mode } = Representing.getValue(comp);
  31665. Representing.setValue(comp, { mode, count: e.wordCount });
  31666. replaceCountText(comp, e.wordCount, mode);
  31667. });
  31668. })
  31669. ])
  31670. ]),
  31671. eventOrder: {
  31672. [execute$5()]: ['disabling', 'alloy.base.behaviour', 'wordcount-events']
  31673. }
  31674. });
  31675. };
  31676. const renderStatusbar = (editor, providersBackstage) => {
  31677. const renderBranding = () => {
  31678. return {
  31679. dom: {
  31680. tag: 'span',
  31681. classes: ['tox-statusbar__branding'],
  31682. },
  31683. components: [
  31684. {
  31685. dom: {
  31686. tag: 'a',
  31687. attributes: {
  31688. 'href': 'https://www.tiny.cloud/powered-by-tiny?utm_campaign=poweredby&utm_source=tiny&utm_medium=referral&utm_content=v7',
  31689. 'rel': 'noopener',
  31690. 'target': '_blank',
  31691. 'aria-label': editor.translate(['Build with {0}', 'TinyMCE'])
  31692. },
  31693. innerHtml: editor.translate(['Build with {0}', Logo.trim()])
  31694. },
  31695. behaviours: derive$1([
  31696. Focusing.config({})
  31697. ])
  31698. }
  31699. ]
  31700. };
  31701. };
  31702. const renderHelpAccessibility = () => {
  31703. const shortcutText = convertText('Alt+0');
  31704. const text = `Press {0} for help`;
  31705. return {
  31706. dom: {
  31707. tag: 'div',
  31708. classes: ['tox-statusbar__help-text'],
  31709. },
  31710. components: [
  31711. text$2(global$6.translate([text, shortcutText]))
  31712. ]
  31713. };
  31714. };
  31715. const renderRightContainer = () => {
  31716. const components = [];
  31717. if (editor.hasPlugin('wordcount')) {
  31718. components.push(renderWordCount(editor, providersBackstage));
  31719. }
  31720. if (useBranding(editor)) {
  31721. components.push(renderBranding());
  31722. }
  31723. return {
  31724. dom: {
  31725. tag: 'div',
  31726. classes: ['tox-statusbar__right-container']
  31727. },
  31728. components
  31729. };
  31730. };
  31731. const getTextComponents = () => {
  31732. const components = [];
  31733. const shouldRenderHelp = useHelpAccessibility(editor);
  31734. const shouldRenderElementPath = useElementPath(editor);
  31735. const shouldRenderRightContainer = useBranding(editor) || editor.hasPlugin('wordcount');
  31736. const getTextComponentClasses = () => {
  31737. const flexStart = 'tox-statusbar__text-container--flex-start';
  31738. const flexEnd = 'tox-statusbar__text-container--flex-end';
  31739. const spaceAround = 'tox-statusbar__text-container--space-around';
  31740. if (shouldRenderHelp) {
  31741. const container3Columns = 'tox-statusbar__text-container-3-cols';
  31742. if (!shouldRenderRightContainer && !shouldRenderElementPath) {
  31743. return [container3Columns, spaceAround];
  31744. }
  31745. if (shouldRenderRightContainer && !shouldRenderElementPath) {
  31746. return [container3Columns, flexEnd];
  31747. }
  31748. return [container3Columns, flexStart];
  31749. }
  31750. return [shouldRenderRightContainer && !shouldRenderElementPath ? flexEnd : flexStart];
  31751. };
  31752. if (shouldRenderElementPath) {
  31753. components.push(renderElementPath(editor, {}, providersBackstage));
  31754. }
  31755. if (shouldRenderHelp) {
  31756. components.push(renderHelpAccessibility());
  31757. }
  31758. if (shouldRenderRightContainer) {
  31759. components.push(renderRightContainer());
  31760. }
  31761. if (components.length > 0) {
  31762. return [{
  31763. dom: {
  31764. tag: 'div',
  31765. classes: ['tox-statusbar__text-container', ...getTextComponentClasses()]
  31766. },
  31767. components
  31768. }];
  31769. }
  31770. return [];
  31771. };
  31772. const getComponents = () => {
  31773. const components = getTextComponents();
  31774. const resizeHandler = renderResizeHandler(editor, providersBackstage);
  31775. return components.concat(resizeHandler.toArray());
  31776. };
  31777. return {
  31778. dom: {
  31779. tag: 'div',
  31780. classes: ['tox-statusbar']
  31781. },
  31782. components: getComponents()
  31783. };
  31784. };
  31785. const getLazyMothership = (label, singleton) => singleton.get().getOrDie(`UI for ${label} has not been rendered`);
  31786. const setup$3 = (editor, setupForTheme) => {
  31787. const isInline = editor.inline;
  31788. const mode = isInline ? Inline : Iframe;
  31789. // We use a different component for creating the sticky toolbar behaviour. The
  31790. // most important difference is it needs "Docking" configured and all of the
  31791. // ripple effects that creates.
  31792. const header = isStickyToolbar(editor) ? StickyHeader : StaticHeader;
  31793. const lazyUiRefs = LazyUiReferences();
  31794. // Importantly, this is outside the setup function.
  31795. const lazyMothership = value$2();
  31796. const lazyDialogMothership = value$2();
  31797. const lazyPopupMothership = value$2();
  31798. const platform = detect$1();
  31799. const isTouch = platform.deviceType.isTouch();
  31800. const touchPlatformClass = 'tox-platform-touch';
  31801. const deviceClasses = isTouch ? [touchPlatformClass] : [];
  31802. const isToolbarBottom = isToolbarLocationBottom(editor);
  31803. const toolbarMode = getToolbarMode(editor);
  31804. const memAnchorBar = record({
  31805. dom: {
  31806. tag: 'div',
  31807. classes: ['tox-anchorbar']
  31808. }
  31809. });
  31810. const memBottomAnchorBar = record({
  31811. dom: {
  31812. tag: 'div',
  31813. classes: ['tox-bottom-anchorbar']
  31814. }
  31815. });
  31816. const lazyHeader = () => lazyUiRefs.mainUi.get()
  31817. .map((ui) => ui.outerContainer)
  31818. .bind(OuterContainer.getHeader);
  31819. const lazyDialogSinkResult = () => Result.fromOption(lazyUiRefs.dialogUi.get().map((ui) => ui.sink), 'UI has not been rendered');
  31820. const lazyPopupSinkResult = () => Result.fromOption(lazyUiRefs.popupUi.get().map((ui) => ui.sink), '(popup) UI has not been rendered');
  31821. const lazyAnchorBar = lazyUiRefs.lazyGetInOuterOrDie('anchor bar', memAnchorBar.getOpt);
  31822. const lazyBottomAnchorBar = lazyUiRefs.lazyGetInOuterOrDie('bottom anchor bar', memBottomAnchorBar.getOpt);
  31823. const lazyToolbar = lazyUiRefs.lazyGetInOuterOrDie('toolbar', OuterContainer.getToolbar);
  31824. const lazyThrobber = lazyUiRefs.lazyGetInOuterOrDie('throbber', OuterContainer.getThrobber);
  31825. // Here, we build the backstage. The backstage is going to use different sinks for dialog
  31826. // vs popup.
  31827. const backstages = init({
  31828. popup: lazyPopupSinkResult,
  31829. dialog: lazyDialogSinkResult
  31830. }, editor, lazyAnchorBar, lazyBottomAnchorBar);
  31831. const makeHeaderPart = () => {
  31832. const verticalDirAttributes = {
  31833. attributes: {
  31834. [Attribute]: isToolbarBottom ?
  31835. AttributeValue.BottomToTop :
  31836. AttributeValue.TopToBottom
  31837. }
  31838. };
  31839. const partMenubar = OuterContainer.parts.menubar({
  31840. dom: {
  31841. tag: 'div',
  31842. classes: ['tox-menubar']
  31843. },
  31844. // TINY-9223: The menu bar should scroll with the editor.
  31845. backstage: backstages.popup,
  31846. onEscape: () => {
  31847. editor.focus();
  31848. }
  31849. });
  31850. const partToolbar = OuterContainer.parts.toolbar({
  31851. dom: {
  31852. tag: 'div',
  31853. classes: ['tox-toolbar']
  31854. },
  31855. getSink: backstages.popup.shared.getSink,
  31856. providers: backstages.popup.shared.providers,
  31857. onEscape: () => {
  31858. editor.focus();
  31859. },
  31860. onToolbarToggled: (state) => {
  31861. fireToggleToolbarDrawer(editor, state);
  31862. },
  31863. type: toolbarMode,
  31864. lazyToolbar,
  31865. lazyHeader: () => lazyHeader().getOrDie('Could not find header element'),
  31866. ...verticalDirAttributes
  31867. });
  31868. const partMultipleToolbar = OuterContainer.parts['multiple-toolbar']({
  31869. dom: {
  31870. tag: 'div',
  31871. classes: ['tox-toolbar-overlord']
  31872. },
  31873. providers: backstages.popup.shared.providers,
  31874. onEscape: () => {
  31875. editor.focus();
  31876. },
  31877. type: toolbarMode
  31878. });
  31879. // False should stop the menubar and toolbar rendering altogether
  31880. const hasMultipleToolbar = isMultipleToolbars(editor);
  31881. const hasToolbar = isToolbarEnabled(editor);
  31882. const hasMenubar = isMenubarEnabled(editor);
  31883. const shouldHavePromotionLink = promotionEnabled(editor);
  31884. const partPromotion = makePromotion(shouldHavePromotionLink);
  31885. const hasAnyContents = hasMultipleToolbar || hasToolbar || hasMenubar;
  31886. const getPartToolbar = () => {
  31887. if (hasMultipleToolbar) {
  31888. return [partMultipleToolbar];
  31889. }
  31890. else if (hasToolbar) {
  31891. return [partToolbar];
  31892. }
  31893. else {
  31894. return [];
  31895. }
  31896. };
  31897. const menubarCollection = [partPromotion, partMenubar];
  31898. return OuterContainer.parts.header({
  31899. dom: {
  31900. tag: 'div',
  31901. classes: ['tox-editor-header']
  31902. .concat(hasAnyContents ? [] : ['tox-editor-header--empty']),
  31903. ...verticalDirAttributes
  31904. },
  31905. components: flatten([
  31906. hasMenubar ? menubarCollection : [],
  31907. getPartToolbar(),
  31908. // fixed_toolbar_container anchors to the editable area, else add an anchor bar
  31909. useFixedContainer(editor) ? [] : [memAnchorBar.asSpec()]
  31910. ]),
  31911. sticky: isStickyToolbar(editor),
  31912. editor,
  31913. // TINY-9223: If using a sticky toolbar, which sink should it really go in?
  31914. sharedBackstage: backstages.popup.shared
  31915. });
  31916. };
  31917. const makePromotion = (promotionLink) => {
  31918. return OuterContainer.parts.promotion({
  31919. dom: {
  31920. tag: 'div',
  31921. classes: ['tox-promotion'],
  31922. },
  31923. promotionLink
  31924. });
  31925. };
  31926. const makeSidebarDefinition = () => {
  31927. const partSocket = OuterContainer.parts.socket({
  31928. dom: {
  31929. tag: 'div',
  31930. classes: ['tox-edit-area']
  31931. }
  31932. });
  31933. const partSidebar = OuterContainer.parts.sidebar({
  31934. dom: {
  31935. tag: 'div',
  31936. classes: ['tox-sidebar']
  31937. }
  31938. });
  31939. return {
  31940. dom: {
  31941. tag: 'div',
  31942. classes: ['tox-sidebar-wrap']
  31943. },
  31944. components: [
  31945. partSocket,
  31946. partSidebar
  31947. ]
  31948. };
  31949. };
  31950. const renderDialogUi = () => {
  31951. const uiContainer = getUiContainer(editor);
  31952. // TINY-3321: When the body is using a grid layout, we need to ensure the sink width is manually set
  31953. const isGridUiContainer = eq(body(), uiContainer) && get$e(uiContainer, 'display') === 'grid';
  31954. const sinkSpec = {
  31955. dom: {
  31956. tag: 'div',
  31957. classes: ['tox', 'tox-silver-sink', 'tox-tinymce-aux'].concat(deviceClasses),
  31958. attributes: {
  31959. ...global$6.isRtl() ? { dir: 'rtl' } : {}
  31960. }
  31961. },
  31962. behaviours: derive$1([
  31963. Positioning.config({
  31964. useFixed: () => header.isDocked(lazyHeader)
  31965. })
  31966. ])
  31967. };
  31968. const reactiveWidthSpec = {
  31969. dom: {
  31970. styles: { width: document.body.clientWidth + 'px' }
  31971. },
  31972. events: derive$2([
  31973. run$1(windowResize(), (comp) => {
  31974. set$7(comp.element, 'width', document.body.clientWidth + 'px');
  31975. })
  31976. ])
  31977. };
  31978. const sink = build$1(deepMerge(sinkSpec, isGridUiContainer ? reactiveWidthSpec : {}));
  31979. const uiMothership = takeover(sink);
  31980. lazyDialogMothership.set(uiMothership);
  31981. return { sink, mothership: uiMothership };
  31982. };
  31983. const renderPopupUi = () => {
  31984. // TINY-9226: Because the popupUi is going to be placed adjacent to the editor, we aren't currently
  31985. // implementing the behaviour to reset widths based on window sizing. It is a workaround that
  31986. // is mainly targeted at Ui containers in the root. However, we may need to revisit this
  31987. // if the ui_mode: split setting is commonly used when the editor is at the root level, and the
  31988. // page has size-unfriendly CSS for sinks (like CSS grid)
  31989. const sinkSpec = {
  31990. dom: {
  31991. tag: 'div',
  31992. classes: ['tox', 'tox-silver-sink', 'tox-silver-popup-sink', 'tox-tinymce-aux'].concat(deviceClasses),
  31993. attributes: {
  31994. ...global$6.isRtl() ? { dir: 'rtl' } : {}
  31995. }
  31996. },
  31997. behaviours: derive$1([
  31998. Positioning.config({
  31999. useFixed: () => header.isDocked(lazyHeader),
  32000. // TINY-9226: We want to limit the popup sink's bounds based on its scrolling environment. We don't
  32001. // want it to try to position things outside of its scrolling viewport, because they will
  32002. // just appear offscreen (hidden by the current scroll values)
  32003. getBounds: () => setupForTheme.getPopupSinkBounds()
  32004. })
  32005. ])
  32006. };
  32007. const sink = build$1(sinkSpec);
  32008. const uiMothership = takeover(sink);
  32009. lazyPopupMothership.set(uiMothership);
  32010. return { sink, mothership: uiMothership };
  32011. };
  32012. const renderMainUi = () => {
  32013. const partHeader = makeHeaderPart();
  32014. const sidebarContainer = makeSidebarDefinition();
  32015. const partThrobber = OuterContainer.parts.throbber({
  32016. dom: {
  32017. tag: 'div',
  32018. classes: ['tox-throbber']
  32019. },
  32020. backstage: backstages.popup
  32021. });
  32022. const partViewWrapper = OuterContainer.parts.viewWrapper({
  32023. backstage: backstages.popup
  32024. });
  32025. const statusbar = useStatusBar(editor) && !isInline ? Optional.some(renderStatusbar(editor, backstages.popup.shared.providers)) : Optional.none();
  32026. // We need the statusbar to be separate to everything else so resizing works properly
  32027. const editorComponents = flatten([
  32028. isToolbarBottom ? [] : [partHeader],
  32029. // Inline mode does not have a socket/sidebar
  32030. isInline ? [] : [sidebarContainer],
  32031. isToolbarBottom ? [partHeader] : []
  32032. ]);
  32033. const editorContainer = OuterContainer.parts.editorContainer({
  32034. components: flatten([
  32035. editorComponents,
  32036. isInline ? [] : [memBottomAnchorBar.asSpec()]
  32037. ])
  32038. });
  32039. // Hide the outer container if using inline mode and there's no menubar or toolbar
  32040. const isHidden = isDistractionFree(editor);
  32041. const attributes = {
  32042. role: 'application',
  32043. ...global$6.isRtl() ? { dir: 'rtl' } : {},
  32044. ...isHidden ? { 'aria-hidden': 'true' } : {}
  32045. };
  32046. const outerContainer = build$1(OuterContainer.sketch({
  32047. dom: {
  32048. tag: 'div',
  32049. classes: ['tox', 'tox-tinymce']
  32050. .concat(isInline ? ['tox-tinymce-inline'] : [])
  32051. .concat(isToolbarBottom ? ['tox-tinymce--toolbar-bottom'] : [])
  32052. .concat(deviceClasses),
  32053. styles: {
  32054. // This is overridden by the skin, it helps avoid FOUC
  32055. visibility: 'hidden',
  32056. // Hide the container if needed, but don't use "display: none" so that it still has a position
  32057. ...isHidden ? { opacity: '0', border: '0' } : {}
  32058. },
  32059. attributes
  32060. },
  32061. components: [
  32062. editorContainer,
  32063. // Inline mode does not have a status bar
  32064. ...isInline ? [] : [partViewWrapper, ...statusbar.toArray()],
  32065. partThrobber,
  32066. ],
  32067. behaviours: derive$1([
  32068. toggleOnReceive(() => backstages.popup.shared.providers.checkUiComponentContext('any')),
  32069. Disabling.config({
  32070. disableClass: 'tox-tinymce--disabled'
  32071. }),
  32072. Keying.config({
  32073. mode: 'cyclic',
  32074. selector: '.tox-menubar, .tox-toolbar, .tox-toolbar__primary, .tox-toolbar__overflow--open, .tox-sidebar__overflow--open, .tox-statusbar__path, .tox-statusbar__wordcount, .tox-statusbar__branding a, .tox-statusbar__resize-handle'
  32075. })
  32076. ])
  32077. }));
  32078. const mothership = takeover(outerContainer);
  32079. lazyMothership.set(mothership);
  32080. return { mothership, outerContainer };
  32081. };
  32082. const setEditorSize = (outerContainer) => {
  32083. // Set height and width if they were given, though height only applies to iframe mode
  32084. const parsedHeight = numToPx(getHeightWithFallback(editor));
  32085. const parsedWidth = numToPx(getWidthWithFallback(editor));
  32086. if (!editor.inline) {
  32087. // Update the width
  32088. if (isValidValue$1('div', 'width', parsedWidth)) {
  32089. set$7(outerContainer.element, 'width', parsedWidth);
  32090. }
  32091. // Update the height
  32092. if (isValidValue$1('div', 'height', parsedHeight)) {
  32093. set$7(outerContainer.element, 'height', parsedHeight);
  32094. }
  32095. else {
  32096. set$7(outerContainer.element, 'height', '400px');
  32097. }
  32098. }
  32099. return parsedHeight;
  32100. };
  32101. const setupShortcutsAndCommands = (outerContainer) => {
  32102. editor.addShortcut('alt+F9', 'focus menubar', () => {
  32103. OuterContainer.focusMenubar(outerContainer);
  32104. });
  32105. editor.addShortcut('alt+F10', 'focus toolbar', () => {
  32106. OuterContainer.focusToolbar(outerContainer);
  32107. });
  32108. editor.addCommand('ToggleToolbarDrawer', (_ui, options, args) => {
  32109. if (options === null || options === void 0 ? void 0 : options.skipFocus) {
  32110. logFeatureDeprecationWarning('skipFocus');
  32111. OuterContainer.toggleToolbarDrawerWithoutFocusing(outerContainer);
  32112. }
  32113. else if (args === null || args === void 0 ? void 0 : args.skip_focus) {
  32114. OuterContainer.toggleToolbarDrawerWithoutFocusing(outerContainer);
  32115. }
  32116. else {
  32117. OuterContainer.toggleToolbarDrawer(outerContainer);
  32118. }
  32119. });
  32120. editor.addQueryStateHandler('ToggleToolbarDrawer', () => OuterContainer.isToolbarDrawerToggled(outerContainer));
  32121. editor.on('blur', () => {
  32122. if (getToolbarMode(editor) === ToolbarMode$1.floating && OuterContainer.isToolbarDrawerToggled(outerContainer)) {
  32123. OuterContainer.toggleToolbarDrawerWithoutFocusing(outerContainer);
  32124. }
  32125. });
  32126. };
  32127. const renderUIWithRefs = (uiRefs) => {
  32128. const { mainUi, popupUi, uiMotherships } = uiRefs;
  32129. map$1(getToolbarGroups(editor), (toolbarGroupButtonConfig, name) => {
  32130. editor.ui.registry.addGroupToolbarButton(name, toolbarGroupButtonConfig);
  32131. });
  32132. // Apply Bridge types
  32133. const { buttons, menuItems, contextToolbars, sidebars, views } = editor.ui.registry.getAll();
  32134. const toolbarOpt = getMultipleToolbarsOption(editor);
  32135. const rawUiConfig = {
  32136. menuItems,
  32137. menus: getMenus(editor),
  32138. menubar: getMenubar(editor),
  32139. toolbar: toolbarOpt.getOrThunk(() => getToolbar(editor)),
  32140. allowToolbarGroups: toolbarMode === ToolbarMode$1.floating,
  32141. buttons,
  32142. sidebar: sidebars,
  32143. views
  32144. };
  32145. setupShortcutsAndCommands(mainUi.outerContainer);
  32146. setup$b(editor, mainUi.mothership, uiMotherships);
  32147. // This backstage needs to kept in sync with the one passed to the Header part.
  32148. header.setup(editor, backstages.popup.shared, lazyHeader);
  32149. // This backstage is probably needed for just the bespoke dropdowns
  32150. setup$6(editor, backstages.popup);
  32151. setup$5(editor, backstages.popup.shared.getSink, backstages.popup);
  32152. setup$8(editor);
  32153. setup$7(editor, lazyThrobber, backstages.popup.shared);
  32154. register$a(editor, contextToolbars, popupUi.sink, { backstage: backstages.popup });
  32155. setup$4(editor, popupUi.sink);
  32156. const elm = editor.getElement();
  32157. const height = setEditorSize(mainUi.outerContainer);
  32158. const args = { targetNode: elm, height };
  32159. // The only components that use backstages.dialog currently are the Modal dialogs.
  32160. return mode.render(editor, uiRefs, rawUiConfig, backstages.popup, args);
  32161. };
  32162. const reuseDialogUiForPopuUi = (dialogUi) => {
  32163. lazyPopupMothership.set(dialogUi.mothership);
  32164. return dialogUi;
  32165. };
  32166. const renderUI = () => {
  32167. const mainUi = renderMainUi();
  32168. const dialogUi = renderDialogUi();
  32169. // If dialogUi and popupUi are the same, LazyUiReferences should handle deduplicating then
  32170. // get calling getUiMotherships
  32171. const popupUi = isSplitUiMode(editor) ? renderPopupUi() : reuseDialogUiForPopuUi(dialogUi);
  32172. lazyUiRefs.dialogUi.set(dialogUi);
  32173. lazyUiRefs.popupUi.set(popupUi);
  32174. lazyUiRefs.mainUi.set(mainUi);
  32175. // From this point on, we shouldn't use LazyReferences any more.
  32176. const uiRefs = {
  32177. popupUi,
  32178. dialogUi,
  32179. mainUi,
  32180. uiMotherships: lazyUiRefs.getUiMotherships()
  32181. };
  32182. return renderUIWithRefs(uiRefs);
  32183. };
  32184. // We don't have uiRefs here, so we have to rely on cells that are set by renderUI unfortunately.
  32185. return {
  32186. popups: {
  32187. backstage: backstages.popup,
  32188. getMothership: () => getLazyMothership('popups', lazyPopupMothership)
  32189. },
  32190. dialogs: {
  32191. backstage: backstages.dialog,
  32192. getMothership: () => getLazyMothership('dialogs', lazyDialogMothership)
  32193. },
  32194. renderUI
  32195. };
  32196. };
  32197. const toValidValues = (values) => {
  32198. const errors = [];
  32199. const result = {};
  32200. each(values, (value, name) => {
  32201. value.fold(() => {
  32202. errors.push(name);
  32203. }, (v) => {
  32204. result[name] = v;
  32205. });
  32206. });
  32207. return errors.length > 0 ? Result.error(errors) :
  32208. Result.value(result);
  32209. };
  32210. const renderBodyPanel = (spec, dialogData, backstage, getCompByName) => {
  32211. const memForm = record(Form.sketch((parts) => ({
  32212. dom: {
  32213. tag: 'div',
  32214. classes: ['tox-form'].concat(spec.classes)
  32215. },
  32216. // All of the items passed through the form need to be put through the interpreter
  32217. // with their form part preserved.
  32218. components: map$2(spec.items, (item) => interpretInForm(parts, item, dialogData, backstage, getCompByName))
  32219. })));
  32220. return {
  32221. dom: {
  32222. tag: 'div',
  32223. classes: ['tox-dialog__body']
  32224. },
  32225. components: [
  32226. {
  32227. dom: {
  32228. tag: 'div',
  32229. classes: ['tox-dialog__body-content']
  32230. },
  32231. components: [
  32232. memForm.asSpec()
  32233. ]
  32234. }
  32235. ],
  32236. behaviours: derive$1([
  32237. Keying.config({
  32238. mode: 'acyclic',
  32239. useTabstopAt: not(isPseudoStop)
  32240. }),
  32241. ComposingConfigs.memento(memForm),
  32242. memento(memForm, {
  32243. postprocess: (formValue) => toValidValues(formValue).fold((err) => {
  32244. // eslint-disable-next-line no-console
  32245. console.error(err);
  32246. return {};
  32247. }, identity)
  32248. }),
  32249. config('dialog-body-panel', [
  32250. // TINY-10101: This is to cater for the case where clicks are made into the dialog instead using keyboard navigation, as FocusShifted would not be triggered in that case.
  32251. run$1(focusin(), (comp, se) => {
  32252. comp.getSystem().broadcastOn([dialogFocusShiftedChannel], {
  32253. newFocus: Optional.some(se.event.target)
  32254. });
  32255. }),
  32256. ])
  32257. ])
  32258. };
  32259. };
  32260. const measureHeights = (allTabs, tabview, tabviewComp) => map$2(allTabs, (_tab, i) => {
  32261. Replacing.set(tabviewComp, allTabs[i].view());
  32262. const rect = tabview.dom.getBoundingClientRect();
  32263. Replacing.set(tabviewComp, []);
  32264. return rect.height;
  32265. });
  32266. const getMaxHeight = (heights) => head(sort(heights, (a, b) => {
  32267. if (a > b) {
  32268. return -1;
  32269. }
  32270. else if (a < b) {
  32271. return +1;
  32272. }
  32273. else {
  32274. return 0;
  32275. }
  32276. }));
  32277. const getMaxTabviewHeight = (dialog, tabview, tablist) => {
  32278. const documentElement$1 = documentElement(dialog).dom;
  32279. const rootElm = ancestor$1(dialog, '.tox-dialog-wrap').getOr(dialog);
  32280. const isFixed = get$e(rootElm, 'position') === 'fixed';
  32281. // Get the document or window/viewport height
  32282. let maxHeight;
  32283. if (isFixed) {
  32284. maxHeight = Math.max(documentElement$1.clientHeight, window.innerHeight);
  32285. }
  32286. else {
  32287. maxHeight = Math.max(documentElement$1.offsetHeight, documentElement$1.scrollHeight);
  32288. }
  32289. // Determine the current height taken up by the tabview panel
  32290. const tabviewHeight = get$d(tabview);
  32291. const isTabListBeside = tabview.dom.offsetLeft >= tablist.dom.offsetLeft + get$c(tablist);
  32292. const currentTabHeight = isTabListBeside ? Math.max(get$d(tablist), tabviewHeight) : tabviewHeight;
  32293. // Get the dialog height, making sure to account for any margins on the dialog
  32294. const dialogTopMargin = parseInt(get$e(dialog, 'margin-top'), 10) || 0;
  32295. const dialogBottomMargin = parseInt(get$e(dialog, 'margin-bottom'), 10) || 0;
  32296. const dialogHeight = get$d(dialog) + dialogTopMargin + dialogBottomMargin;
  32297. const chromeHeight = dialogHeight - currentTabHeight;
  32298. return maxHeight - chromeHeight;
  32299. };
  32300. const showTab = (allTabs, comp) => {
  32301. head(allTabs).each((tab) => TabSection.showTab(comp, tab.value));
  32302. };
  32303. const setTabviewHeight = (tabview, height) => {
  32304. // Set both height and flex-basis as some browsers don't support flex-basis.
  32305. set$7(tabview, 'height', height + 'px');
  32306. set$7(tabview, 'flex-basis', height + 'px');
  32307. };
  32308. const updateTabviewHeight = (dialogBody, tabview, maxTabHeight) => {
  32309. ancestor$1(dialogBody, '[role="dialog"]').each((dialog) => {
  32310. descendant(dialog, '[role="tablist"]').each((tablist) => {
  32311. maxTabHeight.get().map((height) => {
  32312. // Set the tab view height to 0, so we can calculate the max tabview height, without worrying about overflows
  32313. set$7(tabview, 'height', '0');
  32314. set$7(tabview, 'flex-basis', '0');
  32315. return Math.min(height, getMaxTabviewHeight(dialog, tabview, tablist));
  32316. }).each((height) => {
  32317. setTabviewHeight(tabview, height);
  32318. });
  32319. });
  32320. });
  32321. };
  32322. const getTabview = (dialog) => descendant(dialog, '[role="tabpanel"]');
  32323. const smartMode = (allTabs) => {
  32324. const maxTabHeight = value$2();
  32325. const extraEvents = [
  32326. runOnAttached((comp) => {
  32327. const dialog = comp.element;
  32328. getTabview(dialog).each((tabview) => {
  32329. set$7(tabview, 'visibility', 'hidden');
  32330. // Determine the maximum heights of each tab
  32331. comp.getSystem().getByDom(tabview).toOptional().each((tabviewComp) => {
  32332. const heights = measureHeights(allTabs, tabview, tabviewComp);
  32333. // Calculate the maximum tab height and store it
  32334. const maxTabHeightOpt = getMaxHeight(heights);
  32335. maxTabHeightOpt.fold(maxTabHeight.clear, maxTabHeight.set);
  32336. });
  32337. // Set an initial height, based on the current size
  32338. updateTabviewHeight(dialog, tabview, maxTabHeight);
  32339. // Show the tabs
  32340. remove$6(tabview, 'visibility');
  32341. showTab(allTabs, comp);
  32342. // Use a delay here and recalculate the height, as we need all the components attached
  32343. // to be able to properly calculate the max height
  32344. requestAnimationFrame(() => {
  32345. updateTabviewHeight(dialog, tabview, maxTabHeight);
  32346. });
  32347. });
  32348. }),
  32349. run$1(windowResize(), (comp) => {
  32350. const dialog = comp.element;
  32351. getTabview(dialog).each((tabview) => {
  32352. updateTabviewHeight(dialog, tabview, maxTabHeight);
  32353. });
  32354. }),
  32355. run$1(formResizeEvent, (comp, _se) => {
  32356. const dialog = comp.element;
  32357. getTabview(dialog).each((tabview) => {
  32358. const oldFocus = active$1(getRootNode(tabview));
  32359. set$7(tabview, 'visibility', 'hidden');
  32360. const oldHeight = getRaw(tabview, 'height').map((h) => parseInt(h, 10));
  32361. remove$6(tabview, 'height');
  32362. remove$6(tabview, 'flex-basis');
  32363. const newHeight = tabview.dom.getBoundingClientRect().height;
  32364. const hasGrown = oldHeight.forall((h) => newHeight > h);
  32365. if (hasGrown) {
  32366. maxTabHeight.set(newHeight);
  32367. updateTabviewHeight(dialog, tabview, maxTabHeight);
  32368. }
  32369. else {
  32370. oldHeight.each((h) => {
  32371. setTabviewHeight(tabview, h);
  32372. });
  32373. }
  32374. remove$6(tabview, 'visibility');
  32375. oldFocus.each(focus$4);
  32376. });
  32377. })
  32378. ];
  32379. const selectFirst = false;
  32380. return {
  32381. extraEvents,
  32382. selectFirst
  32383. };
  32384. };
  32385. const SendDataToSectionChannel = 'send-data-to-section';
  32386. const SendDataToViewChannel = 'send-data-to-view';
  32387. const renderTabPanel = (spec, dialogData, backstage, getCompByName) => {
  32388. const storedValue = Cell({});
  32389. const updateDataWithForm = (form) => {
  32390. const formData = Representing.getValue(form);
  32391. const validData = toValidValues(formData).getOr({});
  32392. const currentData = storedValue.get();
  32393. const newData = deepMerge(currentData, validData);
  32394. storedValue.set(newData);
  32395. };
  32396. const setDataOnForm = (form) => {
  32397. const tabData = storedValue.get();
  32398. Representing.setValue(form, tabData);
  32399. };
  32400. const oldTab = Cell(null);
  32401. const allTabs = map$2(spec.tabs, (tab) => {
  32402. return {
  32403. value: tab.name,
  32404. dom: {
  32405. tag: 'div',
  32406. classes: ['tox-dialog__body-nav-item']
  32407. },
  32408. components: [
  32409. text$2(backstage.shared.providers.translate(tab.title))
  32410. ],
  32411. view: () => {
  32412. return [
  32413. // Dupe with SilverDialog
  32414. Form.sketch((parts) => ({
  32415. dom: {
  32416. tag: 'div',
  32417. classes: ['tox-form']
  32418. },
  32419. components: map$2(tab.items, (item) => interpretInForm(parts, item, dialogData, backstage, getCompByName)),
  32420. formBehaviours: derive$1([
  32421. Keying.config({
  32422. mode: 'acyclic',
  32423. useTabstopAt: not(isPseudoStop)
  32424. }),
  32425. config('TabView.form.events', [
  32426. runOnAttached(setDataOnForm),
  32427. runOnDetached(updateDataWithForm)
  32428. ]),
  32429. Receiving.config({
  32430. channels: wrapAll([
  32431. {
  32432. key: SendDataToSectionChannel,
  32433. value: {
  32434. onReceive: updateDataWithForm
  32435. }
  32436. },
  32437. {
  32438. key: SendDataToViewChannel,
  32439. value: {
  32440. onReceive: setDataOnForm
  32441. }
  32442. }
  32443. ])
  32444. })
  32445. ])
  32446. }))
  32447. ];
  32448. }
  32449. };
  32450. });
  32451. // Assign fixed height or variable height to the tabs
  32452. const tabMode = smartMode(allTabs);
  32453. return TabSection.sketch({
  32454. dom: {
  32455. tag: 'div',
  32456. classes: ['tox-dialog__body']
  32457. },
  32458. onChangeTab: (section, button, _viewItems) => {
  32459. const name = Representing.getValue(button);
  32460. emitWith(section, formTabChangeEvent, {
  32461. name,
  32462. oldName: oldTab.get()
  32463. });
  32464. oldTab.set(name);
  32465. },
  32466. tabs: allTabs,
  32467. components: [
  32468. TabSection.parts.tabbar({
  32469. dom: {
  32470. tag: 'div',
  32471. classes: ['tox-dialog__body-nav']
  32472. },
  32473. components: [
  32474. Tabbar.parts.tabs({})
  32475. ],
  32476. markers: {
  32477. tabClass: 'tox-tab',
  32478. selectedClass: 'tox-dialog__body-nav-item--active'
  32479. },
  32480. tabbarBehaviours: derive$1([
  32481. Tabstopping.config({})
  32482. ])
  32483. }),
  32484. TabSection.parts.tabview({
  32485. dom: {
  32486. tag: 'div',
  32487. classes: ['tox-dialog__body-content']
  32488. }
  32489. })
  32490. ],
  32491. selectFirst: tabMode.selectFirst,
  32492. tabSectionBehaviours: derive$1([
  32493. config('tabpanel', tabMode.extraEvents),
  32494. Keying.config({
  32495. mode: 'acyclic'
  32496. }),
  32497. // INVESTIGATE: Is this necessary? Probably used by getCompByName.
  32498. Composing.config({
  32499. // TODO: Think about this
  32500. find: (comp) => head(TabSection.getViewItems(comp))
  32501. }),
  32502. withComp(Optional.none(), (tsection) => {
  32503. // NOTE: Assumes synchronous updating of store.
  32504. tsection.getSystem().broadcastOn([SendDataToSectionChannel], {});
  32505. return storedValue.get();
  32506. }, (tsection, value) => {
  32507. storedValue.set(value);
  32508. tsection.getSystem().broadcastOn([SendDataToViewChannel], {});
  32509. })
  32510. ])
  32511. });
  32512. };
  32513. // ariaAttrs is being passed through to silver inline dialog
  32514. // from the WindowManager as a property of 'params'
  32515. const renderBody = (spec, dialogId, contentId, backstage, ariaAttrs, getCompByName) => {
  32516. const renderComponents = (incoming) => {
  32517. const body = incoming.body;
  32518. switch (body.type) {
  32519. case 'tabpanel': {
  32520. return [
  32521. renderTabPanel(body, incoming.initialData, backstage, getCompByName)
  32522. ];
  32523. }
  32524. default: {
  32525. return [
  32526. renderBodyPanel(body, incoming.initialData, backstage, getCompByName)
  32527. ];
  32528. }
  32529. }
  32530. };
  32531. const updateState = (_comp, incoming) => Optional.some({
  32532. isTabPanel: () => incoming.body.type === 'tabpanel'
  32533. });
  32534. const ariaAttributes = {
  32535. 'aria-live': 'polite'
  32536. };
  32537. return {
  32538. dom: {
  32539. tag: 'div',
  32540. classes: ['tox-dialog__content-js'],
  32541. attributes: {
  32542. ...contentId.map((x) => ({ id: x })).getOr({}),
  32543. ...ariaAttrs ? ariaAttributes : {}
  32544. }
  32545. },
  32546. components: [],
  32547. behaviours: derive$1([
  32548. ComposingConfigs.childAt(0),
  32549. Reflecting.config({
  32550. channel: `${bodyChannel}-${dialogId}`,
  32551. updateState,
  32552. renderComponents,
  32553. initialData: spec
  32554. })
  32555. ])
  32556. };
  32557. };
  32558. const renderInlineBody = (spec, dialogId, contentId, backstage, ariaAttrs, getCompByName) => renderBody(spec, dialogId, Optional.some(contentId), backstage, ariaAttrs, getCompByName);
  32559. const renderModalBody = (spec, dialogId, backstage, getCompByName) => {
  32560. const bodySpec = renderBody(spec, dialogId, Optional.none(), backstage, false, getCompByName);
  32561. return ModalDialog.parts.body(bodySpec);
  32562. };
  32563. const renderIframeBody = (spec) => {
  32564. const bodySpec = {
  32565. dom: {
  32566. tag: 'div',
  32567. classes: ['tox-dialog__content-js']
  32568. },
  32569. components: [
  32570. {
  32571. dom: {
  32572. tag: 'div',
  32573. classes: ['tox-dialog__body-iframe']
  32574. },
  32575. components: [
  32576. craft(Optional.none(), {
  32577. dom: {
  32578. tag: 'iframe',
  32579. attributes: {
  32580. src: spec.url
  32581. }
  32582. },
  32583. behaviours: derive$1([
  32584. Tabstopping.config({}),
  32585. Focusing.config({})
  32586. ])
  32587. })
  32588. ]
  32589. }
  32590. ],
  32591. behaviours: derive$1([
  32592. Keying.config({
  32593. mode: 'acyclic',
  32594. useTabstopAt: not(isPseudoStop)
  32595. })
  32596. ])
  32597. };
  32598. return ModalDialog.parts.body(bodySpec);
  32599. };
  32600. const isTouch = global$7.deviceType.isTouch();
  32601. const hiddenHeader = (title, close) => ({
  32602. dom: {
  32603. tag: 'div',
  32604. styles: { display: 'none' },
  32605. classes: ['tox-dialog__header']
  32606. },
  32607. components: [
  32608. title,
  32609. close
  32610. ]
  32611. });
  32612. const pClose = (onClose, providersBackstage) => ModalDialog.parts.close(
  32613. // Need to find a way to make it clear in the docs whether parts can be sketches
  32614. Button.sketch({
  32615. dom: {
  32616. tag: 'button',
  32617. classes: ['tox-button', 'tox-button--icon', 'tox-button--naked'],
  32618. attributes: {
  32619. 'type': 'button',
  32620. 'aria-label': providersBackstage.translate('Close')
  32621. }
  32622. },
  32623. action: onClose,
  32624. buttonBehaviours: derive$1([
  32625. Tabstopping.config({})
  32626. ])
  32627. }));
  32628. const pUntitled = () => ModalDialog.parts.title({
  32629. dom: {
  32630. tag: 'div',
  32631. classes: ['tox-dialog__title'],
  32632. innerHtml: '',
  32633. styles: {
  32634. display: 'none'
  32635. }
  32636. }
  32637. });
  32638. const pBodyMessage = (message, providersBackstage) => ModalDialog.parts.body({
  32639. dom: {
  32640. tag: 'div',
  32641. classes: ['tox-dialog__body']
  32642. },
  32643. components: [
  32644. {
  32645. dom: {
  32646. tag: 'div',
  32647. classes: ['tox-dialog__body-content']
  32648. },
  32649. components: [
  32650. {
  32651. dom: fromHtml(`<p>${sanitizeHtmlString(providersBackstage.translate(message))}</p>`)
  32652. }
  32653. ]
  32654. }
  32655. ]
  32656. });
  32657. const pFooter = (buttons) => ModalDialog.parts.footer({
  32658. dom: {
  32659. tag: 'div',
  32660. classes: ['tox-dialog__footer']
  32661. },
  32662. components: buttons
  32663. });
  32664. const pFooterGroup = (startButtons, endButtons) => [
  32665. Container.sketch({
  32666. dom: {
  32667. tag: 'div',
  32668. classes: ['tox-dialog__footer-start']
  32669. },
  32670. components: startButtons
  32671. }),
  32672. Container.sketch({
  32673. dom: {
  32674. tag: 'div',
  32675. classes: ['tox-dialog__footer-end']
  32676. },
  32677. components: endButtons
  32678. })
  32679. ];
  32680. const renderDialog$1 = (spec) => {
  32681. const dialogClass = 'tox-dialog';
  32682. const blockerClass = dialogClass + '-wrap';
  32683. const blockerBackdropClass = blockerClass + '__backdrop';
  32684. const scrollLockClass = dialogClass + '__disable-scroll';
  32685. return ModalDialog.sketch({
  32686. lazySink: spec.lazySink,
  32687. onEscape: (comp) => {
  32688. spec.onEscape(comp);
  32689. // TODO: Make a strong type for Handled KeyEvent
  32690. return Optional.some(true);
  32691. },
  32692. useTabstopAt: (elem) => !isPseudoStop(elem),
  32693. firstTabstop: spec.firstTabstop,
  32694. dom: {
  32695. tag: 'div',
  32696. classes: [dialogClass].concat(spec.extraClasses),
  32697. styles: {
  32698. position: 'relative',
  32699. ...spec.extraStyles
  32700. }
  32701. },
  32702. components: [
  32703. spec.header,
  32704. spec.body,
  32705. ...spec.footer.toArray()
  32706. ],
  32707. parts: {
  32708. blocker: {
  32709. dom: fromHtml(`<div class="${blockerClass}"></div>`),
  32710. components: [
  32711. {
  32712. dom: {
  32713. tag: 'div',
  32714. classes: (isTouch ? [blockerBackdropClass, blockerBackdropClass + '--opaque'] : [blockerBackdropClass])
  32715. }
  32716. }
  32717. ]
  32718. }
  32719. },
  32720. dragBlockClass: blockerClass,
  32721. modalBehaviours: derive$1([
  32722. Focusing.config({}),
  32723. config('dialog-events', spec.dialogEvents.concat([
  32724. // Note: `runOnSource` here will only listen to the event at the outer component level.
  32725. // Using just `run` instead will cause an infinite loop as `focusIn` would fire a `focusin` which would then get responded to and so forth.
  32726. runOnSource(focusin(), (comp, _se) => {
  32727. Blocking.isBlocked(comp) ? noop() : Keying.focusIn(comp);
  32728. }),
  32729. run$1(focusShifted(), (comp, se) => {
  32730. comp.getSystem().broadcastOn([dialogFocusShiftedChannel], {
  32731. newFocus: se.event.newFocus
  32732. });
  32733. })
  32734. ])),
  32735. config('scroll-lock', [
  32736. runOnAttached(() => {
  32737. add$2(body(), scrollLockClass);
  32738. }),
  32739. runOnDetached(() => {
  32740. remove$3(body(), scrollLockClass);
  32741. })
  32742. ]),
  32743. ...spec.extraBehaviours
  32744. ]),
  32745. eventOrder: {
  32746. [execute$5()]: ['dialog-events'],
  32747. [attachedToDom()]: ['scroll-lock', 'dialog-events', 'alloy.base.behaviour'],
  32748. [detachedFromDom()]: ['alloy.base.behaviour', 'dialog-events', 'scroll-lock'],
  32749. ...spec.eventOrder
  32750. }
  32751. });
  32752. };
  32753. const renderClose = (providersBackstage) => Button.sketch({
  32754. dom: {
  32755. tag: 'button',
  32756. classes: ['tox-button', 'tox-button--icon', 'tox-button--naked'],
  32757. attributes: {
  32758. 'type': 'button',
  32759. 'aria-label': providersBackstage.translate('Close'),
  32760. 'data-mce-name': 'close'
  32761. }
  32762. },
  32763. buttonBehaviours: derive$1([
  32764. Tabstopping.config({}),
  32765. Tooltipping.config(providersBackstage.tooltips.getConfig({
  32766. tooltipText: providersBackstage.translate('Close')
  32767. }))
  32768. ]),
  32769. components: [
  32770. render$4('close', { tag: 'span', classes: ['tox-icon'] }, providersBackstage.icons)
  32771. ],
  32772. action: (comp) => {
  32773. emit(comp, formCancelEvent);
  32774. },
  32775. });
  32776. const renderTitle = (spec, dialogId, titleId, providersBackstage) => {
  32777. const renderComponents = (data) => [text$2(providersBackstage.translate(data.title))];
  32778. return {
  32779. dom: {
  32780. tag: 'h1',
  32781. classes: ['tox-dialog__title'],
  32782. attributes: {
  32783. ...titleId.map((x) => ({ id: x })).getOr({})
  32784. }
  32785. },
  32786. components: [],
  32787. behaviours: derive$1([
  32788. Reflecting.config({
  32789. channel: `${titleChannel}-${dialogId}`,
  32790. initialData: spec,
  32791. renderComponents
  32792. })
  32793. ])
  32794. };
  32795. };
  32796. const renderDragHandle = () => ({
  32797. dom: fromHtml('<div class="tox-dialog__draghandle"></div>')
  32798. });
  32799. const renderInlineHeader = (spec, dialogId, titleId, providersBackstage) => Container.sketch({
  32800. dom: fromHtml('<div class="tox-dialog__header"></div>'),
  32801. components: [
  32802. renderTitle(spec, dialogId, Optional.some(titleId), providersBackstage),
  32803. renderDragHandle(),
  32804. renderClose(providersBackstage)
  32805. ],
  32806. containerBehaviours: derive$1([
  32807. Dragging.config({
  32808. mode: 'mouse',
  32809. blockerClass: 'blocker',
  32810. getTarget: (handle) => {
  32811. return closest$3(handle, '[role="dialog"]').getOrDie();
  32812. },
  32813. snaps: {
  32814. getSnapPoints: () => [],
  32815. leftAttr: 'data-drag-left',
  32816. topAttr: 'data-drag-top'
  32817. },
  32818. onDrag: (comp, target) => {
  32819. comp.getSystem().broadcastOn([repositionPopups()], { target });
  32820. }
  32821. })
  32822. ])
  32823. });
  32824. const renderModalHeader = (spec, dialogId, providersBackstage) => {
  32825. const pTitle = ModalDialog.parts.title(renderTitle(spec, dialogId, Optional.none(), providersBackstage));
  32826. const pHandle = ModalDialog.parts.draghandle(renderDragHandle());
  32827. const pClose = ModalDialog.parts.close(renderClose(providersBackstage));
  32828. const components = [pTitle].concat(spec.draggable ? [pHandle] : []).concat([pClose]);
  32829. return Container.sketch({
  32830. dom: fromHtml('<div class="tox-dialog__header"></div>'),
  32831. components
  32832. });
  32833. };
  32834. const getHeader = (title, dialogId, backstage) => renderModalHeader({
  32835. title: backstage.shared.providers.translate(title),
  32836. draggable: backstage.dialog.isDraggableModal()
  32837. }, dialogId, backstage.shared.providers);
  32838. const getBusySpec = (message, bs, providers, headerHeight) => ({
  32839. dom: {
  32840. tag: 'div',
  32841. classes: ['tox-dialog__busy-spinner'],
  32842. attributes: {
  32843. 'aria-label': providers.translate(message)
  32844. },
  32845. styles: {
  32846. left: '0px',
  32847. right: '0px',
  32848. bottom: '0px',
  32849. top: `${headerHeight.getOr(0)}px`,
  32850. position: 'absolute'
  32851. }
  32852. },
  32853. behaviours: bs,
  32854. components: [{
  32855. dom: fromHtml('<div class="tox-spinner"><div></div><div></div><div></div></div>')
  32856. }]
  32857. });
  32858. const getEventExtras = (lazyDialog, providers, extra) => ({
  32859. onClose: () => extra.closeWindow(),
  32860. onBlock: (blockEvent) => {
  32861. const headerHeight = descendant(lazyDialog().element, '.tox-dialog__header').map((header) => get$d(header));
  32862. ModalDialog.setBusy(lazyDialog(), (_comp, bs) => getBusySpec(blockEvent.message, bs, providers, headerHeight));
  32863. },
  32864. onUnblock: () => {
  32865. ModalDialog.setIdle(lazyDialog());
  32866. }
  32867. });
  32868. const fullscreenClass = 'tox-dialog--fullscreen';
  32869. const largeDialogClass = 'tox-dialog--width-lg';
  32870. const mediumDialogClass = 'tox-dialog--width-md';
  32871. const getDialogSizeClass = (size) => {
  32872. switch (size) {
  32873. case 'large':
  32874. return Optional.some(largeDialogClass);
  32875. case 'medium':
  32876. return Optional.some(mediumDialogClass);
  32877. default:
  32878. return Optional.none();
  32879. }
  32880. };
  32881. const updateDialogSizeClass = (size, component) => {
  32882. const dialogBody = SugarElement.fromDom(component.element.dom);
  32883. if (!has(dialogBody, fullscreenClass)) {
  32884. remove$2(dialogBody, [largeDialogClass, mediumDialogClass]);
  32885. getDialogSizeClass(size).each((dialogSizeClass) => add$2(dialogBody, dialogSizeClass));
  32886. }
  32887. };
  32888. const toggleFullscreen = (comp, currentSize) => {
  32889. const dialogBody = SugarElement.fromDom(comp.element.dom);
  32890. const classes = get$7(dialogBody);
  32891. const currentSizeClass = find$5(classes, (c) => c === largeDialogClass || c === mediumDialogClass).or(getDialogSizeClass(currentSize));
  32892. toggle$3(dialogBody, [fullscreenClass, ...currentSizeClass.toArray()]);
  32893. };
  32894. const renderModalDialog = (spec, dialogEvents, backstage) => build$1(renderDialog$1({
  32895. ...spec,
  32896. firstTabstop: 1,
  32897. lazySink: backstage.shared.getSink,
  32898. extraBehaviours: [
  32899. memory({}),
  32900. ...spec.extraBehaviours
  32901. ],
  32902. onEscape: (comp) => {
  32903. emit(comp, formCancelEvent);
  32904. },
  32905. dialogEvents,
  32906. eventOrder: {
  32907. [receive()]: [Reflecting.name(), Receiving.name()],
  32908. [attachedToDom()]: ['scroll-lock', Reflecting.name(), 'messages', 'dialog-events', 'alloy.base.behaviour'],
  32909. [detachedFromDom()]: ['alloy.base.behaviour', 'dialog-events', 'messages', Reflecting.name(), 'scroll-lock']
  32910. }
  32911. }));
  32912. const mapMenuButtons = (buttons, menuItemStates = {}) => {
  32913. const mapItems = (button) => {
  32914. const items = map$2(button.items, (item) => {
  32915. const cell = get$h(menuItemStates, item.name).getOr(Cell(false));
  32916. return {
  32917. ...item,
  32918. storage: cell
  32919. };
  32920. });
  32921. return {
  32922. ...button,
  32923. items
  32924. };
  32925. };
  32926. return map$2(buttons, (button) => {
  32927. return button.type === 'menu' ? mapItems(button) : button;
  32928. });
  32929. };
  32930. const extractCellsToObject = (buttons) => foldl(buttons, (acc, button) => {
  32931. if (button.type === 'menu') {
  32932. const menuButton = button;
  32933. return foldl(menuButton.items, (innerAcc, item) => {
  32934. innerAcc[item.name] = item.storage;
  32935. return innerAcc;
  32936. }, acc);
  32937. }
  32938. return acc;
  32939. }, {});
  32940. const initCommonEvents = (fireApiEvent, extras) => [
  32941. // When focus moves onto a tab-placeholder, skip to the next thing in the tab sequence
  32942. runWithTarget(focusin(), onFocus),
  32943. // TODO: Test if disabled first.
  32944. fireApiEvent(formCloseEvent, (_api, spec, _event, self) => {
  32945. // TINY-9148: Safari scrolls down to the sink if the dialog is selected before removing,
  32946. // so we should blur the currently active element beforehand.
  32947. if (hasFocus(self.element)) {
  32948. active$1(getRootNode(self.element)).each(blur$1);
  32949. }
  32950. extras.onClose();
  32951. spec.onClose();
  32952. }),
  32953. // TODO: Test if disabled first.
  32954. fireApiEvent(formCancelEvent, (api, spec, _event, self) => {
  32955. spec.onCancel(api);
  32956. emit(self, formCloseEvent);
  32957. }),
  32958. run$1(formUnblockEvent, (_c, _se) => extras.onUnblock()),
  32959. run$1(formBlockEvent, (_c, se) => extras.onBlock(se.event))
  32960. ];
  32961. const initUrlDialog = (getInstanceApi, extras) => {
  32962. const fireApiEvent = (eventName, f) => run$1(eventName, (c, se) => {
  32963. withSpec(c, (spec, _c) => {
  32964. f(getInstanceApi(), spec, se.event, c);
  32965. });
  32966. });
  32967. const withSpec = (c, f) => {
  32968. Reflecting.getState(c).get().each((currentDialog) => {
  32969. f(currentDialog, c);
  32970. });
  32971. };
  32972. return [
  32973. ...initCommonEvents(fireApiEvent, extras),
  32974. fireApiEvent(formActionEvent, (api, spec, event) => {
  32975. spec.onAction(api, { name: event.name });
  32976. })
  32977. ];
  32978. };
  32979. const initDialog = (getInstanceApi, extras, getSink) => {
  32980. const fireApiEvent = (eventName, f) => run$1(eventName, (c, se) => {
  32981. withSpec(c, (spec, _c) => {
  32982. f(getInstanceApi(), spec, se.event, c);
  32983. });
  32984. });
  32985. const withSpec = (c, f) => {
  32986. Reflecting.getState(c).get().each((currentDialogInit) => {
  32987. f(currentDialogInit.internalDialog, c);
  32988. });
  32989. };
  32990. return [
  32991. ...initCommonEvents(fireApiEvent, extras),
  32992. fireApiEvent(formSubmitEvent, (api, spec) => spec.onSubmit(api)),
  32993. fireApiEvent(formChangeEvent, (api, spec, event) => {
  32994. spec.onChange(api, { name: event.name });
  32995. }),
  32996. fireApiEvent(formActionEvent, (api, spec, event, component) => {
  32997. // TODO: add a test for focusIn (TINY-10125)
  32998. const focusIn = () => component.getSystem().isConnected() ? Keying.focusIn(component) : undefined;
  32999. const isDisabled = (focused) => has$1(focused, 'disabled') || getOpt(focused, 'aria-disabled').exists((val) => val === 'true');
  33000. const rootNode = getRootNode(component.element);
  33001. const current = active$1(rootNode);
  33002. spec.onAction(api, { name: event.name, value: event.value });
  33003. active$1(rootNode).fold(focusIn, (focused) => {
  33004. // We need to check if the focused element is disabled because apparently firefox likes to leave focus on disabled elements.
  33005. if (isDisabled(focused)) {
  33006. focusIn();
  33007. // And we need the below check for IE, which likes to leave focus on the parent of disabled elements
  33008. }
  33009. else if (current.exists((cur) => contains(focused, cur) && isDisabled(cur))) {
  33010. focusIn();
  33011. // Lastly if something outside the sink has focus then return the focus back to the dialog
  33012. }
  33013. else {
  33014. getSink().toOptional()
  33015. .filter((sink) => !contains(sink.element, focused))
  33016. .each(focusIn);
  33017. }
  33018. });
  33019. }),
  33020. fireApiEvent(formTabChangeEvent, (api, spec, event) => {
  33021. spec.onTabChange(api, { newTabName: event.name, oldTabName: event.oldName });
  33022. }),
  33023. // When the dialog is being closed, store the current state of the form
  33024. runOnDetached((component) => {
  33025. const api = getInstanceApi();
  33026. Representing.setValue(component, api.getData());
  33027. })
  33028. ];
  33029. };
  33030. const makeButton = (button, backstage) => renderFooterButton(button, button.type, backstage);
  33031. const lookup = (compInSystem, footerButtons, buttonName) => find$5(footerButtons, (button) => button.name === buttonName)
  33032. .bind((memButton) => memButton.memento.getOpt(compInSystem));
  33033. const renderComponents = (_data, state) => {
  33034. // default group is 'end'
  33035. const footerButtons = state.map((s) => s.footerButtons).getOr([]);
  33036. const buttonGroups = partition$3(footerButtons, (button) => button.align === 'start');
  33037. const makeGroup = (edge, buttons) => Container.sketch({
  33038. dom: {
  33039. tag: 'div',
  33040. classes: [`tox-dialog__footer-${edge}`]
  33041. },
  33042. components: map$2(buttons, (button) => button.memento.asSpec())
  33043. });
  33044. const startButtons = makeGroup('start', buttonGroups.pass);
  33045. const endButtons = makeGroup('end', buttonGroups.fail);
  33046. return [startButtons, endButtons];
  33047. };
  33048. const renderFooter = (initSpec, dialogId, backstage) => {
  33049. const updateState = (comp, data) => {
  33050. const footerButtons = map$2(data.buttons, (button) => {
  33051. const memButton = record(makeButton(button, backstage));
  33052. return {
  33053. name: button.name,
  33054. align: button.align,
  33055. memento: memButton
  33056. };
  33057. });
  33058. const lookupByName = (buttonName) => lookup(comp, footerButtons, buttonName);
  33059. return Optional.some({
  33060. lookupByName,
  33061. footerButtons
  33062. });
  33063. };
  33064. return {
  33065. dom: fromHtml('<div class="tox-dialog__footer"></div>'),
  33066. components: [],
  33067. behaviours: derive$1([
  33068. Reflecting.config({
  33069. channel: `${footerChannel}-${dialogId}`,
  33070. initialData: initSpec,
  33071. updateState,
  33072. renderComponents
  33073. })
  33074. ])
  33075. };
  33076. };
  33077. const renderInlineFooter = (initSpec, dialogId, backstage) => renderFooter(initSpec, dialogId, backstage);
  33078. const renderModalFooter = (initSpec, dialogId, backstage) => ModalDialog.parts.footer(renderFooter(initSpec, dialogId, backstage));
  33079. const getCompByName = (access, name) => {
  33080. // TODO: Add API to alloy to find the inner most component of a Composing chain.
  33081. const root = access.getRoot();
  33082. // This is just to avoid throwing errors if the dialog closes before this. We should take it out
  33083. // while developing (probably), and put it back in for the real thing.
  33084. if (root.getSystem().isConnected()) {
  33085. const form = Composing.getCurrent(access.getFormWrapper()).getOr(access.getFormWrapper());
  33086. return Form.getField(form, name).orThunk(() => {
  33087. const footer = access.getFooter();
  33088. const footerState = footer.bind((f) => Reflecting.getState(f).get());
  33089. return footerState.bind((f) => f.lookupByName(name));
  33090. });
  33091. }
  33092. else {
  33093. return Optional.none();
  33094. }
  33095. };
  33096. const validateData$1 = (access, data) => {
  33097. const root = access.getRoot();
  33098. return Reflecting.getState(root).get().map((dialogState) => getOrDie(asRaw('data', dialogState.dataValidator, data))).getOr(data);
  33099. };
  33100. const getDialogApi = (access, doRedial, menuItemStates) => {
  33101. const withRoot = (f) => {
  33102. const root = access.getRoot();
  33103. if (root.getSystem().isConnected()) {
  33104. f(root);
  33105. }
  33106. };
  33107. const getData = () => {
  33108. const root = access.getRoot();
  33109. const valueComp = root.getSystem().isConnected() ? access.getFormWrapper() : root;
  33110. const representedValues = Representing.getValue(valueComp);
  33111. const menuItemCurrentState = map$1(menuItemStates, (cell) => cell.get());
  33112. return {
  33113. ...representedValues,
  33114. ...menuItemCurrentState
  33115. };
  33116. };
  33117. const setData = (newData) => {
  33118. // Currently, the decision is to ignore setData calls that fire after the dialog is closed
  33119. withRoot((_) => {
  33120. const prevData = instanceApi.getData();
  33121. const mergedData = deepMerge(prevData, newData);
  33122. const newInternalData = validateData$1(access, mergedData);
  33123. const form = access.getFormWrapper();
  33124. Representing.setValue(form, newInternalData);
  33125. each(menuItemStates, (v, k) => {
  33126. if (has$2(mergedData, k)) {
  33127. v.set(mergedData[k]);
  33128. }
  33129. });
  33130. });
  33131. };
  33132. const setEnabled = (name, state) => {
  33133. getCompByName(access, name).each(state ? Disabling.enable : Disabling.disable);
  33134. };
  33135. const focus = (name) => {
  33136. getCompByName(access, name).each(Focusing.focus);
  33137. };
  33138. const block = (message) => {
  33139. if (!isString(message)) {
  33140. throw new Error('The dialogInstanceAPI.block function should be passed a blocking message of type string as an argument');
  33141. }
  33142. withRoot((root) => {
  33143. emitWith(root, formBlockEvent, { message });
  33144. });
  33145. };
  33146. const unblock = () => {
  33147. withRoot((root) => {
  33148. emit(root, formUnblockEvent);
  33149. });
  33150. };
  33151. const showTab = (name) => {
  33152. withRoot((_) => {
  33153. const body = access.getBody();
  33154. const bodyState = Reflecting.getState(body);
  33155. if (bodyState.get().exists((b) => b.isTabPanel())) {
  33156. Composing.getCurrent(body).each((tabSection) => {
  33157. TabSection.showTab(tabSection, name);
  33158. });
  33159. }
  33160. });
  33161. };
  33162. const redial = (d) => {
  33163. withRoot((root) => {
  33164. const id = access.getId();
  33165. const dialogInit = doRedial(d);
  33166. const storedMenuButtons = mapMenuButtons(dialogInit.internalDialog.buttons, menuItemStates);
  33167. // TINY-9223: We only need to broadcast to the mothership containing the dialog
  33168. root.getSystem().broadcastOn([`${dialogChannel}-${id}`], dialogInit);
  33169. // NOTE: Reflecting does not have any smart handling of nested reflecting components,
  33170. // and the order of receiving a broadcast is non-deterministic. Here we use separate
  33171. // channels for each section (title, body, footer), and make those broadcasts *after*
  33172. // we've already sent the overall dialog broadcast. The overall dialog broadcast
  33173. // doesn't actually change the components ... its Reflecting config just stores state,
  33174. // but these Reflecting configs (title, body, footer) do change the components based on
  33175. // the received broadcasts.
  33176. root.getSystem().broadcastOn([`${titleChannel}-${id}`], dialogInit.internalDialog);
  33177. root.getSystem().broadcastOn([`${bodyChannel}-${id}`], dialogInit.internalDialog);
  33178. root.getSystem().broadcastOn([`${footerChannel}-${id}`], {
  33179. ...dialogInit.internalDialog,
  33180. buttons: storedMenuButtons
  33181. });
  33182. instanceApi.setData(dialogInit.initialData);
  33183. });
  33184. };
  33185. const close = () => {
  33186. withRoot((root) => {
  33187. emit(root, formCloseEvent);
  33188. });
  33189. };
  33190. const instanceApi = {
  33191. getData,
  33192. setData,
  33193. setEnabled,
  33194. focus,
  33195. block,
  33196. unblock,
  33197. showTab,
  33198. redial,
  33199. close,
  33200. toggleFullscreen: access.toggleFullscreen
  33201. };
  33202. return instanceApi;
  33203. };
  33204. const renderDialog = (dialogInit, extra, backstage) => {
  33205. const dialogId = generate$6('dialog');
  33206. const internalDialog = dialogInit.internalDialog;
  33207. const header = getHeader(internalDialog.title, dialogId, backstage);
  33208. const dialogSize = Cell(internalDialog.size);
  33209. const getCompByName$1 = (name) => getCompByName(modalAccess, name);
  33210. const dialogSizeClasses = getDialogSizeClass(dialogSize.get()).toArray();
  33211. const updateState = (comp, incoming) => {
  33212. dialogSize.set(incoming.internalDialog.size);
  33213. updateDialogSizeClass(incoming.internalDialog.size, comp);
  33214. return Optional.some(incoming);
  33215. };
  33216. const body = renderModalBody({
  33217. body: internalDialog.body,
  33218. initialData: internalDialog.initialData
  33219. }, dialogId, backstage, getCompByName$1);
  33220. const storedMenuButtons = mapMenuButtons(internalDialog.buttons);
  33221. const objOfCells = extractCellsToObject(storedMenuButtons);
  33222. const footer = someIf(storedMenuButtons.length !== 0, renderModalFooter({ buttons: storedMenuButtons }, dialogId, backstage));
  33223. const dialogEvents = initDialog(() => instanceApi, getEventExtras(() => dialog, backstage.shared.providers, extra), backstage.shared.getSink);
  33224. const spec = {
  33225. id: dialogId,
  33226. header,
  33227. body,
  33228. footer,
  33229. extraClasses: dialogSizeClasses,
  33230. extraBehaviours: [
  33231. Reflecting.config({
  33232. channel: `${dialogChannel}-${dialogId}`,
  33233. updateState,
  33234. initialData: dialogInit
  33235. }),
  33236. ],
  33237. extraStyles: {}
  33238. };
  33239. const dialog = renderModalDialog(spec, dialogEvents, backstage);
  33240. const modalAccess = (() => {
  33241. const getForm = () => {
  33242. const outerForm = ModalDialog.getBody(dialog);
  33243. return Composing.getCurrent(outerForm).getOr(outerForm);
  33244. };
  33245. const toggleFullscreen$1 = () => {
  33246. toggleFullscreen(dialog, dialogSize.get());
  33247. };
  33248. return {
  33249. getId: constant$1(dialogId),
  33250. getRoot: constant$1(dialog),
  33251. getBody: () => ModalDialog.getBody(dialog),
  33252. getFooter: () => ModalDialog.getFooter(dialog),
  33253. getFormWrapper: getForm,
  33254. toggleFullscreen: toggleFullscreen$1
  33255. };
  33256. })();
  33257. // TODO: Get the validator from the dialog state.
  33258. const instanceApi = getDialogApi(modalAccess, extra.redial, objOfCells);
  33259. return {
  33260. dialog,
  33261. instanceApi
  33262. };
  33263. };
  33264. // DUPE with SilverDialog. Cleaning up.
  33265. const renderInlineDialog = (dialogInit, extra, backstage, ariaAttrs = false, refreshDocking) => {
  33266. const dialogId = generate$6('dialog');
  33267. const dialogLabelId = generate$6('dialog-label');
  33268. const dialogContentId = generate$6('dialog-content');
  33269. const internalDialog = dialogInit.internalDialog;
  33270. const getCompByName$1 = (name) => getCompByName(modalAccess, name);
  33271. const dialogSize = Cell(internalDialog.size);
  33272. const dialogSizeClass = getDialogSizeClass(dialogSize.get()).toArray();
  33273. // Reflecting behaviour broadcasts on dialog channel only on redial.
  33274. const updateState = (comp, incoming) => {
  33275. // Update dialog size and position upon redial.
  33276. dialogSize.set(incoming.internalDialog.size);
  33277. updateDialogSizeClass(incoming.internalDialog.size, comp);
  33278. refreshDocking();
  33279. return Optional.some(incoming);
  33280. };
  33281. const memHeader = record(renderInlineHeader({
  33282. title: internalDialog.title,
  33283. draggable: true
  33284. }, dialogId, dialogLabelId, backstage.shared.providers));
  33285. const memBody = record(renderInlineBody({
  33286. body: internalDialog.body,
  33287. initialData: internalDialog.initialData,
  33288. }, dialogId, dialogContentId, backstage, ariaAttrs, getCompByName$1));
  33289. const storagedMenuButtons = mapMenuButtons(internalDialog.buttons);
  33290. const objOfCells = extractCellsToObject(storagedMenuButtons);
  33291. const optMemFooter = someIf(storagedMenuButtons.length !== 0, record(renderInlineFooter({
  33292. buttons: storagedMenuButtons
  33293. }, dialogId, backstage)));
  33294. const dialogEvents = initDialog(() => instanceApi, {
  33295. onBlock: (event) => {
  33296. Blocking.block(dialog, (_comp, bs) => {
  33297. const headerHeight = memHeader.getOpt(dialog).map((dialog) => get$d(dialog.element));
  33298. return getBusySpec(event.message, bs, backstage.shared.providers, headerHeight);
  33299. });
  33300. },
  33301. onUnblock: () => {
  33302. Blocking.unblock(dialog);
  33303. },
  33304. onClose: () => extra.closeWindow()
  33305. }, backstage.shared.getSink);
  33306. const inlineClass = 'tox-dialog-inline';
  33307. const os = detect$1().os;
  33308. // TODO: Disable while validating?
  33309. const dialog = build$1({
  33310. dom: {
  33311. tag: 'div',
  33312. classes: ['tox-dialog', inlineClass, ...dialogSizeClass],
  33313. attributes: {
  33314. role: 'dialog',
  33315. // TINY-10808 - Workaround to address the dialog header not being announced on VoiceOver with aria-labelledby, ideally we should use the aria-labelledby
  33316. ...os.isMacOS() ? { 'aria-label': internalDialog.title } : { 'aria-labelledby': dialogLabelId }
  33317. }
  33318. },
  33319. eventOrder: {
  33320. [receive()]: [Reflecting.name(), Receiving.name()],
  33321. [execute$5()]: ['execute-on-form'],
  33322. [attachedToDom()]: ['reflecting', 'execute-on-form']
  33323. },
  33324. // Dupe with SilverDialog.
  33325. behaviours: derive$1([
  33326. Keying.config({
  33327. mode: 'cyclic',
  33328. onEscape: (c) => {
  33329. emit(c, formCloseEvent);
  33330. return Optional.some(true);
  33331. },
  33332. useTabstopAt: (elem) => !isPseudoStop(elem) && (name$3(elem) !== 'button' || get$g(elem, 'disabled') !== 'disabled'),
  33333. firstTabstop: 1
  33334. }),
  33335. Reflecting.config({
  33336. channel: `${dialogChannel}-${dialogId}`,
  33337. updateState,
  33338. initialData: dialogInit
  33339. }),
  33340. Focusing.config({}),
  33341. config('execute-on-form', dialogEvents.concat([
  33342. // Note: `runOnSource` here will only listen to the event at the outer component level.
  33343. // Using just `run` instead will cause an infinite loop as `focusIn` would fire a `focusin` which would then get responded to and so forth.
  33344. runOnSource(focusin(), (comp, _se) => {
  33345. Keying.focusIn(comp);
  33346. }),
  33347. run$1(focusShifted(), (comp, se) => {
  33348. comp.getSystem().broadcastOn([dialogFocusShiftedChannel], {
  33349. newFocus: se.event.newFocus
  33350. });
  33351. })
  33352. ])),
  33353. Blocking.config({ getRoot: () => Optional.some(dialog) }),
  33354. Replacing.config({}),
  33355. memory({})
  33356. ]),
  33357. components: [
  33358. memHeader.asSpec(),
  33359. memBody.asSpec(),
  33360. ...optMemFooter.map((memFooter) => memFooter.asSpec()).toArray()
  33361. ]
  33362. });
  33363. const toggleFullscreen$1 = () => {
  33364. toggleFullscreen(dialog, dialogSize.get());
  33365. };
  33366. // TODO: Clean up the dupe between this (InlineDialog) and SilverDialog
  33367. const modalAccess = {
  33368. getId: constant$1(dialogId),
  33369. getRoot: constant$1(dialog),
  33370. getFooter: () => optMemFooter.map((memFooter) => memFooter.get(dialog)),
  33371. getBody: () => memBody.get(dialog),
  33372. getFormWrapper: () => {
  33373. const body = memBody.get(dialog);
  33374. return Composing.getCurrent(body).getOr(body);
  33375. },
  33376. toggleFullscreen: toggleFullscreen$1
  33377. };
  33378. const instanceApi = getDialogApi(modalAccess, extra.redial, objOfCells);
  33379. return {
  33380. dialog,
  33381. instanceApi
  33382. };
  33383. };
  33384. var global = tinymce.util.Tools.resolve('tinymce.util.URI');
  33385. const getUrlDialogApi = (root) => {
  33386. const withRoot = (f) => {
  33387. if (root.getSystem().isConnected()) {
  33388. f(root);
  33389. }
  33390. };
  33391. const block = (message) => {
  33392. if (!isString(message)) {
  33393. throw new Error('The urlDialogInstanceAPI.block function should be passed a blocking message of type string as an argument');
  33394. }
  33395. withRoot((root) => {
  33396. emitWith(root, formBlockEvent, { message });
  33397. });
  33398. };
  33399. const unblock = () => {
  33400. withRoot((root) => {
  33401. emit(root, formUnblockEvent);
  33402. });
  33403. };
  33404. const close = () => {
  33405. withRoot((root) => {
  33406. emit(root, formCloseEvent);
  33407. });
  33408. };
  33409. const sendMessage = (data) => {
  33410. withRoot((root) => {
  33411. root.getSystem().broadcastOn([bodySendMessageChannel], data);
  33412. });
  33413. };
  33414. return {
  33415. block,
  33416. unblock,
  33417. close,
  33418. sendMessage
  33419. };
  33420. };
  33421. // A list of supported message actions
  33422. const SUPPORTED_MESSAGE_ACTIONS = ['insertContent', 'setContent', 'execCommand', 'close', 'block', 'unblock'];
  33423. const isSupportedMessage = (data) => isObject(data) && SUPPORTED_MESSAGE_ACTIONS.indexOf(data.mceAction) !== -1;
  33424. const isCustomMessage = (data) => !isSupportedMessage(data) && isObject(data) && has$2(data, 'mceAction');
  33425. const handleMessage = (editor, api, data) => {
  33426. switch (data.mceAction) {
  33427. case 'insertContent':
  33428. editor.insertContent(data.content);
  33429. break;
  33430. case 'setContent':
  33431. editor.setContent(data.content);
  33432. break;
  33433. case 'execCommand':
  33434. const ui = isBoolean(data.ui) ? data.ui : false;
  33435. editor.execCommand(data.cmd, ui, data.value);
  33436. break;
  33437. case 'close':
  33438. api.close();
  33439. break;
  33440. case 'block':
  33441. api.block(data.message);
  33442. break;
  33443. case 'unblock':
  33444. api.unblock();
  33445. break;
  33446. }
  33447. };
  33448. const renderUrlDialog = (internalDialog, extra, editor, backstage) => {
  33449. const dialogId = generate$6('dialog');
  33450. const header = getHeader(internalDialog.title, dialogId, backstage);
  33451. const body = renderIframeBody(internalDialog);
  33452. const footer = internalDialog.buttons.bind((buttons) => {
  33453. // Don't render a footer if no buttons are specified
  33454. if (buttons.length === 0) {
  33455. return Optional.none();
  33456. }
  33457. else {
  33458. return Optional.some(renderModalFooter({ buttons }, dialogId, backstage));
  33459. }
  33460. });
  33461. const dialogEvents = initUrlDialog(() => instanceApi, getEventExtras(() => dialog, backstage.shared.providers, extra));
  33462. // Add the styles for the modal width/height
  33463. const styles = {
  33464. ...internalDialog.height.fold(() => ({}), (height) => ({ 'height': height + 'px', 'max-height': height + 'px' })),
  33465. ...internalDialog.width.fold(() => ({}), (width) => ({ 'width': width + 'px', 'max-width': width + 'px' }))
  33466. };
  33467. // Default back to using a large sized dialog, if no dimensions are specified
  33468. const classes = internalDialog.width.isNone() && internalDialog.height.isNone() ? ['tox-dialog--width-lg'] : [];
  33469. // Determine the iframe urls domain, so we can target that specifically when sending messages
  33470. const iframeUri = new global(internalDialog.url, { base_uri: new global(window.location.href) });
  33471. const iframeDomain = `${iframeUri.protocol}://${iframeUri.host}${iframeUri.port ? ':' + iframeUri.port : ''}`;
  33472. const messageHandlerUnbinder = unbindable();
  33473. const updateState = (_comp, incoming) => Optional.some(incoming);
  33474. // Setup the behaviours for dealing with messages between the iframe and current window
  33475. const extraBehaviours = [
  33476. // Because this doesn't define `renderComponents`, all this does is update the state.
  33477. // We use the state for the initialData. The other parts (body etc.) render the
  33478. // components based on what reflecting receives.
  33479. Reflecting.config({
  33480. channel: `${dialogChannel}-${dialogId}`,
  33481. updateState,
  33482. initialData: internalDialog
  33483. }),
  33484. config('messages', [
  33485. // When the dialog is opened, bind a window message listener for the spec url
  33486. runOnAttached(() => {
  33487. const unbind = bind$1(SugarElement.fromDom(window), 'message', (e) => {
  33488. // Validate that the request came from the correct domain
  33489. if (iframeUri.isSameOrigin(new global(e.raw.origin))) {
  33490. const data = e.raw.data;
  33491. // Handle the message if it has the 'mceAction' key, otherwise just ignore it
  33492. if (isSupportedMessage(data)) {
  33493. handleMessage(editor, instanceApi, data);
  33494. }
  33495. else if (isCustomMessage(data)) {
  33496. internalDialog.onMessage(instanceApi, data);
  33497. }
  33498. }
  33499. });
  33500. messageHandlerUnbinder.set(unbind);
  33501. }),
  33502. // When the dialog is closed, unbind the window message listener
  33503. runOnDetached(messageHandlerUnbinder.clear)
  33504. ]),
  33505. Receiving.config({
  33506. channels: {
  33507. [bodySendMessageChannel]: {
  33508. onReceive: (comp, data) => {
  33509. // Send the message to the iframe via postMessage
  33510. descendant(comp.element, 'iframe').each((iframeEle) => {
  33511. const iframeWin = iframeEle.dom.contentWindow;
  33512. if (isNonNullable(iframeWin)) {
  33513. iframeWin.postMessage(data, iframeDomain);
  33514. }
  33515. });
  33516. }
  33517. }
  33518. }
  33519. })
  33520. ];
  33521. const spec = {
  33522. id: dialogId,
  33523. header,
  33524. body,
  33525. footer,
  33526. extraClasses: classes,
  33527. extraBehaviours,
  33528. extraStyles: styles
  33529. };
  33530. const dialog = renderModalDialog(spec, dialogEvents, backstage);
  33531. const instanceApi = getUrlDialogApi(dialog);
  33532. return {
  33533. dialog,
  33534. instanceApi
  33535. };
  33536. };
  33537. const setup$2 = (backstage) => {
  33538. const sharedBackstage = backstage.shared;
  33539. const open = (message, callback) => {
  33540. const closeDialog = () => {
  33541. ModalDialog.hide(alertDialog);
  33542. callback();
  33543. };
  33544. const memFooterClose = record(renderFooterButton({
  33545. context: 'any',
  33546. name: 'close-alert',
  33547. text: 'OK',
  33548. primary: true,
  33549. buttonType: Optional.some('primary'),
  33550. align: 'end',
  33551. enabled: true,
  33552. icon: Optional.none()
  33553. }, 'cancel', backstage));
  33554. const titleSpec = pUntitled();
  33555. const closeSpec = pClose(closeDialog, sharedBackstage.providers);
  33556. const alertDialog = build$1(renderDialog$1({
  33557. lazySink: () => sharedBackstage.getSink(),
  33558. header: hiddenHeader(titleSpec, closeSpec),
  33559. body: pBodyMessage(message, sharedBackstage.providers),
  33560. footer: Optional.some(pFooter(pFooterGroup([], [
  33561. memFooterClose.asSpec()
  33562. ]))),
  33563. onEscape: closeDialog,
  33564. extraClasses: ['tox-alert-dialog'],
  33565. extraBehaviours: [],
  33566. extraStyles: {},
  33567. dialogEvents: [
  33568. run$1(formCancelEvent, closeDialog)
  33569. ],
  33570. eventOrder: {}
  33571. }));
  33572. ModalDialog.show(alertDialog);
  33573. const footerCloseButton = memFooterClose.get(alertDialog);
  33574. Focusing.focus(footerCloseButton);
  33575. };
  33576. return {
  33577. open
  33578. };
  33579. };
  33580. const setup$1 = (backstage) => {
  33581. const sharedBackstage = backstage.shared;
  33582. // FIX: Extreme dupe with Alert dialog
  33583. const open = (message, callback) => {
  33584. const closeDialog = (state) => {
  33585. ModalDialog.hide(confirmDialog);
  33586. callback(state);
  33587. };
  33588. const memFooterYes = record(renderFooterButton({
  33589. context: 'any',
  33590. name: 'yes',
  33591. text: 'Yes',
  33592. primary: true,
  33593. buttonType: Optional.some('primary'),
  33594. align: 'end',
  33595. enabled: true,
  33596. icon: Optional.none()
  33597. }, 'submit', backstage));
  33598. const footerNo = renderFooterButton({
  33599. context: 'any',
  33600. name: 'no',
  33601. text: 'No',
  33602. primary: false,
  33603. buttonType: Optional.some('secondary'),
  33604. align: 'end',
  33605. enabled: true,
  33606. icon: Optional.none()
  33607. }, 'cancel', backstage);
  33608. const titleSpec = pUntitled();
  33609. const closeSpec = pClose(() => closeDialog(false), sharedBackstage.providers);
  33610. const confirmDialog = build$1(renderDialog$1({
  33611. lazySink: () => sharedBackstage.getSink(),
  33612. header: hiddenHeader(titleSpec, closeSpec),
  33613. body: pBodyMessage(message, sharedBackstage.providers),
  33614. footer: Optional.some(pFooter(pFooterGroup([], [
  33615. footerNo,
  33616. memFooterYes.asSpec()
  33617. ]))),
  33618. onEscape: () => closeDialog(false),
  33619. extraClasses: ['tox-confirm-dialog'],
  33620. extraBehaviours: [],
  33621. extraStyles: {},
  33622. dialogEvents: [
  33623. run$1(formCancelEvent, () => closeDialog(false)),
  33624. run$1(formSubmitEvent, () => closeDialog(true))
  33625. ],
  33626. eventOrder: {}
  33627. }));
  33628. ModalDialog.show(confirmDialog);
  33629. const footerYesButton = memFooterYes.get(confirmDialog);
  33630. Focusing.focus(footerYesButton);
  33631. };
  33632. return {
  33633. open
  33634. };
  33635. };
  33636. const validateData = (data, validator) => getOrDie(asRaw('data', validator, data));
  33637. const isAlertOrConfirmDialog = (target) => closest$1(target, '.tox-alert-dialog') || closest$1(target, '.tox-confirm-dialog');
  33638. const inlineAdditionalBehaviours = (editor, isStickyToolbar, isToolbarLocationTop, onHide) => {
  33639. // When using sticky toolbars it already handles the docking behaviours so applying docking would
  33640. // do nothing except add additional processing when scrolling, so we don't want to include it here
  33641. // (Except when the toolbar is located at the bottom since the anchor will be at the top)
  33642. if (isStickyToolbar && isToolbarLocationTop) {
  33643. return [];
  33644. }
  33645. else {
  33646. return [
  33647. Docking.config({
  33648. contextual: {
  33649. lazyContext: () => Optional.some(box$1(SugarElement.fromDom(editor.getContentAreaContainer()))),
  33650. fadeInClass: 'tox-dialog-dock-fadein',
  33651. fadeOutClass: 'tox-dialog-dock-fadeout',
  33652. transitionClass: 'tox-dialog-dock-transition',
  33653. onHide
  33654. },
  33655. modes: ['top'],
  33656. lazyViewport: (comp) => {
  33657. // If we don't have a special scrolling environment, then just use the default
  33658. // viewport of (window)
  33659. const optScrollingContext = detectWhenSplitUiMode(editor, comp.element);
  33660. return optScrollingContext
  33661. .map((sc) => {
  33662. const combinedBounds = getBoundsFrom(sc);
  33663. return {
  33664. bounds: combinedBounds,
  33665. optScrollEnv: Optional.some({
  33666. currentScrollTop: sc.element.dom.scrollTop,
  33667. scrollElmTop: absolute$3(sc.element).top
  33668. })
  33669. };
  33670. }).getOrThunk(() => ({
  33671. bounds: win(),
  33672. optScrollEnv: Optional.none()
  33673. }));
  33674. }
  33675. })
  33676. ];
  33677. }
  33678. };
  33679. const setup = (extras) => {
  33680. const editor = extras.editor;
  33681. const isStickyToolbar$1 = isStickyToolbar(editor);
  33682. // Alert and Confirm dialogs are Modal Dialogs
  33683. const alertDialog = setup$2(extras.backstages.dialog);
  33684. const confirmDialog = setup$1(extras.backstages.dialog);
  33685. const open = (config, params, closeWindow) => {
  33686. if (!isUndefined(params)) {
  33687. if (params.inline === 'toolbar') {
  33688. return openInlineDialog(config, extras.backstages.popup.shared.anchors.inlineDialog(), closeWindow, params);
  33689. }
  33690. else if (params.inline === 'bottom') {
  33691. return openBottomInlineDialog(config, extras.backstages.popup.shared.anchors.inlineBottomDialog(), closeWindow, params);
  33692. }
  33693. else if (params.inline === 'cursor') {
  33694. return openInlineDialog(config, extras.backstages.popup.shared.anchors.cursor(), closeWindow, params);
  33695. }
  33696. }
  33697. return openModalDialog(config, closeWindow);
  33698. };
  33699. const openUrl = (config, closeWindow) => openModalUrlDialog(config, closeWindow);
  33700. const openModalUrlDialog = (config, closeWindow) => {
  33701. const factory = (contents) => {
  33702. const dialog = renderUrlDialog(contents, {
  33703. closeWindow: () => {
  33704. ModalDialog.hide(dialog.dialog);
  33705. closeWindow(dialog.instanceApi);
  33706. }
  33707. }, editor, extras.backstages.dialog);
  33708. ModalDialog.show(dialog.dialog);
  33709. return dialog.instanceApi;
  33710. };
  33711. return DialogManager.openUrl(factory, config);
  33712. };
  33713. const openModalDialog = (config, closeWindow) => {
  33714. const factory = (contents, internalInitialData, dataValidator) => {
  33715. // We used to validate data here, but it's done by the instanceApi.setData call below.
  33716. const initialData = internalInitialData;
  33717. const dialogInit = {
  33718. dataValidator,
  33719. initialData,
  33720. internalDialog: contents
  33721. };
  33722. const dialog = renderDialog(dialogInit, {
  33723. redial: DialogManager.redial,
  33724. closeWindow: () => {
  33725. ModalDialog.hide(dialog.dialog);
  33726. closeWindow(dialog.instanceApi);
  33727. }
  33728. }, extras.backstages.dialog);
  33729. ModalDialog.show(dialog.dialog);
  33730. dialog.instanceApi.setData(initialData);
  33731. return dialog.instanceApi;
  33732. };
  33733. return DialogManager.open(factory, config);
  33734. };
  33735. const openInlineDialog = (config$1, anchor, closeWindow, windowParams) => {
  33736. const factory = (contents, internalInitialData, dataValidator) => {
  33737. const initialData = validateData(internalInitialData, dataValidator);
  33738. const inlineDialog = value$2();
  33739. const isToolbarLocationTop = extras.backstages.popup.shared.header.isPositionedAtTop();
  33740. const dialogInit = {
  33741. dataValidator,
  33742. initialData,
  33743. internalDialog: contents
  33744. };
  33745. const refreshDocking = () => inlineDialog.on((dialog) => {
  33746. InlineView.reposition(dialog);
  33747. if (!isStickyToolbar$1 || !isToolbarLocationTop) {
  33748. Docking.refresh(dialog);
  33749. }
  33750. });
  33751. const dialogUi = renderInlineDialog(dialogInit, {
  33752. redial: DialogManager.redial,
  33753. closeWindow: () => {
  33754. inlineDialog.on(InlineView.hide);
  33755. editor.off('ResizeEditor', refreshDocking);
  33756. editor.off('ScrollWindow', repositionPopups$1);
  33757. inlineDialog.clear();
  33758. closeWindow(dialogUi.instanceApi);
  33759. }
  33760. }, extras.backstages.popup, windowParams.ariaAttrs, refreshDocking);
  33761. const repositionPopups$1 = () => dialogUi.dialog.getSystem().broadcastOn([repositionPopups()], { target: dialogUi.dialog.element });
  33762. const dismissPopups$1 = () => dialogUi.dialog.getSystem().broadcastOn([dismissPopups()], { target: dialogUi.dialog.element });
  33763. const inlineDialogComp = build$1(InlineView.sketch({
  33764. lazySink: extras.backstages.popup.shared.getSink,
  33765. dom: {
  33766. tag: 'div',
  33767. classes: []
  33768. },
  33769. // Fires the default dismiss event.
  33770. fireDismissalEventInstead: (windowParams.persistent ? { event: 'doNotDismissYet' } : {}),
  33771. // TINY-9412: The docking behaviour for inline dialogs is inconsistent
  33772. // for toolbar_location: bottom. We need to clarify exactly what the behaviour
  33773. // should be. The intent here might have been that they shouldn't automatically
  33774. // reposition at all because they aren't visually connected to the toolbar
  33775. // (i.e. inline "toolbar" dialogs still display at the top, even when the
  33776. // toolbar_location is bottom), but it's unclear.
  33777. ...isToolbarLocationTop ? {} : { fireRepositionEventInstead: {} },
  33778. inlineBehaviours: derive$1([
  33779. config('window-manager-inline-events', [
  33780. run$1(dismissRequested(), (_comp, _se) => {
  33781. emit(dialogUi.dialog, formCancelEvent);
  33782. })
  33783. ]),
  33784. ...inlineAdditionalBehaviours(editor, isStickyToolbar$1, isToolbarLocationTop, dismissPopups$1)
  33785. ]),
  33786. // Treat alert or confirm dialogs as part of the inline dialog
  33787. isExtraPart: (_comp, target) => isAlertOrConfirmDialog(target)
  33788. }));
  33789. inlineDialog.set(inlineDialogComp);
  33790. const getInlineDialogBounds = () => {
  33791. // At the moment the inline dialog is just put anywhere in the body, and docking is what is used to make
  33792. // sure that it stays onscreen
  33793. const elem = editor.inline ? body() : SugarElement.fromDom(editor.getContainer());
  33794. const bounds = box$1(elem);
  33795. return Optional.some(bounds);
  33796. };
  33797. // Position the inline dialog
  33798. InlineView.showWithinBounds(inlineDialogComp, premade(dialogUi.dialog), { anchor }, getInlineDialogBounds);
  33799. // Refresh the docking position if not using a sticky toolbar
  33800. if (!isStickyToolbar$1 || !isToolbarLocationTop) {
  33801. Docking.refresh(inlineDialogComp);
  33802. // Bind to the editor resize event and update docking as needed. We don't need to worry about
  33803. // 'ResizeWindow` as that's handled by docking already.
  33804. editor.on('ResizeEditor', refreshDocking);
  33805. }
  33806. editor.on('ScrollWindow', repositionPopups$1);
  33807. // Set the initial data in the dialog and focus the first focusable item
  33808. dialogUi.instanceApi.setData(initialData);
  33809. Keying.focusIn(dialogUi.dialog);
  33810. return dialogUi.instanceApi;
  33811. };
  33812. return DialogManager.open(factory, config$1);
  33813. };
  33814. const openBottomInlineDialog = (config$1, anchor, closeWindow, windowParams) => {
  33815. const factory = (contents, internalInitialData, dataValidator) => {
  33816. const initialData = validateData(internalInitialData, dataValidator);
  33817. const inlineDialog = value$2();
  33818. const isToolbarLocationTop = extras.backstages.popup.shared.header.isPositionedAtTop();
  33819. const dialogInit = {
  33820. dataValidator,
  33821. initialData,
  33822. internalDialog: contents
  33823. };
  33824. const refreshDocking = () => inlineDialog.on((dialog) => {
  33825. InlineView.reposition(dialog);
  33826. Docking.refresh(dialog);
  33827. });
  33828. const dialogUi = renderInlineDialog(dialogInit, {
  33829. redial: DialogManager.redial,
  33830. closeWindow: () => {
  33831. inlineDialog.on(InlineView.hide);
  33832. editor.off('ResizeEditor ScrollWindow ElementScroll', refreshDocking);
  33833. inlineDialog.clear();
  33834. closeWindow(dialogUi.instanceApi);
  33835. }
  33836. }, extras.backstages.popup, windowParams.ariaAttrs, refreshDocking);
  33837. const inlineDialogComp = build$1(InlineView.sketch({
  33838. lazySink: extras.backstages.popup.shared.getSink,
  33839. dom: {
  33840. tag: 'div',
  33841. classes: []
  33842. },
  33843. // Fires the default dismiss event.
  33844. fireDismissalEventInstead: (windowParams.persistent ? { event: 'doNotDismissYet' } : {}),
  33845. ...isToolbarLocationTop ? {} : { fireRepositionEventInstead: {} },
  33846. inlineBehaviours: derive$1([
  33847. config('window-manager-inline-events', [
  33848. run$1(dismissRequested(), (_comp, _se) => {
  33849. emit(dialogUi.dialog, formCancelEvent);
  33850. })
  33851. ]),
  33852. Docking.config({
  33853. contextual: {
  33854. lazyContext: () => Optional.some(box$1(SugarElement.fromDom(editor.getContentAreaContainer()))),
  33855. fadeInClass: 'tox-dialog-dock-fadein',
  33856. fadeOutClass: 'tox-dialog-dock-fadeout',
  33857. transitionClass: 'tox-dialog-dock-transition'
  33858. },
  33859. modes: ['top', 'bottom'],
  33860. lazyViewport: (comp) => {
  33861. const optScrollingContext = detectWhenSplitUiMode(editor, comp.element);
  33862. return optScrollingContext.map((sc) => {
  33863. const combinedBounds = getBoundsFrom(sc);
  33864. return {
  33865. bounds: combinedBounds,
  33866. optScrollEnv: Optional.some({
  33867. currentScrollTop: sc.element.dom.scrollTop,
  33868. scrollElmTop: absolute$3(sc.element).top
  33869. })
  33870. };
  33871. }).getOrThunk(() => ({
  33872. bounds: win(),
  33873. optScrollEnv: Optional.none()
  33874. }));
  33875. }
  33876. })
  33877. ]),
  33878. // Treat alert or confirm dialogs as part of the inline dialog
  33879. isExtraPart: (_comp, target) => isAlertOrConfirmDialog(target)
  33880. }));
  33881. inlineDialog.set(inlineDialogComp);
  33882. const getInlineDialogBounds = () => {
  33883. return extras.backstages.popup.shared.getSink().toOptional().bind((s) => {
  33884. const optScrollingContext = detectWhenSplitUiMode(editor, s.element);
  33885. // Margin between element and the bottom of the screen or the editor content area container
  33886. const margin = 15;
  33887. const bounds$1 = optScrollingContext.map((sc) => getBoundsFrom(sc)).getOr(win());
  33888. const contentAreaContainer = box$1(SugarElement.fromDom(editor.getContentAreaContainer()));
  33889. const constrainedBounds = constrain(contentAreaContainer, bounds$1);
  33890. return Optional.some(bounds(constrainedBounds.x, constrainedBounds.y, constrainedBounds.width, constrainedBounds.height - margin));
  33891. });
  33892. };
  33893. // Position the inline dialog
  33894. InlineView.showWithinBounds(inlineDialogComp, premade(dialogUi.dialog), { anchor }, getInlineDialogBounds);
  33895. Docking.refresh(inlineDialogComp);
  33896. editor.on('ResizeEditor ScrollWindow ElementScroll ResizeWindow', refreshDocking);
  33897. // Set the initial data in the dialog and focus the first focusable item
  33898. dialogUi.instanceApi.setData(initialData);
  33899. Keying.focusIn(dialogUi.dialog);
  33900. return dialogUi.instanceApi;
  33901. };
  33902. return DialogManager.open(factory, config$1);
  33903. };
  33904. const confirm = (message, callback) => {
  33905. confirmDialog.open(message, callback);
  33906. };
  33907. const alert = (message, callback) => {
  33908. alertDialog.open(message, callback);
  33909. };
  33910. const close = (instanceApi) => {
  33911. instanceApi.close();
  33912. };
  33913. return {
  33914. open,
  33915. openUrl,
  33916. alert,
  33917. close,
  33918. confirm
  33919. };
  33920. };
  33921. const registerOptions = (editor) => {
  33922. register$f(editor);
  33923. register$e(editor);
  33924. register(editor);
  33925. };
  33926. var Theme = () => {
  33927. global$b.add('silver', (editor) => {
  33928. registerOptions(editor);
  33929. // When using the ui_mode: split, the popup sink is placed as a sibling to the
  33930. // editor, which means that it might be subject to any scrolling environments
  33931. // that the editor has. Therefore, we want to make the popup sink have an overall
  33932. // bounds that is dependent on its scrolling environment. We don't know that ahead
  33933. // of time, so we use a mutable variable whose value will change if there is a scrolling context.
  33934. let popupSinkBounds = () => win();
  33935. const { dialogs, popups, renderUI: renderModeUI } = setup$3(editor, {
  33936. // consult the mutable variable to find out the bounds for the popup sink. When renderUI is
  33937. // called, this mutable variable might be reassigned
  33938. getPopupSinkBounds: () => popupSinkBounds()
  33939. });
  33940. // We wrap the `renderModeUI` function being returned by Render so that we can update
  33941. // the getPopupSinkBounds mutable variable if required.
  33942. // DON'T define this function as `async`; otherwise, it will slow down the rendering process and cause flickering if the editor is repeatedly removed and re-initialized.
  33943. const renderUI = () => {
  33944. const renderResult = renderModeUI();
  33945. const optScrollingContext = detectWhenSplitUiMode(editor, popups.getMothership().element);
  33946. optScrollingContext.each((sc) => {
  33947. popupSinkBounds = () => {
  33948. // At this stage, it looks like we need to calculate the bounds each time, just in
  33949. // case the scrolling context details have changed since the last time. The bounds considers
  33950. // the Boxes.box sizes, which might change over time.
  33951. return getBoundsFrom(sc);
  33952. };
  33953. });
  33954. return renderResult;
  33955. };
  33956. Autocompleter.register(editor, popups.backstage.shared);
  33957. const windowMgr = setup({
  33958. editor,
  33959. backstages: {
  33960. popup: popups.backstage,
  33961. dialog: dialogs.backstage
  33962. }
  33963. });
  33964. const notificationRegion = value$2();
  33965. // The NotificationManager uses the popup mothership (and sink)
  33966. const getNotificationManagerImpl = () => NotificationManagerImpl(editor, { backstage: popups.backstage }, popups.getMothership(), notificationRegion);
  33967. const getPromotionElement = () => {
  33968. return descendant(SugarElement.fromDom(editor.getContainer()), '.tox-promotion').map((promotion) => promotion.dom).getOrNull();
  33969. };
  33970. return {
  33971. renderUI,
  33972. getWindowManagerImpl: constant$1(windowMgr),
  33973. getNotificationManagerImpl,
  33974. getPromotionElement
  33975. };
  33976. });
  33977. };
  33978. Theme();
  33979. /** *****
  33980. * DO NOT EXPORT ANYTHING
  33981. *
  33982. * IF YOU DO ROLLUP WILL LEAVE A GLOBAL ON THE PAGE
  33983. *******/
  33984. })();