From 45af81d4c62af19e1f799e6f2defcd3e599cbca7 Mon Sep 17 00:00:00 2001 From: Shay Sapozhnikov <45404072+ShaySapozhnikov@users.noreply.github.com> Date: Wed, 10 Jun 2026 16:27:28 -0500 Subject: [PATCH] feat(dropdown): add single-choice dropdown bubble component Introduces a focusable dropdown with keyboard navigation, selection messages, runtime option updates, and a demo example with VHS recording. Co-authored-by: Cursor --- dropdown/demo.gif | Bin 0 -> 78376 bytes dropdown/demo.tape | 47 ++++ dropdown/dropdown.go | 460 ++++++++++++++++++++++++++++++++++++++ dropdown/dropdown_test.go | 394 ++++++++++++++++++++++++++++++++ examples/dropdown/main.go | 150 +++++++++++++ 5 files changed, 1051 insertions(+) create mode 100644 dropdown/demo.gif create mode 100644 dropdown/demo.tape create mode 100644 dropdown/dropdown.go create mode 100644 dropdown/dropdown_test.go create mode 100644 examples/dropdown/main.go diff --git a/dropdown/demo.gif b/dropdown/demo.gif new file mode 100644 index 0000000000000000000000000000000000000000..07ad93ebb46a79d08df34f52077a760786c7a829 GIT binary patch literal 78376 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

TEqJZj_2s z7wxsx?X9DM^rK`$UaZ@Rcp2z{W&9)g+>iMXIkT zq3gm8l^Dny%i#aZa5fSRY=)0NBj+Mbc#4^JN111#1!%~x0LG4I^dW$T6B1vG7()O< zA)^Iy)-CyWUt{NP^4HxXMgpuSzZNt8#OD4B(317rUzxV=*6)#-;QN3~`V4L2K#B~; z##37@Y>!JAz(Fk}i@pE!(_Wcqj{q<=gSq#avflh*FVb+}6+pkMfo_QyAB41fBKl_y zt%$r|Gj;P&c>yC%p)XWr&_?u1Uv$?aG28^(iK>V1Qg4-Jpq zPg-%P4`6&S)|n6?ESi5hdXAP14)oWJKV~7^Ktt)NIT5r1HWZ8y9tT`Uxbm-fuql@>}^cv|OG5P?Le#U|f z5D~kSf_FyxOAXn|Y_!;S>F_D~3lkV>gx+Ix1M`@rVDMlw4^+@x^XV5cV1|+MNi6sx zvTSwmZ8DRJ&@616d=b=Gp1yu@b}veymoQJ!CW!5vk_e@nNW~oJ?(@?y7>!DLbiQ{uHchSaWgK zvU__1Ry}|fW}7&Ubq`9%z_yF^Oyp(7lO~T&RwmFbTz*? z7vT}^K90S=>#U!Vc$H0%%w^iKDcvHHi?A_c8&Jxj-W5@1i_2R4EJO+F=L^N$HIemM zjg7|}x4pd>`!j6MOy(*b^&cUzDxXOOsKa6kLw%mT1kx!Rwtt`&iNPE+*S>Y}UggSv{jQ&_d zF2X479HNgYex0876j|}i2P8AEu~8cy5N%chGyr;Gjq?FOvWY>n#5naA3p|?U-J9U8 zh`qg}S#iy2=eZ^e^Q8i~5}A6zWiNG&G1;AovP=nX8=Xu1n3>jsmV(Re=EeYGedW8N^NJv7+mpN9A0WGB(+FHum zdCJq}2ysgTg)at+!pT7>zRaw_2P5D`6a6j*O=S~j8qWPAEuQ_ZDD#)wC8@US6ZkXR zHW&fsz8w=)PaG1{U?aGk&3MFt-~MbnCZa#k;8%-i|MXj#Ewy#Z&7Yz0YGoqR1eR&& zk2!djkn&YXYtvh?MHb~9L`}3llzP>O$C*l9#?7C*`_8UQcgTIW4!Pd-=fTz*%0HsJ zUU7^k02pAxP2UKetGDscTbOjT0uf%$p$%b_&#m{?W&%H3U^&1T;*B#jytvB^$~luiVmF(Nl&r6muZ)DN5T?0fk|NA^m|ddctQi{#`RzrX)WS zF)kaya0@Y1OxTT52br`n*rS)t6I8Vqqmu*d;<~<{XX&RhE}=wJL|V^DibinSZu(UW zO2WW~;%zI9RFj4h7i3RBsY51esS;PzO$qFWrgEtNC@F%~(0nH2osujzC0()9Od9e| z6UK%lt zMW}bh6e|t!tHwf3p1ED9le!ESTJSDt@hQo?)))<-f1aI>wPSB+KF>i=uaL8z-cC+ z*ocx3i3w}`K7TpO>$QUo<9Dj;qX0tqT5C}Kd=Ad0BSjvcTCLQ_$Lq9Q5=K}5G= z01LX=X4da{-w!j#%!ip{zCFkD2k?PFl6&3PeV!M|(2q*z;RpB!ZYgZP+}5I9Ad*)6 znn(J;qqMLQ$2TJU_aJCrKw~EoS_@hNq7i@`ssK%SqjlFYcew*(_AVAM#nfGJg(o@Y z9ng}yl(@IqO$&qa9HT=Y$QX9(XvnHz(RtR@NqqPHy;BV5|2I2 zN3-!C6oduNR3|nvgOAB!VQP|7(zuu$J|;~LojX1)o-y`wuHUOcO11FkHU_CBe3BCI zYSl0wLy=;>04E$AhzpqC?rhrgh=+DoI2&v5AJl{ecbq*~_&y)XIG<244@%(+C#aMc zJXGcU9rctSQ7$HaUq}NXcqZ}RI3QL)9Ohw3e9W|*+|S}p09bbb(kR!p2}s}N zgiY6=^1z>mPk8PAc`6V=uNCO)pK!dUi^H1P^lT6|9VAOdyzb+ z!zt!s<-?u|h5^5aM_WD`ad6Jk;~`bc-z~0m)ZHqeuNaH0_SihsQMKZI)bW5lzmbfU z4G${aEV4~^B?~OJ4*w!jJ-0qb%$W;|X0Q=7D0fHJ2jW!|qgxpnSGlZY>gf z==yzS<;FiQZ}~hA-57Hzb|pJvt$j!-9;@5!BL)AU`)=4YHQJIGR#txAxi4(rG z`0$4YdPX|UHn6AF?+HO{k{PNY)C zl;Xp3{FRO!vzCQg@a$r0(H2?M432(;Jj}x9_r4}rp%a4h`(Fy3rRNr*aW~^7viK$; z9N|&rqi|P8X8rNa#7s8)r%vlM%fhz}qxhN+V}=={eRFUozlCkcl(&nz^xUi!$Ya}! zTU3>)Nqx%w$pvR^^!RW4-l8bodBhOcM^SxoyWZkb3`kcR?2hkxwW#}u zB|pY()v>{qS6BV9VPo;Bnj&UUkyX(Q9JVDzh)5QHGT)xI;6FVnwa8|Fvy9yx6GE`a zDoGnyD`IewHd(Rg>21018uw%55Ps4QqwfnQ=K2*{5(ku;&4(I%v&5 z#{TwV_#7(h$tqM$N#@BkYY>n}+v_1%YwXI2*$s25+--YCj%}7+Os2EB4za$fD3e>$ zs}6_X{<#|W#f=un`+znNmmkD~^AGA_sF;cDaPuA4%Fi%J{$&qyPA(oioAvF@b82qR zpq{10cH5tk^H6VHKbPiodlM<8e@H!5;GHt3}4C?uOU2TZzSY2`lYO<~V3}e8>(#@2U+rudugx zB4AVe`^amR@;P%ykK&J>k$tugOLaf;rWiNz}RqQPi52IO@{jQtJe4mT<{k{I~C6 zVO@AJD@SNH(AHo;l{3C7JHP>U$xhyg=~p1!il)Hqjv0Yvi-iIF+I&?_W=Z{$L<5r- z5wQm+%2|3M7QYr{5~nfl--ZJ zT>W{d=`I^Zgik`YQIU9C4RrADdwBRu8$DZxvF8eqxl)n->aa>tr$Un*ZB z$A@Z~+pCnYABetgXvkPyxNZ6bSm@ws)#|h_s-j%r#_8uJAPi_<=h$fkVO#a{cgJSA z8hOk$yn%+?QmyQqs3KUv#5E{xqulJ>XZ{JeP*M4zHehqob9W_iL&$K1s~TWQ%h3k2 zBJg&$WWJrifR;C6y1JRpH4!bp;HyYCb_n-=$VHTH8fFY>Md&z%U{xGsU|O^fx0v<3 zp$#w_Q3PQZGaonkX36(FO@402<9HHliSUh_R}BYE&VYsI-uXJ^dW48s0g^ZKAo}>m zKlp0!@=DEIp5Ct&eb=~(enB`rTdy=ArroSfDMVzuoaM_B&PBAUyoPGerq%$9G{6@B zu|A@U`_(IRZ9Zq3M#xAGi544VT>-`M#2v7H+`LERbV^8G8^c69u|)2J@x`mCb#&Lu z^ZjqS5VsDDkS$qBdGGu-q|fteSnv~Ye$^+jT9qic&;XF&Puq3Pb;d(RLbKb$B}0Ct&o8Fsvgw>J4Huq1z4%-1Oj?%y;v{TR^kG>(`^?*=ddvXHP> zZ6yZdGfk@qDxHe3LJ=+*?JUnn zwP;^vrnVsy!q8PuZ>EO5`3VllB`y)#;@!3)f)%sfY=B4HMTE#{YBr;~?SOkK!h8%6 z59K8G|N5AG9!a8*r(_z)FU(cGzv>(2=j0xF)DIFQCLQQn626T$_O~yyZL@_e53`-F26>1b~+^Zcy|9Y!$BJ_ z4{v_KZ(yD(v1vb|?(w&6JT0=^w~#tTF}yd|Fa(K3vLgd>v1~S)CF6CPgO51{2^qAY z2&-MhTC+b$OW%`x3$xl5c|TKgl+ItqFDVUrW=F!2X;si8|7pV~qTM6S29E;!YHAT2 z5=`D7LZDk)rpkA*@>_OF9EpOz2$M>(x3kWU|ntQ(g z+@%JiOHa8yWZlR}B-8iL4N*gP)rTU`$WZA4zac;e6?+f3w{aMc3=iR)HOlQ_8#d>n z-Uq%}`J-fx&VkwMpZ5zFCC}--Tmmq^T9vu8Zq6wqQN2MEGwYSPY4u0PrXIO^CADD# zx_L_V*|SZp4yc|1s8}y--1?D|k&=a=ZB)4Q242$`HU?Q&Pj>=YSvFBj$nnN~#KR!J zG7p0W9%`er1}OE7Rq_C@5B+*u%72BjMYvdI@G{?zibJK|eJeH8bLTE?YNx4t>Y{qb zD-ID6s{sRwf8hDc1bp10DsKC>JT~C=t-=~${bSASaus+|FL+R!nO%Vca?M5G)o|OK z*SO3Kx_2vt3f;dae?li<+- z4$K+KcyiyZ?XblK-=_l!WZ%6vwLyQq>A&A%bKjUto6L&sJ4WF9r} zcO4}-hVuv36*5%F(Yr=RjP!YbwEk4-9FfAqMQ}l|w*=~#97xNiCutKHwUSNXq z-ZiEz_DI3$c`0-C@|$`2M{Y*UU$^M~`d?G?4=T_bl0W2A^4O@Vucs3$(#rqJJ86oK z7z^@Uk&mF;B6miFxz1|91bl%ThYzC9MS!C$1M3ZKNcYoe-Fy3oEzio@wf5=s4 zH?ozE?*Lu#Mqq@9R*2kMFOo8VM8x~N2;b)|G$K=E_C_J{Az2G!)xj1@_VO@qW-(u0 zXa4nu%KUaFB}dT&;&$`|)#0iL?r@0T9PEYCFVMH;A(NOY{a=R!dn1`3Mx7Gu$R+UA z!RA9eiBH&MzFT9v=~Om)u}OmQ>zYC+zzB;@WHW&I$OkP^=&crxLI|4Mq(mY*HqS+bE zKFT?`oP;F=AQ|B3!N+tg90As?0`=r=51@ z(TE_z>Ydg}jT-|luy>9%?;L;c?xHLR7!;N)xl(8TjP)fjh%ebZI%1dtyI`d8STgl_ z^ZtLsDMSusm*a@h$C+-oMm4ZStulqnsGY{S`6%Lu#mIQ4N!alIBiUDPk~v!=gB6en zMxyJul0Uf&(V_vB*_9ZMhsK30E?`rB8zJp)A}KtUMj0KTB=1xp<9lq(*U>-^UMvc; zGSOm8Aa>DeAsS0#@>ThlOHg@%iFDT$V)UeWOr8|e6Qt{ETKCR&0wf0ztsKXBMY!9% zlDtnRDIGK7fP&1?w{|&qqj_TgUQlT05Ntl7$;W+Sl5z&FneHmW(f0S1<_$n*txRCW zE89UuF6!2pw+aq91aKk*fk0vL4m|+dqtySQoWwMOlmb+oZ;ivTd6 zcqO@P8-S!x>WeR?+R&7YiO>0X2evq5yxsICtIIg#U_s!`AjS{4sEHl4$?(A8#*cil zOL2LzUn4I zi|;d%fUhnz0m0T%$CU`a`lB~0Z7hE_OMDmJIJitYGWIPMjUvyKHRx2PJI94kYk8T~ zvqXy)ECSqLPRW%+Hy%a=oi)6){9pX2?TMLi)_05I#QtIA30Bw8x2R(MwFpbA)nZq; zmR`cu5L+X{*>jA6?jei+NJ7Ez;k8#nb3MU#$jHu?wCZwx>| z%N(Z*lLR1k5|}U)P;qMB!y#@vF_#J!WzC>sy@gP_4b_Az_@Qf7aU?ReswdrE1wHa3Nk1J{0y~4&`yTnIID~il|~9hSn@(HVA`^^5+^+ z|4+lK6R;B42+aAvoc1BMJM;?w_uTcLYx^Lvk=v$M+2wD;^34OoDy)K&I!bBg)iW5I zcS@Gw6_=xN5sS<@jZHldf0!^$%ka3QVUpd`wb(DBuVgj6R^sA&1(9OPh{OQ?inJ51pcGcsDAU?-r*I{C=Uswe#C9 zqz+v3Ici`Xfed_jY4f3EyehB_J74qc*tWBC)85Ed5RiUF(r*9HO3~7Qfsj5fj-Pdz zWNwnGRYG;0OoaJMx{^v>#(YBdd#N5NAv@xmD<`f(!j|@0;MbK%!wwcXY@WnyZuD?xjkx_>JDU)DaI{MX)xAo+aKe^ooX8r((={CD32999e0WUQ`!?e)>thC znQw|k#ZK#~YSkVqA6ti7F*zg{dE=(4p$-!Z3GvHj4J?AJzD|-nQextJ+`nocD;s#R zE7-OEG{hrU=UxEeI}ll$Z`6{#qRi(}ZySdgzztfu?x}1!xS9MqnPN`j$rX4Xr5pj? zxB@Lf%qx)FdLjf$SX|qnR^yMqONT9~E4%?V^ndjp$O)k5=nX*t_r zI?@d!3-k*hlwaBO1FvDJ=q}<<7NiLrinQqs#Nd-21ipwIR`VbAj+D566kNWm)MabB zIqgHD=Sf%9$sIw4W7lU>e_hAms zCK~7jU2a8I`)r#vmOHeeZYRr&R||%b9j(|92e}~s${FM#D=ccUT$1gd2*kz+_AvDk zNG%V?kOEKj-{#}79TJFMiO?m_P~WPXHbP8ok;&CmTVYlJIDW|1SVRfiZkD7VJ>4;>JeEtxO#I3C2H#a+#a#LtKaVxJ&W6j9`l~ zSZv%S#klpb3A<*x4OR+pe-71>cd#IACzBfm&vx6X5f~&F&sU3#4;oU?x_Fd<;W^A! zMeyPm>guvM!(${LVc6%%GMpo1pe+WW!*j zj)q}MSAuJcL>8Cf6kC%lTH8Eg@goAm+9VKJB({~fp`p0ZO1^SYne7$=ww z%2FnpJyD9i8<_{s;I}i&|CRW?8!lnbs(`B387a4fI6_Rj#mAT~qpnE`UoO8eklOC9 zpCzGkl}%IfP}ELtzDvVhMtqFQYgAYQ39KwWXN&c=NG0(e-)p*!B9cA~2W*9>ydo+) zYY*%7$5`vee67%};5wrEUa*NRAolrGiy_zSGMffIlD##O^qxBN9gEbS44^O#TVe4g zqF}FALYxE!VMi3*45E79)>m~#U{RA^R3_zP><v1gAX1|ASLTmoq7AA4j>yW+v)h+^k}nk$Ll`wn{&bRj6ELyk84Z!t{fpsY$1>F z@|Wq{1fWUYt+*9Eo)W<(exgd;8_MNtXEpuwMIK2W^UC(Zs`U{8isH(b>^*B^J~h*2 zHz=X($bc-g+OEKfv!y@I);-HoMiL!0`*z=l%&*Uc8w!M9kf&{E-}Sl!Uqz><+rkOS zpN;9rDadLz5!=`YnvQZsRWrX`D2-ky zhLIeT*zdNY>-RZop-)ot>~*1CkN7{Ky#xg~W(|B@Np%Z9aDbWkTdz-w8M@+@^C?w0 zNkMz_cTdbh27QI#@((-BvudX|9N)WaXiTRpuYdT%-M8s>GVPzEEqkMwdv1hDl+HU_ z4E;IA6AwlHUt~{8{im=`5Tu7Cb!2}yp9>3hiP0?fz2(gmg?bbtq5`Jn+wWssd-z?R zpPLPqdFpNo26G9WVrLvf$=Q;vC@3HA$Tckq#45IYm}FlvIYj~ zmgqZuVp{={;SLYjDy2Wnfk9^ipe`Exeq);mAX| zYG!2J{x7jWzLhRw8bKEPt7V&HT(t@+F!-(ya_E|=GFMxgRgZs19IulX78$KVuKJ8R znlV!2BCr@Lc+w4Qf0mPSob*YTMnXErOj0}@sOL(J9c4zlygs=|yD!=0!XsNLyTpH6ufk*l4wf5w zeWJScdgx-dV-OX2q7tWv{g+Fu$e+$WbiW`IMOk^@lqPwd;Y~j}RN2N0lHK)A2t!uN zx0kx?8qhCsWW?zDidj2s5mElJH~Cq?i+8&{3bf0Gik5{?4a)q71tZzdC*>=w{qD`K zxaK{sfQhkZG7zze&I|g(|IVzN(005dF~R$2s&<)&?&e68@9kLyYc=VjZ3k61%j0fJ zQ?#ilA;tY7cC6uJh?4W~6P0zWmboJ25rtFm9$g;4@7p875#*fhF?;>BtN%4PNp|Sv zNr^eN!0mt7lPu9Jws59h{?q_VZrmt6zIhZ$8yE_9e0&@2DTONJT?ke?8xu>laaAhq zTiXDHX9q-6aww6!8xn5~C=MP(Ssvs>W(9*5Nk#MXtk5Kw3ZhhrJWs}B?l&rI7Uabf zbtXYad}&PDsJ6=zsee+WVMDu{yS)wL^R0eUGih*_iY1~nZ&V2Mz|GJWllq92Ow1g% zGBApp?~rdpMRI}2rk-|3eEESNsSr@yN8ax6*7Bkv%y|<~l*Uw%bpxV+-dbXsf3WeH zIveAwsiB2xxQ$kU_MdeSB#uLjAb#k=+ZS;thvm$lGZ?RI=G{d@lU7N~+O%HO5_vXDl`m^TELaXHhgRdF{fh%A~N(F}f8cTG9fP+;FC^y+n z#@P8msZwEd0R%ei!S4F{QIC@pVLMQxAvqhIq-96;6;#}Na*AXnQE$iP! z@j+orL@wL*d#_kZt0?lKi@gutUvqhd0bhf?=U$5^*=I!ER20z^QR^IUrrx`H{`p@# zmFLavQRe*HNd6|H4L-L!EgNK;2O%EAF7+>+zeyW2qfj9-Bv~lB3*53btW9zA=LZOIhmG zF9YYO-@jVTk*5r9I9%xPwn(_{SubTi@d?>Awwcda(2spW^d8m(agf_%3?6ztl<6Hb z?)R@7@vQ$UJ%qcPH%)RMb5IrH2;7rPy@#2XL(zXGB`(6E+q5drI~r}I+oQZEOP!Vh zM(a>@{Wc`5@^%W63(w!dATIc_j^Jp@@k~PQm?n}Dlt}9An$aCYt>53 zQ2Q3uSGB|p2T=(bK})^gnk9x|>5f55-JS{zNX$7%&LVHk6`fnD*PGGu*nC31O$4Ga zdxXSf6ljrd<*~tcaU8`#lII)S=`-^eDcS4=gX)KPucxd_UeC=8$cV#BD?VHLyJrVXFcl@@n@dr9RJlY@$e}ueOzX z)of0D=zS0iv#pa`Ry=Z@U%(T)Yo($R|CekQgdZ08KY+8&VGjCCc3qUmkb2}knxa4~ z^V7uGl)2-yyl}TC&7udc4^rc&UfV^2B=6D?9UJNLdHgXz?-#1BczKNj#eOdZ#U;LP zl;!LN!&ffD_VZWE*%Z6&fB$?hHyvF-q*MLFu|y*D$n$j8WETzJyp7@>rEwwo)};yx z{XR5AhdF(AIMhKHI;Dy*c%a_S0^47SiuLxfZgiV{h=hV1+ci9*9~ah02JFpnDfU19 z6;Qoi@E$0J8`OE1@Uco8vgt-9L}7VorlOc#J=+(twERM~_9D^#m2lD77^V3~A<0=; zOV=t<BDOrmx6Y4WeTbYT@kU>aq&49r?1ttrbh<9oj{`u_30gNmIy+bB( z_k`E5jdcvK=1~7XP0IWS-kM*=vRPU%ocJo=Ld6r<+g1WJM{A2mF(B$mG}19=3b$Gj z7H~V0ln$4^-JvSym3ji!NXT3a2=8Nu$#LpaV$XilH}KBxkN!lO0bEq0&Lm~fKASoK zVg9jfU2%3~JZ;%4wuib;f}*z3Kky4t*Ty@=zE|cCZ4(;9S4o}~07512o^Q<-(VAfd zjTi3ZAQ$?nMRcn2xmOqqBFb&d!pBuUH7;oEY_M73C!t$!`92~SFy4w3IxF??xy2A| zOwomR^??tRVBe==xkI`Pj2ozBT&3oyoPH6#qPc%L3dffz4vC?@ADjrFSU@GXO$t-Q z+6=ogMIGw*i$~Zw>Pq`LUI;9d6L<`P{`Z(JAPm4|dyk+D4Z{thSds?cr;Imh2&U+7LCkPLeye5rd@zZA8sp* zZw{tl_{wZn zD|~$wqHRv?@)uYYkg|gi(p1BEh*FrJ`;CuHkgW#SS0b@)K?40(XaEX z_4V^%$)EWxZ+pXRE+%jYNxoW+1;A41GMF6d>cENOxbl_39O`(Eg&^ zD3#`8!zEnDVN$ESL+@&1tJ7Ye{Z_Xjmd2&THBSkfqAz#Ymr{e_jq*_&XbMdCWDKAd0_B9= znxG?7nj=2r(Zwb*HnCgw-s{t3alb}m_C<5PzsgEXyjjb5a;4sLmJO^Kt$`1!y8Oc? zg*9+5IfR-%{%ymH^K2zu8HlC~HS*>V$%*Uo@WNkTK45@Bq926YL*1_S1}OhkYJ zb4yOV3lP$PtxI?Wm7LhZ$GFK6+XaMZCgwVT9&o`Rl@SlMf1cK&O*K@Wl+Yn3{smz4 zgwRDMA&?mjMc9*hm}`9EeOP5CM=`YMk7vLDHs%^k2}lim`Izep!fihK=n*Q6wjPiZ zR>t6yw7}VTM1UH9OHJ(J6Ec|4;uvVRfUt-w=JEB!TEaalv6O}KL}#{o^kA~mgoRwjEkbZWm_ae*=hO5-81vQBAevJVdMskjGoXrAL<@r1 zwEb}jRIlaxZC6qM>;VFJC<``tH2;8^lwqabhiwDlfBD|$)KiBfWG>h~v)@FH3eW&A z^$<2cB(6seoGl0E@KE!ZC0M<@9oc21IgqQ|kWp(mPmZunESgr6Yyf&xE#eopP&ZH} zXw&YJgUUBw#?#1xwlk`t2MNjBj)VeZ@s2$ zdr_HdPTAU(xp(*kv@V+pzDPFXVcRtr3)Z1I@nu14DmHgu{-jhtrSg0#qH-$Z$}9OkM=GK# zs}Dol27ESG9Z9%*Bw=4v)?B?^`;NekGr}C%jx^c&mSh1vuf#{TEvHhtMs`djtE|#@ zmsVA`RMotwI{vdtPOm=cQ+;Ypb$w2CLsj*;mg>eA)lEOE74)N*e2!k;chH=Fw7KeN zYs=A_FOJ^+c~nWSY4@q=SX0xLQ`1vbbFZbQ_eIUapEYXwvAz$W1ywJHe{7)Y*kH@C zS1*nY{XC|jAAhU!IX=4P_*l;I@v7q=TaJHvaeU(EaV`DCR11cuMZD#on5jDPtL4P( zixaXtDHC}C;yUD8MXSq$d|N$CcBnaUCU^#H5;t8_*Khf zoV4~mX&ZaeF88EE^~rgyCmjb*I{iAy!j}@|g}-9zJag;3@0oj?tMeYLTUcEe$T;PB zuP!L|R9Nn*i0V^|TTcbwI~Dco6qj*&x$o(fv8PvktUUa)LW-+gd*$@HkEhq*kCfA^ zj;yI)5PyUhTUA_DpMAI9&*#Yg7HZO_`Yc9eW~*$^*~;$?^>H67(l%AaFRsdoJyXCi z-1b2xzI%pP*sxxQZzwpcw>`GuApYzw-?KYtXDh4EZi_uzy*N`gcvk-Fto$SQr0==P z*mL#wLK~{j6||mf{OHs4>)b}h`AgLcuEd^~dd@40`?EIz`bBD#Cd!I6x@zxyY}LW)*PiAz2dX=#@|`u{k}Lo=7?owC_8HyW{1gKOep zmJJxu75IKOj={T>RSh2Gk$TkVm*JSlOadk4qTk<#7hhD|R_y*MMZd<%)*VD#Wl?VN z!R58kS_LjeUbbs4L{vt!0w9YD;r?mB=%FfE7=Nk0FB9`jN`{%JAY}!i?;tUUYQ@D2 zYl-W)h>G)9x6{E^IYrARBDIt{0p$@BMC&2Uw5@v4aAi2+GT}za6eZ++D|OcahV<&c z)zC&hR?UW+X3TRLnJ`uQEeW!pOSR--XXFGsIr#n%r2Exs#zXjMuD7u%tt>#NV=Vv zY;1uRlPHH++UqdawM9$Wp+$Dz2;~CUD{}G!IaUmy&(?y8umnL1S9U?<3~GP|Q^bSi z@|YcLbXpBMV-}O7h77HtWHm;}#%3~A%cPjCIxQnsgNfxC*lW=lao8Pfll+fE+*px8=S;Ysc1Z@UXC^8kB&70u;DU0;i7x^560t8k0Cq&Hbh&rtuJa z?~`Fo!FM+KBFmNc0rO7qV4O-hPBjZr(7y75b_zg}RrD()m4Yy=rR-kfde4#4 zn|H*XiN4DsS17BI> z+vMcOe8N@vT+1KpRZP-%DUr!;Cg>Gbup-8^_)A)}I~VMuQl3(Y&!m)Az@o;Y;wN|nayil06W5|9Mx;~qCW&tqSYz&E;oP?GJYp?BP^f;qkxO~0 z1`POkSv<;_1ujVf1;_DW&MBfGw4%qY@3}YrOg37R6mpKDeX} zxVaMx^3l(<86`bbcvf^|k>9WpG&uqWqTgu9DH^9CH7T17T-T7*EEJUoQg{dp>fmGc zU_F1(NyXX1M+F1m`7q#~hTO zluZ5;evfd8UjPDyY1F3&g)+%C6CdQIq>~fCV;;Fs@bNo~NY)r{Eyql-21EFSPdox@ z5=_&{i9>4g6c57;KtB_NUK#&85%=kb+UZ!u*aPYE*`1Il;D29wnW7=~E&L2~l!d&{ z1)XROKr)aMCNu=96!lC_ejDX#Cm>9)@u5IZPw_(7p<)t8+>}i9)nGoeh(TNk(S{yn zkv0IJQvQ6K{LN1uQKv?(e@)O=RI-%laRppb)T3hqkJ*${00>_c@8g1*M2Py;mP%g+I0723aziO}}KPWT>_Es&* zV|Y4n*(hfN5UzQa^y#t37vj-ja`h2tsqU+-&9@EfF$GW2Z*)|WW(j3Lj(tR>@N}

Hg_0iWg6wzAZ4?_+3$c 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�P0|oJK>=JGU~S%g+7N(LA?6IhkV6{+F&7*i-NrzmRRU)^>^)Oz-V7jRNCz0e zO*hh}0i;ymJPo7hh$FL%%!Z1~zHVG;arls)HZddjk(=N$Tib{(*cyQ=#%SwddFPmU z>Jkg1IX`}`pM~&|XiiDG@Z5Cm^{iYrPA?YFlKuQ#)_ri)h+Ru zz4uwPa@Q7Ik|5?BTvmOjZWm&6CGO98`#%@F|Li;uZyOcO?JL+mRIoi1-u`&^)#HDz zUHNmp`%l}GKQ})7x%uZ&wxpB>*i&YLH&n74Dzs_$=Q zVynmtnIFH~_STV>T$ps2uU8XV2ze7vJ?AnYT-X|Lrl-e{$)? zsmkA;UxqHEKRUbikJr~1*EUUj9sA?0Qg)Q;xnxmo@kVzOWN`M3&*Uq&0#3PG>Vl0{ zZXO-n^8vooDqg@4YcTWag|s_{`+6vxiaPVY+mFtX9(T+x_}Y7rZ68j7+>~&KHu{aq zj5u4uRBEGkOFP|PD+LHGM(hxcg?o0zveb1MeL=u|yhc=PQLg}XB5Z0p$@Bdm%9$MU z!zA56)OZA$B_6Ky-ln1#m$<#6jF8Pt@>AyPiG!-$$uAT^1ctv(rQfK`y2|&Tv)10;uxk*}(S$@-l&LuUB6l3bYq{T&yyu$zqGBuQwz!_3t<3bZE17Vgh4ywH)zN?mxKgg-(Ir& zpy}4o2cE~3>lZrZ;w@co8+uQKU!READ|ef807EvDu*nbHHj5)Wn6s6M)xLunVo&8{ z0Qp|PkJgcJ|8I;(p!-CVGAy8V5bS#JQPCJ4@J1im!8DwMBnFI!HU@9Dt3`zSHe51d zd{&&`m6+uYM9%xNs)yc1z4F{rXq=-`0gv}IC51Ljv@`L@&g^A#&*tSEAaF4vvsD`N(L%0r_@B^h+Xj%hS?#G%`# z|NM^JHf!^V=I(&+=c~8QSku5?+Pu*Yk{NfzL~o}csD8v)k!NFLJ!^jGzvP{J+HCCH zPuy9w{=%+9Gd2!v+}$-yA)@R{MUa~cmj%dxvxT;El4sphFYCXsr=`u-Ip@T^gzp#j z-keNDXpw=SLJ)C0eqoS?iV?OU8R0Q{I8o%l{`-Rj9#VE8=4=SNXp^(=eqkE^yVWA< zciZCsl*U-Ci_UM@wNFbdV8V=WN{eFu(7=0Nd4FG4+v56V`|jb#_VpF+3Eua$cO`4| zsd)W0u4LNAAr^l(qWHbI*!|(~KkCWwvfSV!--^qE_CKmGdn@sq%{L)<-hEhBFwsaB zu*iobXh(8lC^27SSCXLhSEMv;j{3OZ>YK>z<|*jSu_Di7=PO(*&so~h*?*OY9k@yk zmYZ?t5Nr~C8vhu+K%eK=CD^f9dL)`4VCmv@D%X{%laZW*W!h!5${g;Sb@~0DV$Sq0 zDXm*)`duQ2Y+!9Rk%RG<3J7@;;VuEozHuJWI#gGNNQ7-(Z%{4OUipL3b&en z`E^+LYNPfwD~?`xB5LDlD>bwgh7o9-X%FsELT^qYu)F@s(N$YAS>a-QPV0!7Tw3%f z3=pMB!R)&Vq>f03wDFR)*~cD;!hLqYyjE!Bul&g79$4O-x8V@uG`#ZgcnPkFW@$i2 ze=;whtv_Q8-Q4&E74!_hu@E$VE$Oz3Qn_UejF@(pL7sR^B%3BT*`*cY?5o(1D{X?l zaZ7y)vO#h$?EJ>eWwA)h$T&};uO5Lh+frF5BN~tLV<;G;F|{|gzClj?UAt8;sc1A( zfFe(eJbEO+isljH%vb?gL@uWGGPUItj7^lnUu&@s#xSdNfA4sOMKRCW0y-bXq3)%T z5k^n=n1IZ2gp+@x`6njESMj0}e5<@!eZM{PuY{NO&14dCXhDjP3KVKJTtv#7M~mat>de(BImiuW zkEATE4LmEVhlDbT4IwPJBry9Y#2&hhLPd)~^In#2LJw#YO2*Eg0U*U-@9yD;#QcF| zldaOC_|qT9ZYg18KT~8eBf)QqmJ>igOFi&H zqvbM~+VYT?B5l`|Nrl*d?SjQGK$Cnrh-o2L@h6i^-$>-%edkN|K74=IX0OQnEDdaN z*Unb@P1B4%fREYp6!Fc>(HL zODOGn2lWSKp8rW#Ok1{u_(Iz4E4^N_ZU{6Um(8>M)KLAUANKOskQpbLq8~34ym0&F z=KP1+^Aw3#f>yiXSLyZ@uoW>zvYFf`EuH%|k!A{Pby**Yb(diW*7tNehqpsk9#|+a zk8SZ!jIkP!5%XzsvngqD0B{83(fl`agR*jN;V{a6bj#XWCNwG2tJ6$ulxy>WTkSH0 z2?xgqFj586Us9PV`=nZkZ3A>>*dP&w*9fJ`;GALrL!n{lO57`vahVK_EU|R#V&(dtL!R(c# z`TGX*ua*{fhyN~<1#Vy?>nypabpd}EWpl*J`3PzTw4Q5a6LQ2R+{!lQi0uk1yR;*A zSx4-MGOIi}P4`InTPDN}VhjX1HyxRM*2=Nvh~rJGIbBEQ^jSF#9&vhQ<^0~t@gLji zRgqgXn_Qkm-Y+xupmc)WmjmUV}&MnrA7EVxXPwr#` zlW4;-Y+{+)1U+jgndSxh1{)I_$oq^aI}$VfE$0WMu*11%yVmsiPdKZ+pMim(RE9lzn`NoY6#1gtV0m*zI<6}^jHG0=VV z=<>N;%P!nre$zHyD2|1P$ZkKA9hK{#OOFNd9WtX0NEPbomE<2_J~k7a$eTQ_xV-9I}%bevDL&o(@sZEnBb?)Z9V`y8+1 zIdkndgdE@Ss>8HS<{59Fn|3@m%YIYt@l9Lp^F+td?{k^ z!Y!GDA-%ws$l@)w2$y0ZHE$j}R_0y@Qs*E%W7%fKOsKNMxlEvy$27;w>e&M`6!c zxeiYe$bnrYLR+QGEfcf_tT??)l0@i9u?X>Ba#kwzZe0>yH*5dNSqC&z|6e-12Rgv% z6Ksj>P>4Xg6JP-+CAy!y<`nHoLV=-d56#?!^7TSA!m*BR!FLF&hb{0z4~CF7!_>l~ zARx$_AtYAHvRY+SWrxQA+v?Klh^Os7Sh=1;!WuHt?|p18p2hZOdJVD7;C4;E^-%`G zqgZYgpEgG!r!$y`L_$WGnsq|A6X`DxsO%tdz$(j6>`B19llN>6^xec_D z$!hl$qcXs^mqCBZLWm!S0_1)#kf?ZQDy4cVwycC7F;ywsGznuSk^Z4Ur=T-a8$L2C zVT_ElrCkPH68NHogiIN(`4JX7hS{*Hs8EJ_0jK{E{PaMfiXF|wpBXaDb%SAo!RiE`;+6I$}jB62pzGUF)d@pSKlOb{wxIgr_Lyf z;DHV`Y%DQ3O6;79$KKd7Z>S>z{L5B2pJrb=6VNtxY?9R(e%hJB9LU1pGe~QPVNBk< zt|Io$RSzD&Sab8A?9I;1;2s!PDAO8{P2!8S%bY&wa=vECw1$HG!a4-;&NHaD-*adE zY?EaqlCY;U>m zK*o!r!p>pzWRO2&?1V{?r-bbrDmSachb_}AO>(anEc`T!0UP*Mf=V<3Plp{dgd08O zrcwnivxrd-o)8G1-84Y)S zBvHq$WU_B==UtcrZRWVcH0%+UZIE1y;($J4wtGEmGZ)?$Ec9lqH=iCiYf517=G#!Q z5T4DD2=@fAaIq|pc%d0rrbh$4GmFqO*|c*nLp(VezcS7SaA}p(_1nkqAUxwWEuqXM z%G($z!h`Xh9wu~;2JlR*AF#mpwA}cG1tCQZfPQP$@iMzSp)YmBp8n9qB5sWBd-BAJ&%dDLHamc^6p_Xn$K`nFx`vgRIl_~UVlG}J-+?wQ!IYOTvwnY;YJ%jYK z0kL0-0w^HV`9rVQ)2p^!eH)GU7nptN@OmMi6%P;xjCE{;jN(epR4Or#=o`vLikZ4) zto30m|4bo4EF(xkH-CY;CkKpRs>yK0j}BBA(^(?7_mCM!gT8G+`g0J*92ODPt_u|T zQrHw~`%WFeqlfK~381EpXfs0R6muLlsIgZXr?^A$AQh@$Tn6GT;NjoH_Qiu?D{-hv zVAhaF_|ikB`kL$==ztPv$08^h82wCyj{na%Drq;bBkI9mV3^!rAwcw~J2cA-sZORB z%OZpUreQ+wApu7A%;S=Q|89NFDq>Ivn2ABR%yxh&_lG-B9@E|c66$>HqPT37M~dg= zktSio3|99q&$$jDZl>KUnyr%pTj=MKs4Y%SaMC??1qk|!gNV(5{y;1KMc$6jx3&Ed z*m$9Lo)s11?U){B z{4eBz>pt;(z`0oH$GxOe=tLSX^8XS)tZ35#jBga$Y4o2Qs`GoDlt#AtsMZFb_Ql|Wdkb=4bJwDt;5*ju$ z*XL1tz6Eby$@adqipYWK140+FOh5ClnG|qo6S^=p2Yq7G!!-Z0?D96be=pmt48~>% zJO<=mw2o7@9J2qGfd^=Oth(KbCiiJ()BKsG?_}cJa%cGFg)6rOQh{re(7#OX8VcwP z1^F}C&Z9-n%AnbAr(4`r4P7v{331X`?lcFfW}!Lod-jlr7WQk~0=MPv+!ZKRl(zhi zT!t-_d(D8%*ymI0KxF*5%=mLEq25;8HNJA69kHmA;=_Z%}>wEU}48M1xI44}q zeE3UjT?!$Id}*!EuC)79eDVF~sWL4$zQF2jx&0WcddauUhCH3{dOe+-Udu;EI*#?y z>?mA* z>X#-)CaT)0+03CO1EetHc?WY*4FH6%efY65b#%OICoyrfy|$qCs=SiI<3oqykWWR2 zl3Qp%e1?klBwlY0`2J944_zQCG4R@TJf-88uxQJl05s3;U~$x4#7Ztjq#J+rxM|>A z+V8`(&S}d7>&Op_u;{*^94Gm2Y4?Fpexm)skT!jLv!p(naBtitrmS|uvFv2y{M@Sm z=3e1d%mGQNw4gF&^*&H<)tB+3B|85aWV<%j?~`PoW04XjA1=B9>#py6+Eu@C>mB7` zbBDxq^L01(rBf1uZ}93i4&h37VlQ!znQyo@|AXlQIy_vp`fFs>!8jZBd&$0h1*?0P z`2sR~e?jlZu7hj7m(d6rUs?7C3kF@AQ2mcjr5#)}N1`;`Qn^f7p76;BW0v!&9Su?Q zyi0V4EX{k#LR_f#Z1Yho^KwNyo|1-KH!3$-k8KaMk!{a8Eu_BJK&$6AAR>cpOee_m zJaaFXZ9F|f9ce9fYat`YvL9?#j=g^UHYvaihZy_j&-Mm7^9I_l@sZS0|)zpufIY)w4aIG$HN!L-k#mdE5eK0b+6Gvod3}c zC@2BpD9}ZxvqX{YO8ia-^qhW$a9avY*rU2l&k|r9>v>SRG?>1;2D>1Pm2_k?LT8GG zo4rqlKP;C2ZlP!;hw@H z9txG?3V5Z#+>aJXa4_j@1(Y-88vb5N8yVTyNHHm0JNpns9T@>C| zfo-yZ7$0e<_9PkkO&O#e1uVD|9&^%&Auxy*BW8!YgE6BlXmH+aJ)RP||L=aB0}S@A zkfHI~EJy<#)yix^8Mn$ZgAIzwTpBjQf%TnZP^3#9Mjc(d7MZBY{a>1zt?$4h=iL^V zM=O!{be2GFQb$S(4dou3HK&+W{9sEJs6YC(IbMulM*<*)adyqx3WTVA3fUBJE-U|Y zoO1g~lzz`wd!b_N^@o2Wm%BX?m^|5v&+B8Zzxa`L!cAJXNi5eDO*~t3DlS2*o`$vJ zJ8#hLUHKpKIPR!SuM8GwAKP@b{V#pn-#@&?YnP%U@`^AF+IjK|1SPZSOSQm%Hnok3 z7CPz_U$_CIIKUWSe^H>f#7O^G^~es{Uu?$zTsUkbM6-m%#ef!hny zA}xLHt%!-;zt8`g8GFQNR>nlfp85}rrQ>~S{+s+4GbAf;e>6*Dl+>gTf?f7MtTH#l zzxtdp-{sojRf?bt`-2~iuQR*$PfZ?YnXQhjezuE_ZBLtjaP<cK-!o z_kVdxOaOfiEhy)I=WX*?#uWO-#WcA@UP`vs(1OnYchAYCvUa^IAcLRzKWRZTdmB@U z6<7ne`cb4vMs>F9aHw%VdT+0z@W>1k(4zCyFri$(DfCL-#HW|0OV4(mbhkVisz-cQr>Ax3 zU|j8RVwwN9CRXUesH}xIt;-i&^7l?nI9kW9Fu&37w(u5w?(UZMv=<)k_GC`-2dof> zwJ3T!&1036`j0meV0ya{|A^#stF~aV&%~p^1A*z<2{}JT@A#}-H>(ke!m4GZ>eqnq zx9q{D`KMMs*m@-A>It8V=Jty!gn*(yz>0k8nS&NbrF-AhfZ0;zp^RqZh|heQUT!95 zZ89#_|NV|v;`BpSl3RPPOlQt_C+_N=+XwS$k&m?XK{U%Soh%GO*e|=57wj+H+aiitRj6`O{ra zXS*OqY?@DBmAdz0aam>ZseSRd!o65-8bOO*-Wsa`|+|YX3(}ncV*CknWeV z^GWsx&%D(X7;?k+)DVwUY#nC_pQy;3aVHqt>(k?!Z_PZ?2GXM^?qA;d zgSX(y>$%<;SIJLiAKgXaJUXRmsb?m%P3C^dZgt#qXRz?k)SqvGXPZERnT)!u43ycn zNv{y*QpfLqTkAc(-K9yOq02>ETD930O^rXaIFR;omDR$d8X-qKXo`3|aPbZ@jibTS zjr3UWu*6Rbsy&G)oqGYT8HHyLGd{eSdItWTh48rfN#d1DwLY`yyrHxD{=uX8Y?095 zLhN@G(S*BR+t^md$I#`McHW(xoTm}Z9(cP4a?W(ncJAqYy1?R<;E*t_x4_eRTJoP zHXHVMHohaTw=uORs48csk@^m{Mk7}3$QpfvEC_3|GMW`^z-5YUYc(TTvYrLYzwg}U zW?~aub|<{Rt?B^&sKdPZccb=yuWb%HI(zAzyD`_k@0L~`os&NQ-f~|R$^t-OSI~v! zv_6!Zq?hTXLX@`hKs_md4K2DyVodCt-MZcBcz!3j=Z(>=A+%|xUX5-q%k*{+Ojjeo zRm>5bmvJZ*Rzt|HXB$=zl;QjzR;-+UbLb`+q7{hM;j4dN6uF53uK^uWzGTF7bDeDL ztgf!{jGeinPWBgt1SzbBtxLz>JD+4#Coq`%)za4-%bkKXoyq2$qIv#bYw%8G!A-Qw zukmU0{%y~qa}j?bUn&co+No(TDvzK#zmo`DnYXN51W=T)^G>}G&rksL5ckofO2bb> zC8(y4MOqpfhpq#Q!{b4k%8&7+B-mKPAUB_BMCnei{_e0Hfzbq#b<18Y`g!t-vA@i8 zWI6~Kl{L&I!v-34B6_R1BE6}B`Aya3(H2?7$y1(Cio3j8YAP(7X-=fr+r9~v#Qx@4s7qrlbIcAZ%CC6gBrmdy=Ay6i`hu9=US}XyJpU< z0=AhP8{+vkv2qqo^8}oy=hlh9*4+jp2b(e8Evo2^z#{WcbPhckKrF3nxnwC7db}DZ ztWjuyI#pQI&f!yCoitD+75RBBtWM$4P&ukD@BN8Yc|4(GZxugBJ)0E#GMS1?h3PMv z;HdjJ>RPc-L%YFKqod7tGT4s&mQ`-8Jd9!JGME4Gnm7{uAN(#t7Flc4=?;cYKGSWq zrmRZ=1MSV-^jHOwSJ&9XsgoPV!swJsY~35R(mmGimLBYXLuv=+x%V-FRU3zv6Bq_w zcoo7Oa8f5L;Zm}OQKi{16RpZvm5*GLON*=qmy0 zM@5G=URJenprL{+4vsOV33ba7jX&je(JNVaUJtT|tm9T=nq2gsYUBObDSEL0hiPgz zRW*xTb01ge7yo`SX8glgv)M^|t3ixwY>iObnDB;)DZ+te0;hdud{!c@rS4|eG7yU; zAcdr)(lG1>?r_LyK>8<*@ zr`k^9IlF|m0YNk!w>Nu`A#{m<5Bv3Mq%vi4cjfsCqc&iXoX4gs+wt_Wqo`!5$UBrJ zvZwP5el&Mk6w^xMrUlIp-+w!zJ`eMH8!xj}Y^V21LeM}_aEX{V8q5mIc20)In>YZGH`8ws*>P$d3R zf*2r+6|IidU&|nCxFV?xGK-5e;)_Nvh@2$|Ow1;=1W8euQJC{V zj*eNakzTY(g(iujZZs}ukm(Y%Mwz^EI%XXcmn&PhW!PRQB3kzs5oT{Ak&Qi}v3}5Z9Q5Td8CXicLl*U662Jg1-D=F~5hXCkNSQmc2vBb$d0&HBZpttw}Zj_FQ-P!1!Q?FdU5 zLQSjwu4jhrB7Ef$6yjwXT0{gLa~&WKiUGZy;CPP)-GOA*fJqGq6K1LNFmjwxT@YHy zXH`L{@-=!pw4YWD4p+U-^N4T*$twI06=`%-SJURKP?1vTC>JqE7o+mIm@OI#gj$0B z(G#FS^)B!Nkt*mIVCvNc4e<%tAn3xwY|#)u$Y|$CWV#fiW@0yqQCh1s>UIs0gS3SX z!L6kxzP&OWE1ww$pYQDPO$PNe9uUB7SEQPXP!BXFLLsCTOG%Uh$Lm zjKyNYfe(PI1YsbKchx-g@N02=lf5y-Njhl)z(-&~jtc)-MjYo7MG8MRnYLO<{3<4P zD1d*v+>?wg9e4Vr+}UH!b}-=W%$~2WL93is;XiV<4Hb0-aFhbTBL$d{W@9QD+tLlq zmY|cRXbuck{i=Jy1CZmI%j!RzjKnn3dd=uP4gXcG!q@Y(dzBcf7*R(fzGP}Q^6}SH zKxu5SPJcOr?dyG>b>UZ;<>CYUS$mxjNKm3ZraT~@ zoXOzp4q)RlZIW71cl=1xY#Q{PPtd<&7Ik&mCIEpX<9+)9$QUEWfv&2f-xYWo9rT2W zgLJfp85|E+jxkAx#hQEulEc$}0uv`?cvOC4WYXcS&%7^EnhQs4Y*y{uB0~nzQ5;^G zFAs4;L2RKTSTaODm-GridNZ{WPa-GxfoK(eim&D2i=QMP_Mz=oCucHp(J?o zz^e*kuN1X^7K+9L-e1$6twaVeQI2AuTtx!o+T+@8$)FD`ev9AB$Jf zVtf>cRVw@!6>)%x*!7V(rXtDtTaEen4`fpMDSR3i>8#b79!G#xg{bJmeqiZVmb32n z%U0!Jer{MFU5<8IC45%2Tm*DiA z0D>h2DX#vV8RW?|w@3}9>|wnqH;3mv(ofgtkNsJk(O}rUzVSfQaRh!IOM)KVprgDl z)BR#-tEsKY%DX!Kex3@ykxaJJAe)#7TWPyqd`E~SXeZXXA=SRdhxEjzWR~G$8lFNA zr>pRND%po&+@OZZ!q+kYqU`)TXX$XCh>0;t#OF%P_*-(d4(XwyS<_gI0feix@Kf(1 zgTgxF!*mlwLr`-cgqWZN)07yNjP|8){?}e}{WtL+rP@20_+be?#m=dzC~dVDV5>|U zgnP2pAt+;R`ldbUo4t}5Iw%1|d;kyzd87d)5W&a)kmBWHjovidg3xby#@zbv(k4w& zs|0syu>b5-^Lei|mvh%#g}}Bh;zr4%3o_z$8St?nbBs*rk>Gpi_$vy82OsK|oO%u8 zITB>V%f83}6N4jJUs~|r?0{SJbsEObBi_2zcEEFO4|o0s6&EyQB9TT&A3g%Y;9FTD z!cTs%3Upv*cRd2j0PR;aQl=QWh46UWeWTU$^;d>zZJ}!qODsTI<^w*|rXW72+0RWU z9;adM1EhDk8@>bhbOryu0&SIqOyd)t!#ExjrNh^nq!aQ~;5wz&CN=NWX#h)8g3q}c zW-0Pa)x61H&yw9v=g%XGA>uX+J_bgtqZ7U<2(Ou>Y+bKuCTWa5a4#(UBa<}Wm-JDx z;?M^TCx-Y~rlrM%G_x^pRQMrj!M9S4RtoPHM_`M4aQ>&i+}Hp3nR|_n@8oGtsCJwb zQJapvBgNm4XkX((CQPUUz<1K?cXGq%3hkR@`~x2TjPwa;x%-NSFe$}S_=q^A_8l7G zo=iKFkJPYkx@m;_0DhAUjH532==Wx!&U=;;(Bi+>QUV4 zfeSxLxh24ICPE(&ZVE-D_aVGvkvNSJQ%wW4R9mubA%8I-*}Ndp`Td0+@qM8T`H*WB z9Oyz`{qKd5FI(#5Mf(lTT;A>P5s6v0R-E z&3;L?IzBZB%9r&mTR(43gx1RpxGE@>tm6!zYq{7+uDMR^m##xby?nwt1#k)=+@)b| zWs#{guMb?TRn(Z(*0Dks@s|{XlHez0M6noQr$jV-HAOZcHuDp=^AMFGi`M+Q{%Na~ zl26)52fCHS_ew004sGQ@ZW8n+6)6JVWi+Lg%flq|%cZqn4SiH&kt#7CoRvq>=jQg)Nt)XK>%gEJ-gF^6zw_{Yv~V6`rls`l%uuB2P>y zh@ZsIzGyC2l;f#86JHRd@ao?860Djz8B#s@N`(tMOze>%CuQl2BYya;`_W|iTEW9V z2S`D$e*|xv{7Ig2ttO2qu+dWDCm5gTv#^D6t*&p18>z~o16>MYT*Ob(7OhP#Q-6rW z?Q}GM4Swan1tBUB z#GiZ&ikJGHd8G~h@rk$Ml@g~V%Nv2Qia2-`hz8)w(dL>rw`EmsUOU?AqTVgo1Ld^! zTI$&K?(tmvs&n7s51mxwkP!L7IG?d?-m82@R==34kQx?^jWKyQ{AtnP3NFzT1K5Mb0~DVKNPxB}$lUxJy@-c*ZOPJuk)Bs-uSq zd?_p4IJ7;{MrgMBd5Kw^9qnYPUwwAEd1fAB_oi(GHj0e#xfoZgfha)*-cF=Ac?a7jnXvzbB8#Fh8ASF5W=0k z)4d0(f0$0OaBH>Rm)>?F{XqIw7LSVI2_xTvLmv~cm;(#vN!EyK1A``Uc{m#mMNnti%tGq$T_MC6*uBNfKQ$HTDsjM`~VR9VEqVnByg0ImN zJ8t(J@J_7VkgfYcRy%u(mf3EB$BSRf<@I*z4QIO!|%X?SOt=zt;?}V_suYQ(Qfa#|t^^L3l zyOH7hh0jTq`ZIYs7}6`B6(;@v-ceB8tIIvi_e(o6%|C3|<@mbI|60q~^SM8oH|N?J zUCT}Po%x)I>=5C|v_kCnZ=Cg5y%21tWg>F@JGYZ}Wk9=Z-@1F!IyE*aDCqmexAw4N zI_MYv^OHauujx91$Hv<`rB`pC>rFeEa*V>#TcCa7FZ*Rq|KCl5 zjd${CCIq|qpUOb$a$b8r=#~B-HA7K+$^S{s07TQ{OB0B;u|FT6kXuwiL>9d+J_Geo z^n7QS*Q}bX`!muG`WQ_^A2S*Vs-oYA^@z^;9D1qeaN$U$vHvR8efN~CxXGJI-Hr2 zBCQ8`dVQW-#q;KSJ`W8^bBW<_?|D4jptcTo&n#0#V3&tYEMh$B$<}S$5wyR;z!`O3 zW;al5-RGKCEN-_N@K{V>JOrYG$hF0qjNVAz#3J+F%hOhjA{%a*_bcEC3mZYdc*?W3 zo{VyQ%9~i^cH-Jj6dpBjq-yUi@}I>E!VNQ3?MT+X|3^s!KIDHbX?XR2Cuv~ov?V=o z#a%-E7eB+T|LJEiTlc^4Gtkum|1Sj%55>5}OmK&_4w)6^cg=&@wMD5IyHD^1M*6(M zk{kQH`8M2zj4@5>(Ep0QLdaZTl{qtBI1A5)=nr&|pj_jDnw8MzDng!p0aoedg$er{ zOnAfLzN>3%7W&<_887nwNbl#kPxgk%0)NtyIaX6&UFZ2eg2@$h1|9N~!2hki`+jO7 z|Nj8Kn+|D934|UHl>>zZ1Zip_CEH>PTfQij;Quwk^s&mS95|B3Br(WdetA}(v5MHPQDNh44h#Y8n_Tu?1 zq>Unt*Nlvg5mv6w86M@uKszbz2F1qCSwG%;snZ*n7cvl}xzq$}cft!#hVnUG#AA%? zG9euTf}=Ti;V~JJiforPq9M|jE44%0_JSx~#xo__0NNqN66>ylNqI5RjwV>8I`leg>j0o$ zzgMo#T_gRGY*+n=@jM?4gFaDhjN8h}4}e8#3q1gmhDv@&Ju~yMNs1uj9mdSNT>pM^ zhK(dZk?&97(6;FSDk;enXC6{AUYw!ISV zrrYnGY|&You6kLl%d?urvf>dF)XPDPOl%}A&8bNQ^ae~?ZSL@iF|TAAe|!vHkhqse zm~(@!R*iK^DFtbsvf@8i^{czXnURbjr5yt}JASD;XCjb_<+eNRO9qS-PmT1BOo$ex z@Ch-)Ww=^d*$fjX&Hr+_rZKeCF;#^W@TyhAAUBihsf_z&M25dD@6fm7@kaXsixnnE zmUnt=*gZOpG}IzU!^cUaff@ZB(m<;I7HNkqi$ z=aE84G!dXXBur6yUa~fW&7i&Y#sTwYg5+Kv`BgOz!$cMpd$!?kJEfWr-oURDwjC6A zxmsZ4;QD>HfPKPFD;$h+6SZksNJ{NWxGDD1vnTo|GW408R4t^n2;!zY6$J1M@7gvw z5?YP!HV~Iz*rdoV`+z770^k2o8aku?MQIRlVnLsK?(54&&zz z6USB!pmowj7`t9sCUy5oJyEE$^$18Zj1cL(kSsB%G_*GV=uCgZ&(LRguO!?R>)Ad} zL+Dbx)~=!xvjpf3O@iCWX$(p~$JOE*7r!AQ5WS9zci2#x872wR_(8P9DXKTf0R8T_ zEYaopB9+~k;Zl96D9v!|EG2dMmY&(d)UbB8YuubviiZGWevb=Snw9s4{3W9N#lppV zJ5z(%;zvk5g=5VWi^F*Z+JD3u$K7^eGwOLxdCnq?i+S}Q6Ixf8u6TI6H zy{#Iz@NErw5yaF8oDHb(Ec3l~2~3@uXCHeGnWr|pX}|N%biB(lqllzR=Zo-;^*6}T z^2N9|Q6|Jsroy`AFGxU|V*ZEKR@jG?$dNUo9~A@=Y|LOxEr3DXFZdXU!_Mw`O;W(d2TOXLU* zb|8yohe>pAF9cjfr<&nu8tU4dO ztd`{>L2F8ZYntlQSi>x?$)-&x7{MAKbp&get&(-^Eri6wlUM_gePl7{gkTLox&o0J z0JL-zC7r?=%KCw}=B)c}-~w*0Aq)k)$nn^zTKzZH(2mN}73mr>!BgW@T?SYlnnZ4J zDjx-CqP#n?pf9ppTxwt_imGYesluM4;DeK1gD8L5;;^H31 zgQ$@#y$rYRB2v+OKg&-Ea(I?7^Fn`yx=gEC-V!Vm#xfB zMw6J}%I-v#092NOZ95808g`_`6{W5!-2H2j!?FW8?}}EG74f@^3havybZhR9D~>5H zF1b@2Co4L#r|6ItqAfu4xPNO4J|&smpS6WEJ_j4)zG@4niudb5e`^bmOVYx=(-v;p zAO6kfaDUw4UW8rXsKrS>YYX=kdk*FN@Ne3J{n1C$-~#`%w(#K{_1&@~(T|T}d$}M|J`OC`fO3EEt%AF8x;e9z%``BWK zV=lhO?83{ZwFS??@3e(A@fF@B6}~MMe(p#1FVnHtDmWagvca-)eSBq?Pvy!;Eu+UY zx>se!p33cm#awN5ORp+4+AX}K%3loa>`~3IFWKQxJ@mRtXT0i=-O8;c)jul+i@3Er zGEJ+~9BNWFRv)N^3bSew#;S_LtM|RHDeSK~Vp_{rE(xlsDI2U7wAAh?u0^PY%~`d_ zdTP%M)}D;7E7q>#jn$qVtm{gxJ62NLQmZN)tLq4dMT0dJ;Wce7^@Z{El0oC{lDgA9 z^u4~vZ*A*tUiCR{$5t~pu>rz@oI5PO^-wU<*ehwgC|_ak3Vjy&Ut@)%;DsN zu@e*FIsx%eqOVHu`^pb3C*L^;cPOC^p?r2Y%8;}IPRk32JQ|;AewG(rS}RY;NvaG8Nm?5>t0{S*3WjRvpj3$I2HO=T=sg}&?g9g)_JOH|2n|#0 z!oEOM;}uYkprR{BU6-&t8QuGcU~!5Po=s(q;ajxB@=8sm4O;m=Lu_ z3RGpcIBB9)xN(*v=o!eQs)VE_P}>O*(uK%ELNuWi6(*QBpBxap!eud zJES<9j~I_%A@(=yLhm>10?kP`kw+NeP~zG6jq;8#MrRTew}3apii7(ZBBUuGy!}T^ zoE&$v6q_i(vLjL37@g^CybDiL!3FF26tTci!?S>Xi1>;_8HGtmq-fv-vbgw%K-8HI zB#1`S{*7Ijm}VCkKTom?a@tII@zRLuX>-AH+FW2-PMQnMU6ba5a2H}O^d4+}DyPah z)M1FYy&FnoQ|{(a#-&QbEX;AlJKcz{TyxFF44 z5kb7nyd`81kuC>qnA8gpk3r9A(;$=RYILLa+I2UX$KqA zeFI1XDCdN@Ar2`7ph!4?m9zlS7FGok%M_M~Bcr>TY9yrO0FLz#OZoK+S8x8|Rh$H|-=+eapw&4!!Q~*65 zY#Kr>KwCqmsf8Eq(CTSwflWiG1^pekuhat9AH!U=M)m+UpQ09^sB298C&$m!LgS~Q z1)9Uoc|J`DwE(n!M=dZPPErf}hwJV=tZT>Zv;ue>>U};jp6hTyFmm5*l3Mt2yy`Qx zzyY-ly%B05-H-MiwIF}=`fq9>J#jR?Xo^~3ugd$DT42i2k&hn;9wXGk05FDh>yEGq zm0PK8QXp@f9w((7TaIZGQ5w0b27Fu(5}70UK{R|bh(q2roO}M-j*E#@tr?!d_ektb z+mQtS(P@&)2jH6g>5U_3EjGdL`NJ*|u73g8BBfjeKpzfyO#TcNfNDnW%f_VpRUw-L z_5DbYK;?_GFnSgD*;x=yISV|Wa4ZmHfkr|v;f~t^`zeem zWC1q)7FlTh9$A?E4YEL*m_!z&rP!|_J3^8Rq$TULLPF3KpE3(@Y^UlGP% z`E9`0A;L+VX ztpBPlIOO~jS@`2CvcR9=igUid>&|%e)8$P`n2vepvbXxStcU6GQrfjY92^ZXNw`nf z(;@#yv%>h}_HLF@iU(d}rLqGfvHgnPB?-;|-EZf(s4qqI`a5KSG9nYwrjP~rpU6VX zx5&bk(bGF)5M<%O;#bu0JI^_U$8$iuv5<+Ty1*>0cs>iK%dIZW!0ALJ=PvXOWeb0c zDl$Biw2=v3)(V~o!D+b-rl!%ty-Q1RYeSRU(wQ&0Xy(nmm9<(syc@8jrd0;uYq2=mumjlmew<lk!v)^vV?rGl`28Lg98uX^L0C?4@7Gy19(32i@{Kt-N?;hSQen zm6@J!TmDDF5}Vbl*KV>|txeMtWEzF%bNGY#*Mqo449M4i=ZS|4*ieXV4> z|N5AF@kckky%%>dx5+$y{L9?6{!8$cnw)gaVESt9&>}c+&!T~Pq8yy--TcIeVOywC>mRbBy?Bc8eC?Cs!lTmGpnL^dob4yVu^xnYqasoGY6&r2bg zaLz||8e}?suPyk!BoGA0ba3z0&%fd*bsp2f@^%S#-uBO25Pj!cZDIY}^f~ghgJ`$; z7h?T3i5Rc@&Mu}0q%MfKw_&KWl`$bhA6l5OX75^kxB4>8i8zk<&ov)}wVFp_U*5*= zOX$0xxVUTl<<*$P_$MJp&0d|neo3+D5C3~>R2ye(9na?BdJi zqD93XDQDvuZ_Jxn4Q!#pY!+t~#dzl7q}Ytq<{qii>WM1r&}6(FTTJf~(CEfuC_0u! z3KX`kuHa`bGG<%H24HD!OnPsTGeM}D;#iN?R__v1lHE)UR@}%6V!7J%3Q!o&ApQJi zp0cL^B|gM4)bC1lIu^jI8hw=ISIWXOjSiSzypiXJtQG1IfGC$By(~VDKEglB$dMVi zOt|XbW#w872k`H;lskt`VhW;M|C%6Uo5%dfRnFA4a`8rWOMp{9LsK~xWz`y)i655f zN#RVV-@qJ|h@Xs|cH^!3h3P824Q9n>D(2pZ%nkQM>y6MW;8AflDwMT9BUGgKpp+7Z zY14hIkp2W~wWh{&rRsO{Q)wHx%kaL`l)>{sjv|%3ZNebkyopSBq#+yY4QfewBYGnZ zWN?aDKs#u*UX4EEtpCD0LpzI&{#3j&6?I|A?idS8zyR7~ZZ1v_SwAK`I1R?_cC}$} zQoYW!X&hk9W)Z{Llf=UCd1crzzCF@2h&0=TId1^`F(BeMV4Zh{E6bEjcqLMQZdb$H zEYXdvU#0h4+OEbKgNm<3W!YQ^TM^>`tnZpZ#{g;b{{pX;YP_yqw&c-IYX^}T!g3`> zXy|+W@YQA`H%POXJOdr)J=-|Wn%Am3ZAmiTDQ#MfNnbE|z>AIW@B|CFyJGd@9_JmN zLnq=kxf&TWF@cwz^<{wTe1J6&3-t+4^Qr^;0!TB2%;lG&MmNsbBhu|+kRzmGD)f%( zd4YjnjI`5c?8WRmWYKu)=b-R~sTXjM0j#kvfwDo)Acm{qUAP6253~H{QA?j#PNgoOP^_q z@V~Ehe%{G^ODPh~vX=i^(ll35`{d%YpM_GRL!wbVcpmnOg}m{g?B*74vGkySmUB*k zYW`ew2AT&d3wT<$i#p~vM&?DH57K*E)Vb(QBtO|{qoHPTm#gjeg1x8CZ#2LQp!-ab z{kn)#B=rZn*LN>HaQ6Hr^O!qV{Bb*q+P~2jbjhK9GqlSOKH2x<2x@Ng1I>$~9Y z`(Po~JFuZ{)8Pd#`UWqa?szSUSbx#`(ctaC-)+v7EV=Km`FmfJZS<)a8Uz@=!(cIh z0+{pv&WHW)!hb(Y_rJ6H@6SpLNCc_K>>)r%1r&4XZhXP+B!Y_mx-M?Osrg3HjJr2Z zmbGX7<+*9VFcN<$pRwqDxw_YbOMFAu13BZ{4BYo-xVztdq*~#gXB{?}v*+~8<%f-y zz4^Fo-~Fy!M{m`cWBFCx6^phlOmF$q#5HYI-A{|N_Sg2+urEcE&F0ly7p_=7%ae|N z)OVKqhY^YJ^+nJEc7a9!{p&oWfTfEHkg4vLAoGxzP0}I&Y{lqWkwylPJim>htFc?x ze!HtU&%|D17%;ihPzdLLQVsZtlSjAwsZ7@-NNkbxDMk5p=QeR3-8ORUm5Fe+McKzP z_ql}uu2++gamvdzQB@-B`S;8867ninC!4MkHFw)QWD41xFU&TdtyDK=dh;2jea~(S zx0l~s@PV3JZ4-K=p4-s>WRWlsO{x^$^_xqNE zr@+hEqc;^x{SG8J5lY+-&Iatk#OBfQz9-KKFHkNSs!1_6%$ E2Y4k_M*si- literal 0 HcmV?d00001 diff --git a/dropdown/demo.tape b/dropdown/demo.tape new file mode 100644 index 000000000..0724e13ad --- /dev/null +++ b/dropdown/demo.tape @@ -0,0 +1,47 @@ +Output dropdown.gif + +Set Shell "bash" +Set FontSize 14 +Set Width 640 +Set Height 420 +Set Theme "Catppuccin Mocha" +Set Padding 20 + +Type "go run ./examples/dropdown" +Enter +Sleep 1s + +# Open the first dropdown +Type " " +Sleep 500ms + +# Navigate down twice and select "Bubbles" +Type "jj" +Sleep 300ms +Type " " +Sleep 500ms + +# Reload options at runtime +Type "r" +Sleep 500ms + +# Open again and pick "Rust" +Type " " +Sleep 300ms +Type "j" +Sleep 300ms +Enter +Sleep 500ms + +# Tab to the disabled dropdown (no effect on input) +Type " " +Sleep 300ms +Tab +Sleep 500ms + +# Tab to empty dropdown +Tab +Sleep 500ms + +# Quit +Type "q" diff --git a/dropdown/dropdown.go b/dropdown/dropdown.go new file mode 100644 index 000000000..580aafdb7 --- /dev/null +++ b/dropdown/dropdown.go @@ -0,0 +1,460 @@ +// Package dropdown provides a single-choice dropdown component for Bubble Tea +// applications. It follows the same Model–Update–View pattern and conventions +// as the other Bubbles components (table, paginator, spinner, etc.). +package dropdown + +import ( + "strings" + + "charm.land/bubbles/v2/key" + tea "charm.land/bubbletea/v2" + "charm.land/lipgloss/v2" + "github.com/charmbracelet/x/ansi" +) + +const ( + defaultMaxVisible = 6 + defaultWidth = 20 +) + +// KeyMap is the key bindings for different actions within the dropdown. +// It satisfies the help.KeyMap interface so it integrates with the help +// component out of the box. +type KeyMap struct { + // Open opens the dropdown when it is collapsed. + Open key.Binding + // Close closes the dropdown without changing the current selection. + Close key.Binding + // Confirm confirms the highlighted option and collapses the dropdown. + Confirm key.Binding + // Up moves the highlight cursor up. + Up key.Binding + // Down moves the highlight cursor down. + Down key.Binding +} + +// ShortHelp implements help.KeyMap. +func (km KeyMap) ShortHelp() []key.Binding { + return []key.Binding{km.Up, km.Down, km.Confirm, km.Close} +} + +// FullHelp implements help.KeyMap. +func (km KeyMap) FullHelp() [][]key.Binding { + return [][]key.Binding{ + {km.Up, km.Down}, + {km.Open, km.Confirm, km.Close}, + } +} + +// DefaultKeyMap returns the default set of key bindings for the dropdown. +func DefaultKeyMap() KeyMap { + return KeyMap{ + Open: key.NewBinding( + key.WithKeys("enter", "space"), + key.WithHelp("enter/space", "open"), + ), + Close: key.NewBinding( + key.WithKeys("esc"), + key.WithHelp("esc", "close"), + ), + Confirm: key.NewBinding( + key.WithKeys("enter"), + key.WithHelp("enter", "select"), + ), + Up: key.NewBinding( + key.WithKeys("up", "k"), + key.WithHelp("↑/k", "up"), + ), + Down: key.NewBinding( + key.WithKeys("down", "j"), + key.WithHelp("↓/j", "down"), + ), + } +} + +// Styles contains style definitions for this dropdown component. By default, +// these values are generated by DefaultStyles. +type Styles struct { + // FocusedHeader is the style for the collapsed header when the component + // has keyboard focus. + FocusedHeader lipgloss.Style + // BlurredHeader is the style for the collapsed header when the component + // does not have keyboard focus. + BlurredHeader lipgloss.Style + // DisabledHeader is the style for the header when the component is disabled. + DisabledHeader lipgloss.Style + + // SelectedOption is the style for the currently highlighted option in the + // expanded list. + SelectedOption lipgloss.Style + // NormalOption is the style for options that are not highlighted. + NormalOption lipgloss.Style + + // OpenIndicator is the decorator appended to the header when expanded. + OpenIndicator lipgloss.Style + // ClosedIndicator is the decorator appended to the header when collapsed. + ClosedIndicator lipgloss.Style +} + +// DefaultStyles returns a set of default style definitions for this dropdown +// component. +func DefaultStyles() Styles { + return Styles{ + FocusedHeader: lipgloss.NewStyle(). + Border(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("212")). + Padding(0, 1), + + BlurredHeader: lipgloss.NewStyle(). + Border(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("240")). + Foreground(lipgloss.Color("240")). + Padding(0, 1), + + DisabledHeader: lipgloss.NewStyle(). + Border(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("238")). + Foreground(lipgloss.Color("238")). + Padding(0, 1), + + SelectedOption: lipgloss.NewStyle(). + Bold(true). + Foreground(lipgloss.Color("212")). + PaddingLeft(1). + Border(lipgloss.NormalBorder(), false, false, false, true). + BorderForeground(lipgloss.Color("212")), + + NormalOption: lipgloss.NewStyle(). + PaddingLeft(2), //nolint:mnd + + OpenIndicator: lipgloss.NewStyle().Foreground(lipgloss.Color("212")).SetString("▼"), + ClosedIndicator: lipgloss.NewStyle().Foreground(lipgloss.Color("240")).SetString("▶"), + } +} + +// Option represents a single selectable item in the dropdown list. +type Option struct { + // Label is the text displayed in the header and in the expanded list. + Label string + // Value is the semantic value associated with this option. It may differ + // from Label (e.g. "en" vs "English"). + Value string +} + +// SelectMsg is emitted when the user confirms a selection. Parent models +// should type-switch on this in their own Update to react to a completed +// selection. +type SelectMsg struct { + Option Option + Index int +} + +// CloseMsg is emitted when the user closes the dropdown without confirming a +// new selection (e.g. by pressing Escape). +type CloseMsg struct{} + +// Model is the Bubble Tea model for the dropdown component. +type Model struct { + // KeyMap encodes the keybindings recognized by the dropdown. + KeyMap KeyMap + + // Styles holds all visual style definitions for the dropdown. + Styles Styles + + // Placeholder is the text shown in the header when no option is selected. + Placeholder string + + // MaxVisible is the maximum number of options shown at once when expanded. + // Options beyond this count are accessed by scrolling. Defaults to 6. + MaxVisible int + + // Disabled prevents the dropdown from receiving keyboard input and renders + // it with DisabledHeader style. + Disabled bool + + options []Option + cursor int // index of the currently highlighted option + selected int // index of the committed selection; -1 means none + open bool + focus bool + scrollOffset int // index of the first option in the visible window + width int // inner content width for header label and option rows +} + +// New creates a new model with default settings. Use the With* option +// functions to configure it, for example: +// +// dd := dropdown.New( +// dropdown.WithOptions(opts...), +// dropdown.WithWidth(24), +// ) +func New(o ...OptionFunc) Model { + m := Model{ + KeyMap: DefaultKeyMap(), + Styles: DefaultStyles(), + Placeholder: "Select…", + MaxVisible: defaultMaxVisible, + selected: -1, + width: defaultWidth, + } + for _, opt := range o { + opt(&m) + } + return m +} + +// OptionFunc is used to set options in New. For example: +// +// dd := dropdown.New(dropdown.WithWidth(30)) +type OptionFunc func(*Model) + +// WithOptions sets the initial option list. +func WithOptions(opts ...Option) OptionFunc { + return func(m *Model) { + m.options = opts + } +} + +// WithWidth sets the inner content width used for rendering. +func WithWidth(w int) OptionFunc { + return func(m *Model) { + m.width = w + } +} + +// WithPlaceholder sets the placeholder text shown when nothing is selected. +func WithPlaceholder(s string) OptionFunc { + return func(m *Model) { + m.Placeholder = s + } +} + +// WithMaxVisible sets the maximum number of options visible at once when +// the dropdown is expanded. +func WithMaxVisible(n int) OptionFunc { + return func(m *Model) { + m.MaxVisible = n + } +} + +// WithStyles sets the styles. +func WithStyles(s Styles) OptionFunc { + return func(m *Model) { + m.Styles = s + } +} + +// WithKeyMap sets the key map. +func WithKeyMap(km KeyMap) OptionFunc { + return func(m *Model) { + m.KeyMap = km + } +} + +// SetStyles sets the styles after construction. +func (m *Model) SetStyles(s Styles) { + m.Styles = s +} + +// SetOptions replaces the current option list and resets the selection, +// cursor, and scroll offset. +func (m *Model) SetOptions(opts []Option) { + m.options = opts + m.selected = -1 + m.cursor = 0 + m.scrollOffset = 0 +} + +// Options returns a copy of the current option list. +func (m Model) Options() []Option { + out := make([]Option, len(m.options)) + copy(out, m.options) + return out +} + +// SelectedItem returns the currently selected option and true, or an empty +// Option and false when nothing has been selected. +func (m Model) SelectedItem() (Option, bool) { + if m.selected < 0 || m.selected >= len(m.options) { + return Option{}, false + } + return m.options[m.selected], true +} + +// SelectedIndex returns the index of the selected option, or -1 if nothing +// has been selected. +func (m Model) SelectedIndex() int { + return m.selected +} + +// IsOpen returns true when the dropdown is expanded. +func (m Model) IsOpen() bool { + return m.open +} + +// Width returns the inner content width. +func (m Model) Width() int { + return m.width +} + +// SetWidth sets the inner content width. +func (m *Model) SetWidth(w int) { + m.width = w +} + +// Focus grants keyboard focus to the dropdown. It returns nil to match the +// tea.Cmd return type convention used by textinput and other components. +func (m *Model) Focus() tea.Cmd { + m.focus = true + return nil +} + +// Blur removes keyboard focus. The dropdown is also closed so that an +// abandoned expanded list is not left on screen. +func (m *Model) Blur() { + m.focus = false + m.open = false +} + +// Focused returns the current focus state. +func (m Model) Focused() bool { + return m.focus +} + +// Update is the Bubble Tea update loop. +func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { + if !m.focus || m.Disabled { + return m, nil + } + + keyMsg, ok := msg.(tea.KeyPressMsg) + if !ok { + return m, nil + } + + if !m.open { + if key.Matches(keyMsg, m.KeyMap.Open) && len(m.options) > 0 { + m.open = true + // Start the cursor on the already-selected item when reopening. + if m.selected >= 0 { + m.cursor = m.selected + m.clampScrollOffset() + } + } + return m, nil + } + + switch { + case key.Matches(keyMsg, m.KeyMap.Close): + m.open = false + return m, func() tea.Msg { return CloseMsg{} } + + case key.Matches(keyMsg, m.KeyMap.Confirm): + m.selected = m.cursor + m.open = false + sel, idx := m.options[m.selected], m.selected + return m, func() tea.Msg { return SelectMsg{Option: sel, Index: idx} } + + case key.Matches(keyMsg, m.KeyMap.Up): + if m.cursor > 0 { + m.cursor-- + m.clampScrollOffset() + } + + case key.Matches(keyMsg, m.KeyMap.Down): + if m.cursor < len(m.options)-1 { + m.cursor++ + m.clampScrollOffset() + } + } + + return m, nil +} + +// clampScrollOffset keeps cursor visible within the MaxVisible window. +func (m *Model) clampScrollOffset() { + maxVis := m.MaxVisible + if maxVis <= 0 { + maxVis = defaultMaxVisible + } + if m.cursor < m.scrollOffset { + m.scrollOffset = m.cursor + } + if m.cursor >= m.scrollOffset+maxVis { + m.scrollOffset = m.cursor - maxVis + 1 + } +} + +// View renders the dropdown. +func (m Model) View() string { + var sb strings.Builder + + // ── Header ──────────────────────────────────────────────────────────────── + indicatorStr := m.indicatorString() + // Keep one space between the label and the indicator. + availForLabel := m.width - ansi.StringWidth(indicatorStr) - 1 + if availForLabel < 0 { + availForLabel = 0 + } + label := m.headerLabel() + label = ansi.Truncate(label, availForLabel, "…") + // Pad so the header width stays fixed regardless of label length. + label += strings.Repeat(" ", max(0, availForLabel-ansi.StringWidth(label))) + + sb.WriteString(m.headerStyle().Render(label + " " + indicatorStr)) + + // ── Option list ─────────────────────────────────────────────────────────── + if !m.open || len(m.options) == 0 { + return sb.String() + } + + sb.WriteRune('\n') + + maxVis := m.MaxVisible + if maxVis <= 0 { + maxVis = defaultMaxVisible + } + end := m.scrollOffset + maxVis + if end > len(m.options) { + end = len(m.options) + } + + for i := m.scrollOffset; i < end; i++ { + optLabel := ansi.Truncate(m.options[i].Label, m.width, "…") + if i == m.cursor { + sb.WriteString(m.Styles.SelectedOption.Render(optLabel)) + } else { + sb.WriteString(m.Styles.NormalOption.Render(optLabel)) + } + if i < end-1 { + sb.WriteRune('\n') + } + } + + return sb.String() +} + +func (m Model) headerLabel() string { + if opt, ok := m.SelectedItem(); ok { + return opt.Label + } + return m.Placeholder +} + +func (m Model) indicatorString() string { + if m.open { + return m.Styles.OpenIndicator.String() + } + return m.Styles.ClosedIndicator.String() +} + +func (m Model) headerStyle() lipgloss.Style { + switch { + case m.Disabled: + return m.Styles.DisabledHeader + case m.focus: + return m.Styles.FocusedHeader + default: + return m.Styles.BlurredHeader + } +} diff --git a/dropdown/dropdown_test.go b/dropdown/dropdown_test.go new file mode 100644 index 000000000..0ca1215bc --- /dev/null +++ b/dropdown/dropdown_test.go @@ -0,0 +1,394 @@ +package dropdown + +import ( + "testing" + + tea "charm.land/bubbletea/v2" +) + +// helpers ────────────────────────────────────────────────────────────────── + +func testOptions() []Option { + return []Option{ + {Label: "Alpha", Value: "a"}, + {Label: "Beta", Value: "b"}, + {Label: "Gamma", Value: "c"}, + {Label: "Delta", Value: "d"}, + {Label: "Epsilon", Value: "e"}, + } +} + +// press sends a single key event and returns the updated model plus any +// emitted tea.Msg (by calling the returned Cmd immediately). +func press(m Model, code rune) (Model, tea.Msg) { + updated, cmd := m.Update(tea.KeyPressMsg{Code: code}) + var emitted tea.Msg + if cmd != nil { + emitted = cmd() + } + return updated, emitted +} + +func focused(opts []Option) Model { + m := New(WithOptions(opts...), WithWidth(20)) + m.Focus() //nolint:errcheck + return m +} + +// ── Focus / Blur ─────────────────────────────────────────────────────────── + +func TestFocused(t *testing.T) { + m := New(WithOptions(testOptions()...)) + if m.Focused() { + t.Fatal("expected unfocused on creation") + } + m.Focus() //nolint:errcheck + if !m.Focused() { + t.Fatal("expected focused after Focus()") + } + m.Blur() + if m.Focused() { + t.Fatal("expected unfocused after Blur()") + } +} + +func TestBlurClosesDropdown(t *testing.T) { + m := focused(testOptions()) + m, _ = press(m, tea.KeyEnter) + if !m.open { + t.Fatal("expected open after Enter") + } + m.Blur() + if m.open { + t.Fatal("expected closed after Blur()") + } +} + +// ── Disabled ─────────────────────────────────────────────────────────────── + +func TestDisabledIgnoresInput(t *testing.T) { + m := focused(testOptions()) + m.Disabled = true + m, emitted := press(m, tea.KeyEnter) + if m.open { + t.Error("disabled dropdown should not open") + } + if emitted != nil { + t.Error("disabled dropdown should not emit messages") + } +} + +// ── Empty options ───────────────────────────────────────────────────────── + +func TestEmptyOptionsNoOpen(t *testing.T) { + m := New(WithWidth(20)) + m.Focus() //nolint:errcheck + m, _ = press(m, tea.KeyEnter) + if m.open { + t.Fatal("empty dropdown should not open") + } +} + +func TestEmptyOptionsNoPanic(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Fatalf("unexpected panic: %v", r) + } + }() + m := New(WithWidth(20)) + m.Focus() //nolint:errcheck + for _, k := range []rune{tea.KeyEnter, tea.KeyDown, tea.KeyUp, tea.KeyEscape} { + m, _ = press(m, k) + } + _ = m.View() +} + +// ── Opening / Closing ───────────────────────────────────────────────────── + +func TestOpenWithEnter(t *testing.T) { + m := focused(testOptions()) + m, _ = press(m, tea.KeyEnter) + if !m.IsOpen() { + t.Fatal("should be open after Enter") + } +} + +func TestOpenWithSpace(t *testing.T) { + m := focused(testOptions()) + m, _ = press(m, tea.KeySpace) + if !m.IsOpen() { + t.Fatal("should be open after Space") + } +} + +func TestCloseWithEscape(t *testing.T) { + m := focused(testOptions()) + m, _ = press(m, tea.KeyEnter) + m, emitted := press(m, tea.KeyEscape) + if m.IsOpen() { + t.Fatal("should be closed after Escape") + } + if _, ok := emitted.(CloseMsg); !ok { + t.Fatalf("expected CloseMsg, got %T", emitted) + } +} + +func TestEscapeDoesNotChangeSelection(t *testing.T) { + m := focused(testOptions()) + m, _ = press(m, tea.KeyEnter) + m, _ = press(m, tea.KeyDown) + m, _ = press(m, tea.KeyEscape) + if m.SelectedIndex() != -1 { + t.Fatalf("Escape should not commit selection; got index %d", m.SelectedIndex()) + } +} + +// ── Navigation ──────────────────────────────────────────────────────────── + +func TestCursorDownUp(t *testing.T) { + m := focused(testOptions()) + m, _ = press(m, tea.KeyEnter) + m, _ = press(m, tea.KeyDown) + m, _ = press(m, tea.KeyDown) + if m.cursor != 2 { + t.Fatalf("expected cursor=2, got %d", m.cursor) + } + m, _ = press(m, tea.KeyUp) + if m.cursor != 1 { + t.Fatalf("expected cursor=1 after Up, got %d", m.cursor) + } +} + +func TestCursorNoWrapAtTop(t *testing.T) { + m := focused(testOptions()) + m, _ = press(m, tea.KeyEnter) + m, _ = press(m, tea.KeyUp) + if m.cursor != 0 { + t.Fatalf("cursor should stay at 0, got %d", m.cursor) + } +} + +func TestCursorNoWrapAtBottom(t *testing.T) { + opts := testOptions() + m := focused(opts) + m, _ = press(m, tea.KeyEnter) + for i := 0; i < len(opts)-1; i++ { + m, _ = press(m, tea.KeyDown) + } + last := m.cursor + m, _ = press(m, tea.KeyDown) + if m.cursor != last { + t.Fatalf("cursor should not go past last option; got %d", m.cursor) + } +} + +func TestVimKeys(t *testing.T) { + m := focused(testOptions()) + m, _ = press(m, tea.KeyEnter) + m, _ = m.Update(tea.KeyPressMsg{Text: "j"}) + if m.cursor != 1 { + t.Fatalf("expected cursor=1 after 'j', got %d", m.cursor) + } + m, _ = m.Update(tea.KeyPressMsg{Text: "k"}) + if m.cursor != 0 { + t.Fatalf("expected cursor=0 after 'k', got %d", m.cursor) + } +} + +// ── Selection (SelectMsg) ───────────────────────────────────────────────── + +func TestSelectEmitsSelectMsg(t *testing.T) { + m := focused(testOptions()) + m, _ = press(m, tea.KeyEnter) // open + m, _ = press(m, tea.KeyDown) // cursor → 1 "Beta" + m, emitted := press(m, tea.KeyEnter) + + sel, ok := emitted.(SelectMsg) + if !ok { + t.Fatalf("expected SelectMsg, got %T", emitted) + } + if sel.Index != 1 { + t.Errorf("expected index 1, got %d", sel.Index) + } + if sel.Option.Value != "b" { + t.Errorf("expected value 'b', got %q", sel.Option.Value) + } +} + +func TestSelectCommitsSelection(t *testing.T) { + m := focused(testOptions()) + m, _ = press(m, tea.KeyEnter) + m, _ = press(m, tea.KeyDown) + m, _ = press(m, tea.KeyDown) + m, _ = press(m, tea.KeyEnter) + + if m.SelectedIndex() != 2 { + t.Fatalf("expected selected=2, got %d", m.SelectedIndex()) + } + opt, ok := m.SelectedItem() + if !ok { + t.Fatal("SelectedItem should return true after selection") + } + if opt.Label != "Gamma" { + t.Errorf("expected 'Gamma', got %q", opt.Label) + } +} + +func TestSelectClosesDropdown(t *testing.T) { + m := focused(testOptions()) + m, _ = press(m, tea.KeyEnter) + m, _ = press(m, tea.KeyEnter) + if m.IsOpen() { + t.Fatal("dropdown should close after selecting") + } +} + +func TestSelectedItemNoneInitially(t *testing.T) { + m := New(WithOptions(testOptions()...)) + _, ok := m.SelectedItem() + if ok { + t.Fatal("expected no selection initially") + } + if m.SelectedIndex() != -1 { + t.Fatalf("expected SelectedIndex=-1, got %d", m.SelectedIndex()) + } +} + +// ── Scroll offset ───────────────────────────────────────────────────────── + +func TestScrollOffsetAdvancesWithCursor(t *testing.T) { + m := New(WithOptions(testOptions()...), WithWidth(20), WithMaxVisible(3)) + m.Focus() //nolint:errcheck + m, _ = press(m, tea.KeyEnter) + for i := 0; i < 4; i++ { + m, _ = press(m, tea.KeyDown) + } + if m.scrollOffset < 2 { + t.Fatalf("expected scrollOffset>=2 at cursor=4 with MaxVisible=3, got %d", m.scrollOffset) + } + end := m.scrollOffset + m.MaxVisible + if m.cursor < m.scrollOffset || m.cursor >= end { + t.Fatalf("cursor %d outside visible window [%d, %d)", m.cursor, m.scrollOffset, end) + } +} + +func TestScrollOffsetRetreatsWithCursor(t *testing.T) { + opts := testOptions() + m := New(WithOptions(opts...), WithWidth(20), WithMaxVisible(3)) + m.Focus() //nolint:errcheck + m, _ = press(m, tea.KeyEnter) + for i := 0; i < len(opts)-1; i++ { + m, _ = press(m, tea.KeyDown) + } + prevOffset := m.scrollOffset + for i := 0; i < len(opts)-1; i++ { + m, _ = press(m, tea.KeyUp) + } + if m.scrollOffset >= prevOffset { + t.Fatalf("scrollOffset should have decreased (was %d, now %d)", prevOffset, m.scrollOffset) + } + if m.scrollOffset != 0 { + t.Fatalf("expected scrollOffset=0 at top, got %d", m.scrollOffset) + } +} + +// ── SetOptions ──────────────────────────────────────────────────────────── + +func TestSetOptionsResetsState(t *testing.T) { + m := focused(testOptions()) + m, _ = press(m, tea.KeyEnter) + m, _ = press(m, tea.KeyDown) + m, _ = press(m, tea.KeyEnter) + if m.SelectedIndex() == -1 { + t.Fatal("expected a selection before SetOptions") + } + m.SetOptions([]Option{{Label: "X", Value: "x"}}) + if m.SelectedIndex() != -1 { + t.Fatal("SetOptions should reset selection to -1") + } + if m.cursor != 0 { + t.Fatal("SetOptions should reset cursor to 0") + } + if m.scrollOffset != 0 { + t.Fatal("SetOptions should reset scrollOffset to 0") + } +} + +// ── Option funcs ────────────────────────────────────────────────────────── + +func TestWithKeyMap(t *testing.T) { + km := DefaultKeyMap() + m := New(WithKeyMap(km)) + if m.KeyMap.Up.Keys()[0] != km.Up.Keys()[0] { + t.Fatal("WithKeyMap should set the KeyMap") + } +} + +func TestWithStyles(t *testing.T) { + s := DefaultStyles() + m := New(WithStyles(s)) + _ = m.View() // should not panic +} + +// ── View ────────────────────────────────────────────────────────────────── + +func TestViewDoesNotPanic(t *testing.T) { + cases := []struct { + name string + model Model + }{ + {"default", New()}, + {"focused", func() Model { m := New(WithOptions(testOptions()...)); m.Focus(); return m }()}, + {"open", func() Model { + m := New(WithOptions(testOptions()...)) + m.Focus() + m, _ = press(m, tea.KeyEnter) + return m + }()}, + {"disabled", func() Model { + m := New(WithOptions(testOptions()...)) + m.Disabled = true + return m + }()}, + {"empty", New()}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + defer func() { + if r := recover(); r != nil { + t.Fatalf("View() panicked: %v", r) + } + }() + _ = tc.model.View() + }) + } +} + +func TestViewContainsSelectedLabel(t *testing.T) { + m := focused(testOptions()) + m, _ = press(m, tea.KeyEnter) + m, _ = press(m, tea.KeyDown) // cursor → 1 "Beta" + m, _ = press(m, tea.KeyEnter) + view := m.View() + if !containsSubstring(view, "Beta") { + t.Errorf("expected 'Beta' in view, got:\n%s", view) + } +} + +func TestViewContainsPlaceholder(t *testing.T) { + m := New(WithOptions(testOptions()...), WithPlaceholder("Pick one")) + view := m.View() + if !containsSubstring(view, "Pick one") { + t.Errorf("expected placeholder in view, got:\n%s", view) + } +} + +// containsSubstring is an inlined helper to avoid importing strings. +func containsSubstring(s, sub string) bool { + for i := 0; i+len(sub) <= len(s); i++ { + if s[i:i+len(sub)] == sub { + return true + } + } + return false +} diff --git a/examples/dropdown/main.go b/examples/dropdown/main.go new file mode 100644 index 000000000..07b43d0ad --- /dev/null +++ b/examples/dropdown/main.go @@ -0,0 +1,150 @@ +// Example program demonstrating the dropdown component. +// +// Controls: +// +// - tab / shift+tab cycle focus between dropdowns +// - ↑/k, ↓/j navigate options (when expanded) +// - enter / space open or confirm selection +// - esc close without selecting +// - r reload options in the first dropdown at runtime +// - q / ctrl+c quit +package main + +import ( + "fmt" + "os" + "strings" + + "charm.land/bubbles/v2/dropdown" + tea "charm.land/bubbletea/v2" +) + +const numDropdowns = 3 + +var charmOpts = []dropdown.Option{ + {Label: "Bubble Tea", Value: "bubbletea"}, + {Label: "Lip Gloss", Value: "lipgloss"}, + {Label: "Bubbles", Value: "bubbles"}, + {Label: "Huh", Value: "huh"}, + {Label: "Wish", Value: "wish"}, +} + +var langOpts = []dropdown.Option{ + {Label: "Go", Value: "go"}, + {Label: "Rust", Value: "rust"}, + {Label: "Zig", Value: "zig"}, + {Label: "C", Value: "c"}, +} + +type model struct { + dropdowns [numDropdowns]dropdown.Model + focusIndex int + lastSelected string +} + +func initialModel() model { + // Dropdown 0: normal, interactive. + dd0 := dropdown.New( + dropdown.WithOptions(charmOpts...), + dropdown.WithWidth(18), + ) + dd0.Focus() //nolint:errcheck + + // Dropdown 1: disabled, with a pre-committed selection. + dd1 := dropdown.New( + dropdown.WithOptions(charmOpts...), + dropdown.WithWidth(18), + ) + // Pre-select "Lip Gloss" (index 1) by running Update with a temporary focus. + dd1.Focus() //nolint:errcheck + dd1, _ = dd1.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) // open + dd1, _ = dd1.Update(tea.KeyPressMsg{Code: tea.KeyDown}) // cursor → 1 + dd1, _ = dd1.Update(tea.KeyPressMsg{Code: tea.KeyEnter}) // confirm + dd1.Blur() + dd1.Disabled = true + + // Dropdown 2: empty options — shows placeholder only, never opens. + dd2 := dropdown.New(dropdown.WithWidth(18)) + + return model{ + dropdowns: [numDropdowns]dropdown.Model{dd0, dd1, dd2}, + focusIndex: 0, + } +} + +func (m model) Init() tea.Cmd { + return nil +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + switch msg := msg.(type) { + case tea.KeyPressMsg: + switch msg.String() { + case "ctrl+c", "q": + return m, tea.Quit + + case "tab", "shift+tab": + step := 1 + if msg.String() == "shift+tab" { + step = -1 + } + m.dropdowns[m.focusIndex].Blur() + m.focusIndex = (m.focusIndex + step + numDropdowns) % numDropdowns + m.dropdowns[m.focusIndex].Focus() //nolint:errcheck + return m, nil + + case "r": + // Reload options at runtime (only when collapsed). + if !m.dropdowns[0].IsOpen() { + m.dropdowns[0].SetOptions(langOpts) + m.lastSelected = "(options reloaded)" + } + return m, nil + } + + case dropdown.SelectMsg: + m.lastSelected = fmt.Sprintf("selected %q (value: %q, index: %d)", + msg.Option.Label, msg.Option.Value, msg.Index) + return m, nil + + case dropdown.CloseMsg: + m.lastSelected = "(closed without selecting)" + return m, nil + } + + var cmd tea.Cmd + m.dropdowns[m.focusIndex], cmd = m.dropdowns[m.focusIndex].Update(msg) + return m, cmd +} + +func (m model) View() tea.View { + var sb strings.Builder + + sb.WriteString("Dropdown Component Demo\n") + sb.WriteString("───────────────────────────────────────────────\n\n") + + labels := []string{"Normal (tab to focus)", "Disabled", "Empty"} + for i, dd := range m.dropdowns { + sb.WriteString(fmt.Sprintf(" %s\n", labels[i])) + for _, line := range strings.Split(dd.View(), "\n") { + sb.WriteString(" " + line + "\n") + } + sb.WriteRune('\n') + } + + sb.WriteString("───────────────────────────────────────────────\n") + if m.lastSelected != "" { + sb.WriteString(" " + m.lastSelected + "\n") + } + sb.WriteString("\n tab/shift+tab: focus r: reload opts q: quit\n") + + return tea.NewView(sb.String()) +} + +func main() { + p := tea.NewProgram(initialModel()) + if _, err := p.Run(); err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) + } +}