From 761bc45be2d6ef03a504184011df1fb75f864079 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Tue, 17 Aug 2021 11:36:38 -0500 Subject: [PATCH 1/6] chore: check-in work --- ...-inert-npm-3.1.1-27dfbc6ece-d4553ea762.zip | Bin 0 -> 58797 bytes packages/react/package.json | 1 + .../src/components/Dialog/Dialog-story.js | 104 ++++++++++ .../react/src/components/Dialog/FocusScope.js | 185 ++++++++++++++++++ .../Dialog/__tests__/FocusScope-test.js | 10 + packages/react/src/components/Dialog/index.js | 112 +++++++++++ packages/react/src/internal/useMergedRefs.js | 28 +++ .../react/src/internal/useSavedCallback.js | 31 +++ yarn.lock | 8 + 9 files changed, 479 insertions(+) create mode 100644 .yarn/cache/wicg-inert-npm-3.1.1-27dfbc6ece-d4553ea762.zip create mode 100644 packages/react/src/components/Dialog/Dialog-story.js create mode 100644 packages/react/src/components/Dialog/FocusScope.js create mode 100644 packages/react/src/components/Dialog/__tests__/FocusScope-test.js create mode 100644 packages/react/src/components/Dialog/index.js create mode 100644 packages/react/src/internal/useMergedRefs.js create mode 100644 packages/react/src/internal/useSavedCallback.js diff --git a/.yarn/cache/wicg-inert-npm-3.1.1-27dfbc6ece-d4553ea762.zip b/.yarn/cache/wicg-inert-npm-3.1.1-27dfbc6ece-d4553ea762.zip new file mode 100644 index 0000000000000000000000000000000000000000..236a00e3042afacf299211bfaad05ef938f7d976 GIT binary patch literal 58797 zcma%iV|1m#(r#=g6WdNEwkEc1+qP}nwvCBx+qRwDIr{EB-(3fLt=hYPboIOIdAho) ztJW(k0Stly@Rtu?^%BHi2Y>&9{qt*WV`!vfWn<`MY2-jB`!A=W|L>_TW(FoSX4Xdb zj(^tz0)Y7I1JD|`ZY=)H&io_z|3-^eUr*o2(%yhM@+VwBJ_3k`c3pcqWp!o^J``aN zjl_T;b{%v?&%2~RQ~IK*H1G^rgDs+a9|cgfD%f3fe{;g%s_3OldUu?S2b|o>Q%_*$ z2c_2HebPKZrbdQ-tUtK(A+uG!oH_>4Ymd(Q5>{8)5zt@EgZlHA{)WWXy_)HdKjXg$ z$shSAB>o@sXpI~!&8!{GOss9}js9s8B_$mtt+@l#-(dR(00fu&O<8}|d;TK?|9M?| z1F30lxBz+>(48!W+zEWSA?6?#+5*oD=BAIS6+fhy?G%AnUny14PM3m;xb- z2f%;5C2;>%1CBL`8#qiamw>RLgK}d4<;L^mPT!2C0KQO ziHgZuh7)F?_t3~ssx>|KW!-u(R)$NPcQ$!Bz?+BILeTL}>Qi06gF(df?&K2`hdf)Tx zR~B8Tg%|!j^DC5v*?q?Os+IQZ=g(AiWE&CwY>+lgP`VO~W259B35WCgpI;vU>6jYE zLU_N$RiJ~TA7P^8lQ8)BgQ=4l&_|o(pl~cF!JPmjK0qD|!NdfLj7;M|JhXW4IGQOxpgVxJCv*@F&YFwEUG$D14_Jgq@J1Q|OUECk5OJR?=fEsa<=aacE?Ob$ zG7nPY=BiLiw2Iago-~2-!l84ooUVN%!T^cKaqO{WcQ!cKbqmn9B~sEY325ti>28pg z(>y)jE2B-C`d-mSHHb2)T?)xSWoA<8^Y?*j-`j7X|9ad1y92_|$jat_1&+i!(Vc5h z0018}006Ro1LpyXrRug#CL4nHc8|U|o-+}sC`**0m0Ed6qfHl{JfV_O z5;!o}2I$19F{`H2_j^g@#U{xz0?KUy_s+gMs4EE?7CZU4c> zcqYGM@lt>!g%?+6z!WYeEJE^FNoc5C$smgvLo4xj3V%OWNn1~7)pY9ONB+#vMJaW+_ z8XR@@T@d^c3b6-J?PNv7PzOR{C5aNbviG&R7!(CiST&&_|8z8~v2Y-iBv%mz%$Lpk zDdqC!o+o{6iu{sZ6-EFAJHJQO-)3IG@o3b3lCe)wUg`1DW~(ttf`*m$MAayC zX!O@PAB;bFnNjc0lTVi}LI)}&Z8bqzk3fe<=;o2#rKep4vR#i|G|`Cj2Y0yi>S;wg zd`ow3&Bt#Vc0jdIE7i5&k66-2@>fTH?tFh-Jj&rI&SD*sG9CGo4@F{-g%&x$95rk&!F8o8cHIe z2&ZY$9$d8k`oRm}(8FEMYl05?J@Fz6YIx|z8Z#8E9LRNW^V6Y$(8&kJhx^ zv2lqcoZEoXhA>h5)J8S))wmd0Ti(1qn6K0bO>Fz>c8h(6zk3ER@MC zeUMS4%5Z_+q7`bAFes>o(m`|Pcw7Sq1M(kwY`#ybLqDvDqy>SP22gD|jYIYpAZIw1ic?ew3Iat&xy8i##fSsZL5nW=hBzPmlV9>J z9(H8ZG%S&{!YvSyyX(q02;8>VD~PDoI3U~svxd-{(Y{<_;qfWXVG3AzF$!pL2_{7tdON_eS@o%oLwHo!9kjlZ&)h}U)#%AEyI2?5 zWOVM-GBbGM{dGG<>*Z7o-2}b?Nxj_V$cvLZ{>?$w%>B<@Lj-2CjJOLk!#~O1i z{&gWD>iwm;+T&h*G0gd-Kw{&uHUkLM^KKoo9`aiI`M+WYS4ERz(4P>z`zJ$U{Wme= zuleZzWJrIqc_Vv014lDwBZt4n5GVP%=yiIS&Cef|#m=&hmw6JqrlYVp)t9kAtW>DT z&~ZY)iq8+OsU+^2K;09_2avr99;VPK|<+T6AoV{{VLlZ{%quE@0){Q3mrHn~boI?-!*xII5AIWVthwcL{x zND{+j*Ti4+mPN}likq&`T7Bc3fM>FejiKIR8?|<^n#)Pkn^H9{wKFGw7*RmzI@pVS zThZYP4#GmFxI>Td;oWwkRa{X(4rgEp$8A6J3GsM+u3^ zoYakT%m)cOqvRgUoEgCFh=}$}&|t}9nRZobo)Sb_FIuQ(5?~#dTvQJ+#AY7p(cjWO z#DN7yce3uc*G2=?NVFkH&lqTaqAsjEM(lsV>)p!Myxoj_D7GhDsEMmpzfV&8`A_Hwo?L4{6n!m&zry}=f>rVP zTA=WU*`Pnn{`{|*wf=MOFf#mG+8a0dC+$TDymkv8B#X73mnR@dJc#crLlQ+Hh_>#G zsv>nhv^;ydwDT^X(?%exad8&TSssNwo)td@>vYn9Fe!p&f)zK75(F2J|AOiDXxol)EJha7W%;}X%b_n=OR)fA=HTh z>;`dQ57U_;m}w%HxIw*jxA^=HOE#3}HsW+CGr?(j>K+phzPayTf7z_()2FojV zNe>BrQ@SP&1Vy|RjKV0VLgi~<${&%r*s^B!8i*#K4-*QX;$A&ooaCw>+0Ca7+=|Ie z?zR}=M{xr}!T_*HVr3QAQ=R-Z|9IkR@=VS%9 z5#-&78eHtY1L`609EFx|>$un|+Ex=khDbzfz5(rVo@Ejh*ii{Owv}2szg&C({+XeG zUDge(${s^!5!_g*uEP{A_BfSz(7~#Ol(DHz} zzRFaQ^*6UPW-d$|$7;97!`toXZRowJ)(K{(6DNd486y)jrD>!9xFWvi6xxk-%R$kv zSmE9|W2vBEU8mrJ41d#a;=pI`i(`Xx`?}iV=4eNSupdYDC=H_z{D#gx_8fGf%U<~K zewvSdE&HMOOyPU8cTRO*?jFJZ$qxoDVLViH=+38mh`mG7*e(RSS2#|KKS zERyGFPj5|wt?GKun#=S&64Vf!^vohWZWoojN!gy0u0^-LgKq!pG@R%^apPcXWblvL z&p)o7bo%;bfWQC%(J=qT{pN2A{Oxk{m+_yP$fmM{%{(7M_dS)cZ7op~+}>Iv5^NHi z>?0o{_<){O%(>EX$LW=2I>ciS<2f7MvK_KK@c==b&iy4T<2IMQV_Fi$&}@+{Lxgnh z4F{2#BbTp^4S+m#(#~J8m^6MuB(r|AwwGccof!*xS@}+65)mQP~{nY0gO!zuh(e1-flK$m6pg3?!{WTcNQwy)8@+_C|R`vlIyR-QbasLKy}3$lES zS|cN)3S8Ds<}ocWbTXx0w5T^(wNfWI<0vE1L<9H*NPEHQ6g43ZZNWdqHyC@(^9@PG zCiQ}V)hJ%OSQ@>ZL0<8gZ*wL~z&ZGqp6^Uj3to=x*YIBbW!NtZr;5cWHbe+%g=O+G z1u31P8%jArIWvtjxw)HFF_JS?vmpeIcd(53jG5|Q_)zmGePI?vuJ1ZoFdL#ERNf71 zv)XBVmB}&doL`%ubGt8DR^-*kD+eFZlNL#}Dg_(`sAFz>B)~rbUFFzA7Cpw?As2i5 zb|fP7`-%cIMYQ8G1JO1_H_yk7sNC2S;A!q`n8JYPTTIB`W&3--S?6g9KzAnsBJbKz zxe9~!G~$jci;BUm!c844AmnFWTwf(&F~;L=%zyH%3w73uhA#Wt%^yt>U^<-lgiwmE z37XX4FDl=x$Su6GNhY`o>38jT!kb1SD$JyeZ4Jx_m0nRbXAh(DZl3rA*3#`yNHu zrr8qDcz1VwA=&dlA!{5R+nj59xps`K4w@3AMHCSW_aF6|j)0+Iow?fW(fJnm5KZfi zo?t{?UXAHIPxbeY_o#5~KL5QM^bdDpDuT^)`QwLGe}v(`^G72~BP%0oN7}zTl-J}> zs6SPlpk3~g8B(lp+E{i6kDvj$Ix|X0jXc;Q7aOQeAROH9hg)Oovg9yeIOuJ)%*yf# zn;}>KbIQ>O@z9$yfN*e2wPhnPEN-Kli5rKTVZfVtHa9j;S={wkf|T;1#TH+oC;i9+ zapZ)px6K}dZLjBlQ~sm+@60eYva~g_|KG<8$BP@Z>7Td(_2<6-A7jVgj+cKd^tb3? z?vSLYC9}?l;5l8B+N}$Y7-Aw{E}y6@N-hWa325SBg@^4j>lG+0_vP_C1t%*m_kOg# zo|TogdjIg^8ff4NU*8@w!)$K$i8|Iuqvjfr%vER&BY8s)t4yfqNFxp|Gu7c=QBn|9 z+sbmuY$eK`Y2ecI($4h@6}ka=!p1t8)sxsF>hNB_E%jawOKR2oNrM@U7x2eVo8VIv zD$-rfQ^GtDFau;LT+X?fp@L$D#;OIY*?pQGA>7C*9U;seY^j-~6yalo3t?Z&Sl(xD zpH=p|>jd91QxlXQm0|=-7EO~*vS%QD&AWb$F8c#Ah0_4|F-Be21rDzTw51@Cr z$yiK($gjl1D;qoy)=~;w&x2tX8r|_eAm3-ARJ^b{HKu{!U@sy&Q&S>GF_w4h7Q(_> ztv09c?wJs1YfrTLD#p1V?yv3~ueZJYzUmg{dGSz<<$90VqjmxT`-z!{JsID2eHW^c z%=Fqa<@M&#rMf1O#|XcKsGX?NUYyhE(g|>j<6`KBSc-J(uH6Dc)_lRivt)k{nPtlz z>{@-u>W%4AQY;K|DD#_6;C?7|#cf2kaO}2<;rO`ub7VAOQxOR$ZBWC`vLl22iAXE z-!l9E3gB`*9q!kd006W$000#K#(50?qWag)%AYE@)t^{8$!+0)#Tvi;NQJMn81G0< zxwYUGmw%wx+;CpLB0V@&)a*%XHsUv+CdasumWN*5|K0uhyTGqEHZkF%YCn7wBNvbf z^V9la?!Ss=hddIn?n5amlloFCTcrIvzk}MdYyV- zL2$Zq6DAM9Z=hUHH@I)lNjfrUt*vGU*SkTq6@>MJ_O!*p`XC;(jk903x0+npC-zP? z=a%g=GlZWX%Q}lv&8ChsHp)q?gO_7Zmghe@>8uxSMt?P{o|2W53*E!fk{EwSq1TB)S0hU(iroqD(*Rc zp39-V0?h6U zPK{wq2s$=Gb2!%|eadcvz}6XY)>Jf6KB{+xkaSg^m*eudWhWUemSeyaG6)5f!+dcp z@aRS2;bif6xff=aKi}ueotJkj++63Up4QK{sVB8n>w#2xf1S0tt86x&S1wUfIXvd4 z1F9uey=7w&#qM@mrI?hIoIkqPf0@fKwv@1~L&dhV^L}b?8%NCZ`n+EB$e1kJE1ISqDe#lGV@a;X{3=122ru+*WTp?#7HpMB+itADebKnkjx$MMu-i1wc)cIHh2D#)} zvCF;es4>GxDH~t!KrO3+p<|h;gzQ<3GvoYdG%zFN-nDHe)ZnMTS$ENrGsh8uWN8a< zeZs)_5p$1s30nG8uUsg6t~Ota>gyuxg-3wr0H4K{95VWQKmI#?&Ew>7uPUssBd5a^7kBJ>_W$Bxm5+jBk8i0GBqYpS9{O&87!=i0&w@O zf)E!x@98^*ETZQNfZ=SPfaBE1?f%aG!L^bc9Qb}O`NsIU3dziLjMAbu@YMK)^8OG@ z?FT5XhiggtVq+EaO{ji8+0EVH`tnnn`gDbG1!g`69DZ_mNyZDm|3Mf+SLK zd76+}RsX$Ncrns7w)2kF#x%BhHu+e z(qem~LEsJ+5QG6p$BLwW?r&}t=0@vdweuG3%29upIlgay%it+@b2Tmr!T@C&$EDxW zNy(|9im3t!$^k$>x9r$J1}B4;Qr2{~7Enoog>Zb;Sj@}QzW3OE5h~Bihb)wGap$*| zJ$gL?7;G#oax)jR@5u`zqe|YSv~ah0fSow`r!Gg-SI0Rz!Pbw@_z`7aC36t3CEY#P zJ%s6n21~UCV4iytGfw!CdUs6tvX;sW2WMHC@gG6UwSB_8?=M*#DDhDzz=^WI(y+2@ zL)={EryE1_|E}-Smp1atp6jtDv3C3QbKQ0XJ(L0PPWs{BG@dhp8YO$L z7_v5{-=L>f^iQ(77m8hAxahXUZgI!9u_wL&TYkk6;HG&d_x0+EEm-qOD4FQc$hsgY zPpz>0p{T2n{aBG7!C)hwEC{pqPf}puk4StUNhXf7gKr>;pWU}pu&p!@R0tY5PXcr* z5kcn*Ez=3bE`lg;x~VYjCqhR@E%PH&q^OWobpE0M#%|Hq*KrbH8W@*$^1)7|xD#h4 z3+b^qS+6E!^e158G^(D#4V((k!LnBYVa-ZE0`O6xlXH&i;J_Sog7vouhn@QLXV~uO z2E{QN4fYv#rty)dW5H_pj{!f`zwM(Uv5af(>ws9WZeC#y3FFli@Q=4%CU1brJLn8L z<|5e{L&exj#C$)^q29iL?GU-NYNGCLm$+EgWs{x;LC7RzM56hAi6YdGWWIXobv}H(w-tlzOZL^1?G# z1}p)W8;`eF$~Wt84~ru=EivZ=8uOqM;?^a;Tx1Awj=J{$1WomY=!Ds27EKwMvFopU z(~V(Uc{eE)*C^vg&pYrqlaiA$ju7~*Xr8&?+K4R5- zAHK^VwiHnjJ4(R~lo{udro<>HC~a&^1F&&}OpmTDho-veV|Lh98tuC6vSBCant<;) zBpKK8O?WQ`dQ$_|;5$Sv14mrP-`qnif7Y0f~hNWu>CEoUGXGP{|MF07O>=(rZQDa< zB}YucDq+W=o0H>ED(6!fDx5#p$bm-h;^*p#*iwGdf|XAjlb^a<=S+L7FeK z#JtJIW1Qe`D#s=;59VMh4Qi+9qPDit{ACgsp!=nbk{Fvk2eZU1pL>`0Iods*_g`ln zcy!+acw3!bw+vvp7bBpL0z@RP8nI0)!OW>}+!nIc>rsm8TZ zl5}Q2$q72R?;enUhnkXaJe}$*!2_E?Gd)379;QrZN2yV=4P%v?K>wDsPE=F$kb`SH z0pa(-2T>=%we=zTNR4&BRpvlsc>D#)da^5n*pD-0vBw&93 zF5~vCTyg!KAeawL$`QswAn+oz{WFS{lB?MG_ohb!ci_)t0-1~5u4u?d%A}t;Nb}dY zc?KSV+TP#w$2%_o{GD7}6C5OHMg9IUi74wezOm%k#pBpQT{nw$`7!CAklZ-P=XX|E z#Yx39q9#AJs;*==NAw8iqg(UjZX;MwJqu~;@!Z%TO8 zCIo3A(`R%O=lIQZuhZtHtffsm*?^JLQt@};vu;<=bkX%dh`7l-r03_6!tKn(JIFso z_Tvj1Ts3l8#-ufzc_a>y?sJdl99UkXN(QI9zFR{dlOT)wFo37NiI|(h9^2)0OmHvg zCF{A+ibt%Iu1e~^zK3#|BH!%LQ)P1egs!`!_Yp_EN$;LqJ^=2P1oQv6Dse?#W=lXDyPe4fOtIMOxsKcRau^55jTh~Nugyy`1CxR#kl zSjsUOehd*tHF_^(9IXZz7i=KW?HCoT*MUIpO~*O=q3i6|5%#h!=2>zkLyO|H=+Z=~ zMR2!2ii5w+9P1I0Jb>hm;@ZHo7inLbU6h{KQ$QRjAv!dU$*)t zVD0ro1|+uGAX;g3^;Y7YohU6$8B*As4cJN3_-{ZgJT;P4m;_+k=+1>NpDr7cH!j1R z=m2K`tpw{~>r;p#Ljm&$pugNG#6&Ww(YTaO`AP#b0BDuaPB1KQ+Es?(^@S0cMCpx+ zDGKi~giTGyr*p@`g@cAJYkg4G)oeu(w%7WKIz<{L78Xc<-%DRxJs5JHxo}#!gq9E^ zr=Z(uVk?uozR(OE0Xs{*Wy7w!{LDZJ+d|~7Di4|(v+6Mf4EI43j=^PZ+U%`*xQL{8 zwELcLxyUk^XB;I>CHBN}H!T1b`09SNmUK=)@#ah?bbmE9iagzi!z>DhOGxuH{BR{% ztV=aC&Y5PAPm$g}Rsn4{4D}Ps$ZgM4q?HXJ4412?P zT&lb%wm^-7f2k`;ahUOkVKf_#sUD6jqz8?fyM&^NUp^40&$&&PyTITU*dx&;#xt9r zC`1HCmj?+bhC-b-02qmkM`quH_ry6J4BqBEkhg$xd4mBQBZ@7zEt;T@P-o%L7?;a*k)GNTkh_P(0y`*hmS15{$7dm}i>+OvfSgy$>ZB zJJm3R3aH_NTLCw_qwrp1&GiOcsS_!Ijr}o)QMSn??)=@TLvQxFm15>ApYo?K^|K<$ zJiMJU1I2ZK$J@`aMVs-(Po}tWQTanJ7B3<3rXQUlL@OnTjx+A%vh2B;)ka$Q9?k6@ zfXWFfCfsn!zrjls;#7qUZ0Bxet~PICM#Spz$r&L_79APF27XRhQ#4>J|Lz{c4>MCc z56M?&uDuU_L||OY2^pa6+-8RlGq={>?5urXNC*^!7StH>k^nJ(FOUQujOV(z|Dq-n ziSS~V=15yTpdPaeuYD$CKJ8W{wJ8tg@1rk$MF!|JR&N!)E2q%FX^e}U(+RVe44))! zdjcsq`*ZqsW>EBQWM~AH$STYt7NNbErjlXGXD!`FN&1@$VlEtJ^bBIK1!Li5q8qd= z65tSv};$%T=_?3^>;;l6S5A&foZdbm`{w>SVdUk*d&-@=Hn^Bm)elTHs6$ajmS(7=#LD|}Qgi!x9=_{3GW)5t@;YDp~CS@Pfz5ARL(!6VLg z4Tqp}lzO8c7m1LJVDt_qJe>=92IkeIT6uBZ*!pDuN?TymeXzHzI!8 zS=8ksc9T~7x=D1pA`z0UfMe~B6nd)^YrQLa1IJ91+KfnEPEgmv|?v&AJ0%2(V+>vLT zqQRF}hBZ}JKusnr5nI0t;Ipxu;s70mH^VtHM9y3Q;J;j*YA1^GMvDgK|U;|KKA`h2QDxX?pao;dsJF$i?|3g zZ0)lvbMwsi9K_gnj!R?EZ-e7fES}KcdG|hPj;QbQ@%MZmxtU;2t93|=ktH`R(}~N$s#IxNpwy159+fZuU4OPXy`MI9+|_t3nU5K zzYGpAxV}Gmrs$XHAa9^JIg7#wDv6`O>3AMc9?Zf zCF|fdB+#k#bhW%;0>FrTLdU}FZ~ydqio`oB+WP61F(XJOvhf=*l2~-PM|zS{I{Ykc zTCb?tf+?Vi>V)RJL!19n17KoSW2MTldWQ*Y;d z97BjVs#3BC32IC}un0Tebt!c+l2R>&rFE>uz6HtM2MPHDO|g<$>Zk3^-sSnRC&|P! zQ}BJMauK)52cc5rh5`+q3(kYQ^l+s&7D$a?bJtX%D5<*wQJ`Gl+ zGW|p*XOF z_MovGz3S}41O7sG9FNnOUw%kGUP)Sj>_$M1nsKSMw2#J)UYuBehFM1zxa9eqtTrej zDU1ZeOt5KCOexvlGwnZ2I(CdpJWxb6Op|F)Cw@`Sb;UQXrwvvSGy_m|^T&X8&OcoF z7JOd4R7lG27qqUJ6Rrw3$Wzdq*;Q#s5gDy6E6-pPZ4a3q9^Y=!#>T{M`awZZOpO;utfyyUX&hi z*P`b~ts12GMu)xIDTzK*H1xz^UBTrw$p1R#LsRaoff-d=#ojs754^gu12y6kLsMzb zCLehjpoxyB4nQKj8)b99SWt2ZQz9MASw@z;^48qp>w%RA)j`ueG8`?{r_Y2+8-lr3}6;Z`U?7IdvuQO{)N1FFu#EoKyg_LE>bPi*mN&IPV#RH&rzL z8(y-~d9d_=$h??3!@8Z1=<4zFwQWom3=aHIKqyu5MzkSkaz~R{1h^#LPxl}euIQ|zH@nZ|2m1jiUEN!Iq>dt zcQ>zO%*}#sz<}#|V2xH7)3HybV^8Jmg4sB_j{ z^Ft8dbcF{?BSlc){2;xTftpH-ZQu1RrIFKXKb82^c}E6mdq{=Bhe!+&8sA%W^^SS< z)S{M01Ph+h-V>g3AL@(I8zhhElFrCcxrLV9<#AFg^Y2iT`&@cT7E4hllq3t2xZTJ1 zg(9z`O@vQWr8V&lj|DTZ5S*K>17uXrd>Nm-=zaK#`ZP4w%bVY0ncIEZJOPss&JK%q z=@*`Ht)0P;;)+{YB%Pq6%F6^6FcT4(8@*MJQgi?F8S`0&fCd(p5$3h6baT&amGI&q{vP0JZ2KSaL;Vt48~ z?vM2)7BL4paF9({>9-e;h@`7|a;JPd_L_K}!u9yVyURQgAuSGFW3cW>F*G0i4179} zBzryPX(x>)3Y*s=$~-k()EshCF=NU#bdFJhd7ho~_6J;(%@tI{F7=&aT2F@X2sjbl z6fYWZzCsjgTOoQPH3KPW#k_=)p*rBW`T%5FNUUMY^*eE{;+K&VqB>Yqj;JG6vm`ZPPQ5b_b;S}00x34ewF*Lio21rd za8;{9OHOFsgBCXzfuGd4B@kdo^a_DWuqF-Va@|0I)qxpgld%jA*tB~%&^~T_FETIN z_s(?Q)9LEL>rl2+d$m=bDQg3Z^<3=L8STd#qnciKg>HLyoYSBanVj{D@D?rDObh@E z^Rg1Ld4|OARpW)nc&tcJmORlDTA(V?1?`fov=ligf0)doN_YNJeWB-9x<*~(CvFOZ zI9rADZx@$Ef#a=HZ3tZOV*4nxMoqQX;dn2J$*qPF(jpMkdYoHW^v*wX{IY%Qh)sg= zhnl05TBS_-VHXN!!vSX~5JR_yNT@W_iA*dOA5Z8ux zTU?s)#Llx@mht;X3w!jr6URz{Gr0bEw8Xcx_@~5sb?RPIK=aG^*Ity;2jc$An`Mh>3e?E?S zs+g+*y39X9YRhl1n4nk8A79bPD=6jcPRkor2wCV@PWBKi)Yb3ggab-1xx1@0lBq7* z>wA-vVICfIza3vaPgXB?)!3#-BCicuhCOZzKK&Ay8~NNqYGdl)r3S&=T6a42R;2ER zF}$XeVj~(Y@oQTVAaff>r#;l8j0S-!(O~Q$Uc25UG%MfO1WTc71xTvY&zR zPlmNK6QzI{xf!{+Kd*LgjI}1xJE>Q0oaLnF2U9l-8;UENiJndY4JMrj$FYWZtH;XA zR##UsuoCBeZq*odx4lZV>BcR)b}k^ct0N+}4?w5EF3>PJ zVcNs#`T;+bn1Ldx%?7KDf5>2c zImRv3VjexsY!9sTC>=r#O}TMf&YdN*8Z`ThFf-Fou?YII^S$n(e7O;7lR|56u>5J$ zeLnwb(;0SVE4@OI!bFuVzJK(WJ+kc%x{p>kLWy!&xP2Yx`1%V?W_dT%w~pE@mzyY5 z?Rt6l2g6^_52o`Py6G|Ft!l9CsJ|%Y}ME%L>58MO(%a)ft0ZqoKG!m6)F?|D{F8 zi;cK>LZUSpYnfdfF*whjvkB{9nuE%i8nLKEb1K?ndu9T6x!v-_?yMZX?SN_h-Y7Dk}qr<9S}eS#66cqj8U#hT*9)6VUDltOo@qG=0C@e5G`B3~Po|{VN4U#kxTzW9FA< z7nk>_&HUhP3@_K$^}br$n)2&qXW9bN8Az@)1Ef)4!uVjVmi+%{51t#YUseWD-2;wK}cI))|(5_L}grkUb zzM7Qr#@6TC%^3yVMQj7h z&PHn(JZ1}$x;NQ5 zp<5=@3=lXu271K_X8=Eab&nGLN`tKwEod~fdulnwB*>c<(buy#PmCpkZgiuds zMxeq%RSr(}p%_r1n`%r-PuLE;6p}b{@4`ImMc41Rn#y!@Vty#rzloO7H<|c@sGevK zw5}*D4M~s5vUeWSxl;B(Xn@EROmC4~+Mok|n5buUj>Eu%h6SCOHc9}XseCmNIy=e$ zHZ0Ic;%c6pQ8B!)d~f^?SGcWgjD)!)ZO>mg48h{22j5Tf5;s>3+M&)oF}X>?Xc_;V zSjki#szrrJp-Pz=a9D%EN zevADeLC|+?d?oDY4$BYqty5^p*@KiV9ngcC_mufG2(F4fCQEOkTrC$1GD#Cy4tq8M zWef9x8XZy9{*d}?0;3Z&cgPW7T=+As3z3%ib5(S?Kw^uFea>X(IBWB0gM*BRXylN8 z))alAH@M!m7-@sb-%faqU_$p~=O$GPVA+vvm1~-T&DJqem(KU<-ov!l?jO+GZE=Ra-F!BGi>(n zCCdnI5Csn=<#Q=e6L3F^X{uZ2{7nBMI~gu;ne8A&6YcR~dxYXQ2S-E1YG|rB zyuMHD0D@9EKsLTREMW0Fq2Qws_uRN>omFZ0Mh}Dp?ve08>s<7sXG5?)*4`J$hI#u< zd@1SIN)Tw^BNhKbfL@xyt9HJ5aZ@gN(!9-yN%YcR$)uWP!SX_Mv^r z0_HT$1;(YX$EjVcmOIflGO|u8>A!U=b>g1)2b@nzh5O|K<4$ORDgYMVfRuM_*HM#C z{p+z_Gan zv_ijD`WmG~fg4NekRfbcz@%R*kWMUsB|&~@7t#tkMI}b*OzC_EaK%`{t>MqPw^zT0 zLGRv%b^#-p5xhaCjEi%|3=jZP`lE^7 zrcA#%OPj#UOu{USn-3(i8YcD9u z$cF1p+}z^yi+Z>8B&Bvx!;lJ}=iDuvXTqe0j&CC60_=cx+z$f6m&!!y}LaQ8cCX`Gfya`0ZorrP`S^TXz0ck$e0`WJ#^6kZNz)~IgQiwRWwK-RbGu~*7TK6{@V zKw_}yI_Mp)3Ch$$37O^LynKhNlgP}w$;r4M%1o*R>il3kru!Ur=?$pr%QPM@E;M^V zR(L5YEr3RvVyWDtaNAri6*#vqxnhyKH;dcFFQK>qZTz3)>j(IYGX?Mp>Iuo!m7sv! zx9>d^LJ<h++l#Z(~Blf$jh^^5_h%d!pEVh6%bFov>^coO8QNZ6fM$YbGtuUaJhCtA(z;@84mh5)T@pxk&y_J)oN_2Hg-4WP z7D)ZLT(4&y5`4`rYhlsikSbvsr#2{yv43qY{i0NDnrx-*Xn_YBSN!E^J_03;)>Y5hSgCZx!2wbGP)x;)zJ?izy=yXKlB6)W{@e$MN~ zY4uq`EP&MA*NyktZHd^+1Cw0!V==?F?6xHebQ*q8z|*ABm-E1|HL}Y>g9~xSjzf39 zkOi9^Uzfm@&r|HfQY*|>YwO^Yux7OMd5~0%ytHR>-h*}nHhch3LSrjZD;MAJ5pl#0 zoW&JEtW&y;jz8wYXdtGvp;*0I4CmE+lDq5T{$`F;lSX$G_ZyY{K>IwL`uQnF|S=BGXgQ$e)%oG_p(Zl-Z!j9aqMy%zRqc zCoI(rF@KxXYW|v7eUG=CZX~L{J3&qJoxs1EkPTJ2)YeNzbz&}eG@MI(WtsI%(KrQz zP<~V*t92S@Vnq~0t6r&;lDDZSo9jl>tClUM>pd}Fr+b9X}2LW zZr?kvzf4oKW2xC5*VbRMio;`>Q-;#Ofk(ug#d)mRBHijt3bP#yc2ugu5Ny_i*Iy?X zUlY&?VdX_XC-Jl(qErkCuffPRYRYKdV5~YDDa{!YnPm|n*f}x#LkMQJS$zykd==Oz zQXm-hTyd4udv5C-$RSzC6KjUinKXO^LC#eir*J|nL7N{Fh2*!~LgIt@9k|Wtd4=$f zE~j0R4?(UPU*sHwtvQU|(E(|9Hdpr$hu@D%nBkx-;KxM#SyR<|Dyj7HBpZ$KSmI!ME1&%EcC}cfCphMl#?Ye6qbA5^sG8OhcE>wNtL=dkck0+f zQfzsDqp2)IArI!%Nq3r9D?x}pr19UD(1mL3DQlPlNMxy+MF9ZJPykXCKa~!Qd^`Eh zbWl-u=y(qshlc>DLi7uy$z80hp#j!Qmx^NAK)IRdzs*9*uwdd zQXal(=OuC5mm(z)&BvN#E+@I1C4U0fWFAs*Bt((X>OxngrK}+{u>5+QXz@H;eQ%y5 zj72cSR*U)F9JrQvh&gv=tguLEtRB3BDJNFe+dvb z_A~3s4;4HhaX^;d8CPofVIF+cW6ObRXfB<89h|PdDh!9%bAZ z@93T&trHtxGw`NI&PsuYD2T_XC_rvwqjWg3mh|VewlZ0;2#908nL1q%%PO!F^ncU% zbbh&Hn1VqV)XSy%?%&hx$vz}G|UsYe#_tseDL5wy04S$+oLY|(mOE3{@ z)}^=2Dc`b^VJsXfm@4iS4LsJF>(ssD1paEGj+9xj^n$~VHLbCrcE=(9@|CpeqE(qn zTlF9{s)>K3s_+J|AuoP3FQ80kNp9LD{I^f!V1y+!leYUjnLofhQT`a6 zASgXKhk!z9k@>bP>r`1S()ytG1HoKLqQ+0KCh5@Ot7FY4!dm2GxQFNDRjMKsrbIEF z2iyh7e0qF9fnFCGjA1$vE$ZpKkmoM|Q__f8E9q9u(TIV{Ow2d^8sX^u!A1S$w4Lnn z6nf4du&B=>AViu7dt?j~7~R^YC#yPY6<7tZF3c-*+N2s%UO?e;Te4n&w!_&jSev$% z@7hcs=Isp?aI(swc?=0Xg5OrKlCKYfaU`2s=p#JqE*)}Vj>#GqXKvTVj`EX-L zr>l8Dm9k=94Ti?!QAL_|A8Edfe>-h|2T0!PNasmT*L0>0UR6x}WmUV1EsghaKc+iu zz?9uyFvRZTS~^!e((II~0p2j?9>Y(sa#*?dA#k@tF5#DBP~a@dG}5q6p|;653)ZZ3 zXKDGnr(CqacS*@QPji7x(2@yT7;2E(vPSnHa~xi^e33X+nD{2L93CRO^W;k)RD64F z?)%q=y+v@PxEg)&!LfT(#I|>EL2XB4CUK#@y=Ufx`nooEcmJxP`btwo;lvN(`ce2X zZ{t~ZgVS2{k7>C$_`64#AZWRxmAhy8QnOxEKe~$EVIMqA>6fkjL9Ruv(O!w#Ri+xg zrNi;HC4x-=*wOB+ke^=~Vc;Ue=&VR`@HffLM2>u2Qz!uuU#8t(Q?r7vAQUokUaTT} z%Pz{6y8Yyyq#-J>R2YV?jXu+cGOUeuc~6Wp(iN`q{o!(A&=M_aoPw-YiG9?1H;lcu zDhilO9V7zizb#Z3pIFIgNAhaz0v%O)uMUGD7%3CFHHxAfZAw&k5}SMmUa!SgIv%%k zID;TaG_UiDqxUS5?m_F0GMKH!SfV0@bCq&Vt^a_>Z}eq~ex49I&?cesAy|x$WrI9n z{UF&Ja{Q6M26x9Qmd0!&a^FAJmb!(wv!!}RmT18-&ntu8+uN5&K~j*X%E#_zO3h|; z4_Wt7p5J#avLfCqw9ehm>2MoYM#*-R&!CK4D){tg5jj#C#`1_B_bP}jsF2sUj-GLW z12ay+kgA)x04Zp$`p%D0*Uo<>J1>P4x3=XcsF|W0vDt-g2{pNxVBn^smLNQKRDig> zGA~ND$hR395fOB_yN4T%P${FrMAI!-;L8z8g7%o18M)AlG>u-xN7#(l-W1g8D878{YwOt zsV4~$!<3Ma*aIbAYsw`#$gj4&a%A4MUzQ+MYMa7%9TKO)W9x7OqNT?X zn%G#Z&e5WHt*f|sp^^VO%w3I(RHrn|{7MsKj}p+|cLAMly2DFg6)Tc1o)lV@;k?7- z%#|F#WdaCG6i-gU1mdSmbi7E;;3EtDuNI@L@MWr@=JLv}JqQ)gCfm_fj)}TaEE2-v zV~N$IJwR({GlO1HiW4i2>#O4w(IX?k>JM2u&=a;havDZr56Z3SpcCx}vYHuw{dCl0 zmibP$_;S}u*?4>*9S&50^#pd+*h0V?+mjDZ4aQk4n(w)@VG!d)-~-6zO5*U&hd(lD z0@#XW6qj<7QO&0+6%TR7s0o06l3W8LtxIc&aLDDx=ucM$v|JW=w}e~+&B3i)?j<~` z%$Mq^y0c-apWrH{8=*#MfJdvDop9X(Wu_Fn1Ol%&W)aaHYKu`bSNgF!r5ET0`qi^| z`;;526QabGIB`>bGMA0(NEL^OTmC_W`2RMmaSG+uj7{Ym`vL zbtz?<gwZ9#kEQ7JXgx_5%yH@z?;Z%Ogq6(7 zhj9<*mF#$K`T|x^$7e!hN7PC?OuK68sS=nzZ(}wj+uvJ8p3pDa3Sv!E*gS#aUtM@C zW~L*rGC}kk1h^kUdcHjPEvo}n!tZn~#nnN?#XES}=Ee;=5}>)D>^mp!`A(|DEDxJ_ zBMc!q^mS+UlABl(zfCM^1Xp#kj?pZ$kYCW1&QaN)6J_1JVJ{d6Dy{v?-I}a<5G-0t6wZdH|BnZtmSoP#4itP#I zE8-+b21&A2RJMWi)c2FGFF#w+lVH|;XOi6D^>){9@x0(-UUht?m%RIze0ZVL;Hi#2 zCsHiAa>{Ce_gF5eRu@@MJ3zi3A~~(}FB;pGuF*3Lih49zGspwRUYpP9`9FM9=&_I|uItiaJ$E^>3w|B{wa0k=XEamsFD0*{ zN3nyo1}e%)XDM$WsB5bzlH1^T{DK|>btzHvi+uhj_xJ>J9YynDoM?sEr*LI0#xha@ z%efyiCP7!3yc1e2|W+E;#J0CJSVu@pPi4$&PlkFtjW$+ImiN|cfY-h zB?raAg)!Trl0NxG+2qP)`2xlk77<67)*T0PLEh~WtKHVsqt8U;IY4Q_0Y1gEW2Dg$ z=9$nyI}babJC(VYpi-($1O^JwsTI@~ohraE@)30bIsL9XenqePel0tB7l=Vr8s_Xi z{L>;Lmoe~GME}7|iNEXwY~3TGcbACFVnx+R%yTeby4T#$wM)%3*WzY>h3ACK%4NV`&dfgOSl<>9Jy|hih$F&kp9A6Oqdj}BBI&|YXMJ-PdR}SR) zI-(S?rEwH6R6x-9zcOAXD4HM2x{wul6Sbs;nuMiVb(8B-*lOVQO>GZR`x~KotB+5y5PPZYL0!4byD$ETuZ_oM8TQv^$a{tH;E-jPBBu zl6L_bVNhVfl7hM&+HRue^KdR!_O}ewcYXV0sk=crrF9d6dAZ+-hFJ3vhGr9WX!DJd zm{@YF8rPVbH`u2WSy0VK@;@Ur1xMnH8rbqDf)QP8IPG$Rc< z1ux6to1!~qOWIm?Oul5;&g?>|6F>jM)+o;Zu=wWWX!PIgJ&e1IMIL^w2hmIb07(81 zZ~n)|oTby!@3!3kt^GYwi?vt8IZ9BhSAP&`j8)K>tf-`Pn~a^T-?!7mVMM6M1E2%Q zFth!9JR5Ce#3dz6Y_%)agG@gDKAm2>2WB&HgUk-xQ&#mY+< zUCC1oRKe1fYnq41^$ENJWfa&7w7>GwwW#i*2bm}*VVAf{dxZ%ofP-_d20cla#*wIM z&EVTx`{^DYxd&k*9zBtWs8v?~OeU}{s_UHwT070KqIuR}QPm)ZUf;9+)`|(4-jR#a zf#%3ePI&LG;eMs&eL=;%OGDXs$soC8w8-`@8>mn@wxHaghxg$cps*-NZg{GiVa761 zNHqFD2jF_K3R#3R9c1Oru?2I^_oe_h zL(lOFyCd0%jK&$Sx%YVq&_55BR~A6DvV}QmD)%Q?SfB>)(_;LRDKe!syE$BN8UNA z7u{oA<^IesxH>W4kj|TKOtTgH9Vhep;QaN12p#_vf7;VJ{I#Gq#S%4VhU@G{9S>-l) zmK~LVbA~)xrWCT3PHV9IDb6nxyH_+$ENX{8E@IBiSQu3F; zl^e|IGM(O5z@PlrJ_flD30TA@Sxg^o-g;BZl;viIgo?@51Oq1TNEGGRSI@GlvC!u{ zU&7&K{(<;3TB?=)gV-X_`60J~pd~wyS6|hb`SgLtYxjOBSzz9h-Z3DiyLY$@VEC42 z1s-9n-GW`n{gA`Nu=RHEXlGrB$yqA@gM{l%(jsV4YD4I2F}4|a*v#*ydSW^pF*p+z zwJdA6RyA;OLZMHTvK5veZ4^gR{j2YI(HnK`AgqU^36>eAIM96ueKP%tPv)q&0}F-e z@sH*VsU9PV5D?=xp+D02pV2(HOkLaYyyUTM{9b&qkv`dhRXPBFdl|(}Y%0iK8qPFt zqyskmy8$O2T*w~{IWyBGbxyJtv#H_w%*s<5R>JfVFl(zq0NDF-j4h-1=*`cLYj_YQKuGvKG#yi8Vr+b!u2E*h`=UeD5~i)N#Gz< z3Sk911q&>nol4jb^l4elrnx~Q`Fj0j*?91qKAV79QQvmqE$Gz=YXSi!&k8Qlg-~nN zlQQp>XNF54q1*|98y1rzsFdj;%DXbX7=omCX-$yg3(YrpyG0`x7?XTGlD{;(yi73X z!cvRySI9VjYeT{6JPw-yXNhu6FpST@LziDK#Aq8hA+GwM4Nl$I+lC95EK!>9)wJAOTRD0t+L0MMUVKn{g-fL&{foGq#A&QrtICPJNj|I)TawEYHu0lZ)^3O3Oj#Oys~0&O=Yqjb?BnV*u$A-7*4Jw51l5{6TS3SD-m70u+;u`LbC&e2J1%ln3fV>yibf1k%jfA_D z08-wcc8p%l`(EzB0Rnwa(J(yu33p9R7OhqAqDhzCAw=4NWf6??{lt@Cm8M)%rpxfo z`5BLo2txu85Q{c<0OJU1bE`6~glTP87Ox(mi>M&{KDTNKq;>axSjEJUz7C z@4l5@&c|e?YIF%)%RqUI2`V{J#oyQhDy>lv4E(Atr95_}-_Z{8l8=B)vxyI~q$V*( zmTsK)ia-|(*bT`0_Y}v32xb$!S>V*UtT9>7-^Hlkn;zA0a!gDCML!dQsV^7+2mGUm zxXKzOkt&3pr=<G-qsTR6GfHI z*A~k*Jrh9`j8=y)KUq34{J@L=h+iJZYA-U1X7gZ6m{C|qvp@B`I>^5^XRYy+-3^4? z9lZK^erVh$Hgk9}OPK|T%5)@9SQ#B!)4vIM4%c)!j?0gq(}pnjZ7?2!j$DZ|Nu5Fg zkWA`v$VI0=8qWRFa>yhN<(i=?T&jX3fd>ol2ToO9Vw4UzAzb|4zwbn6{scdg-p8`O zCULbqJR$`NYB5Bkb80^CZ@B=0osgIGHnx`!K~*G}FLpIB zaSjK_VyD=Avrg(w?}{;G0$OlPZq4pmQ<;WjP%BQhgd?&2ab-x^q`~I8YyWw8?T#_#n^V&mA)FtGm6LjAR<}qfv0!4 z?B989WG&}VW`tL6MCnws+nTcPwl6P*1!M~S#D9!f1c^3~YdrhLNC(eoJ{|2OdB_Z^ zx{~A}IgUQ;HiOxWJZ@tLpY$?#M?qhzBb72&vUCuv*lOx^QuT}Ewv~!(S1kI~3#!tR zZnoW1r~ycKZ$P+2nrMFuvXUT-{DaT{?4O>G8ur)4el;eVT1qJxR9en3bHCDEMW@RO zIULvKl&L--kYwvNLUGILxHk#R*rG~1c%8)!nzO5XRj=799a%wFxMl24kz0}=TCxJmwV`#e{n~4X8LVixXZYF1l`q8lz6+?1vQ)If;zEp-((iLa~Uic$T`1c@5BpFr#8PF?-t^6wP*=wV~m999bq^j zYPC-ziWsgY0D^@n9>W2jGnH&4-Idg_dgcA}2_wpoqx+Q62EQ9vKf%FlBzQfQDGJyE z7UM4`8SB3yWigjWdcjNp8psd{lrwtdD#`BKx;wJ_%h2k}wNa&MG4z0h*X>2$LW#tf zmm>tAMcfgC=W)kaZa5w~OD|E8#-4ex%P(t~gDVnk7n;SPUA0tpwUC0zcH7g3uTbuT z!HK&8lO^m(3E42WJ7>smX6x(C<0h^~5D4wl>I=d!61>s$nE*$(b5ZgL?s~VVsL_UXU zl?-WxSuzT8Z4BzW9f%AY7UvgY6Ikm{H}lQ=5AqZ! z7DzJ&fu>6(X&++cV~!_}0eIHIZ3iM+@@H1+1eUBWCA=cJ7dwi+f%}uAV>u=E6&XQr z&(C&zjD$u3wOOZ|>#`7hK`zF_;0LO4)1Q8h?H~+%TRZtU9UI7BGi0SBTd5yh|BeIV zPGP#TvTo!_U#Tfm=e`Qh9Fb@2IWocR906t`wnXuY6iV)oRp!fGB9V>Eo{Hn?C=3dJcQAC}RU1%f&3pHTWi;OT@{m{d+s2nC zRFktEZDVVbq?_oD8_Knck2}2!mPR*&%Yyn%U`tN482zO-L*@eUvpBKsU)C&IT-X4~ zUc0?}5k+?a&5J~ZmjX(o70wh#m`<20u9rPdpDd`CmS{Vtdwi`lmOq}wvEs`s|HSy+ zntQi&d+${jo0|q1FtP6UB-17dCx!Cwui|>$W}vuG$GfF`bw1zPst37{Ts^_M_ay0W zxgWE>d~o-)jGUFSE2AB2T8?%v=@64M?X*VcEf`J%XQt}SaH33?t=Ra-)$Vl<1QyK>uyYcD&!upe(D9W<6qWDiTbujyMK z4`9ics zQS#7=TUm10_dNN>Kmk~*b4wShqHb{0XVz;aO6?rzM@hY&>(@9+#x`3lpWJE6%Um@7 z{0)YWUG8>T-9E4tnj_1JR5$={I_<0DtGd!fCcDjw5aMX=;j z=y{@4%DsId)kD=*Wh)wa1LfcZ*%0`slp5BP0+031+K*dc$d2)na1nZ|Zq{y*bdqY& zjiq+X?px*RIA3G^!6n%L@MqN*a6`XT_3uIJ8PrP8@aY4G*dVoDZ#bq_mpS~CcBQbCuP{u!CR9~g&Ji1`rBq`CUm?ks=gIjaHsI4i`R^fyoxe>(S0j$8Cf60 zl;D+e*m(2mCgf?6$*;+$spnV|!jjN3YfGY1A*gSal{%fTALQFwJVKN+x``25G>Y=R z4~i|cqB`?7t-cQJccV8du@(OIAd*M?L6Ox4=n*)%g z;Vg*M^P1B+onFOivp?} z!Kh;d>6oFR9X&-Y>4q6I3e7wHTaLd_g}D{CJ<@aUjzh;U7MVYKmXR+)cQw=QPlEhh zsLSAHu{Y_pg2K_Ix);qWih;H<3&4KwAFgfudxnOaly>0A+##{cTjGaY_hl z`f{qNqP$b&GM999v2VztpYegVgI`JrG;TaF*VN2L9A9sxAI>NE>$=a&wCBs=9m;Rj zYTP`&c}(7JBVF7ViE?r|C5ePuqm6bmtmcs>DnTz`Ek?=HcpNSA=u045tAr!x*p5@| zU(IuEMn-xVrhpH2w${0JW!UTuzx$LttV5$|jB1TD|U1F_M#yqIIR)F6Y`lck}s}apygK3xYEta)aKu)@7QL zs^@4=MELxplR}4&>ImoCLYyIl9~5t1jkR4T1a;G z6{nfw$|lD%R`#}i&Tqm1Ns`|HDR^g_vBj4%N5{&t z+#mhBoKOA6BJ6pExnq27&;4HZB!Eb`A&d z`%&?EH=X9YUMLj+p}pS0e0y$da;1;$_oV-u!1uj{RgP@_SB`%l%grKJrD4inQ)y;G zKC#D+dIOF8i*7Jv6XE%1@yhYfl)ceW4^Imm$Ms$vP8bDArjyO6S?{G+)^ycb^YnH9 zhuEtfF@C@>qys|K<~l2QMDElhg|y<6#m@5Yv1ou=gw`-HFtWWHL#NUdqwaB#P(^*S zVs2OZp~_ZQA9+**OKpk$kC^uFsdGlk(qnqL={ge#fKf4KM4{LM{F}H@gvc2sA zMoDUwpt*9+9Fy=` z25DJYLYTu`0pOEV^4u(lNhW_oLBICB&pT>z_Xm zyAZt^sD5f!s@0t{+`WgFAYsqg0VQa8cx!9~!^z(-p}ovgA<#hr(89D31c;i~#(;fP zmu-raG_~#EiCp#cqCurjRDpiYg*qr3SSz;ATIJ8GmMiatJ39jA2JThM8)ZtHI0p3( z|LOhvzc;3;0gFegAOQf13I0F(4BA-O{`w47YiQYRaG?3->gAjJ3#?ORJT6rrfXH-c zn#-(#|nzr9Z|gYs*#r^n{^HCW&;8T>=PNCob-_`<@gen#e#)Vno{$9hAn= z_8bU>deFIHPo@BVw;O0jEG*QOkm@2uOxzZ2X*V|1+=fK$Ypl+n;j@m&$xWF>EFXou zVTIhrP9V@_^*72m=QwKvKsWaV#AN|suMzJHW^;F5t@vYvrbdaMBTUUOzl)WimcTnE z&#Be!lQ+vca+bbeN-|cjq!o&WIK7Pc`rUI8TJ)bL@GP4C9P&WoE2tRlG82@RB1*Pq zNpzx=8|3!bB;n075}Q zMIsWo{dG1To(eBi$h&O3N|{ZtiAK`!9phYo!YM5v0+n_5)!Ab$nP1Z&)~Kpmz2Vi` zAGY4!gF+c~7rOWOdrTdZ`eJ}Y6!U=}Y-`C0ELq?o89eXffj`hOJLAbl@=b{fISP0qM4Idf{_+L92M z2`Gcn_d&9&B{U2foA7IlmE!|Z91;*!gM?N4vTgXU zHM$Zpcjz6)qSZ3UG88Q*YeM?p5LikPZRMnjR6Wg+&x~Rl(awN$RcZ%}0gJxry2(8G zDK3n_1Qe=8RQw2pOYjEnA+<7_!|u3q#@`?T{J?fVs};#cVd2BE=?93!V12OtnG_uA zC~_(gX@$;S2`9QkdR-DHx=7WMI#@WhYs7qq!1W`P>i*)VFMC)!UWMK>b6Z+}q?dKmxOlVxyA!J;a!z2Suo*@;!1+&O07| zJ?2w0FJg0hyFZ-Y?7of;u9jRr2OSe=0j_;nK#@!mH;qE+AP^q`z#DcGsP z7+$@^pQP1|_;ahI@P?seN##Q7gYQ+M*Uzvj-6=1R=-w}%DV3@EbBCZAq?+V;s&XSR zFf!LfB@d|E9u!`BI$6MX;PL`L^p`fM zm-d&*p5-{kQnOyB(9O<$x&0l$M;vkm$;w%M9>{%*nGF+Y06+x|HErX@KDA%*XoWAX zuaDJ!XX3VC?k{W+>6b4sS2U}Ib=Cw{CpRbVXpFpTgTb$|$SZm9aFyefh4T_}f96vD z7%2*XuCm#3w}}ba=UmK&TKj>oNi6(6U9^rPi&e$$h3MO6bO{Odie*^&5pJ@f4LvyI znRys%A@FNvm<5(vd{BI@w}-LpG)jb-vwVGf%l9cD7In$@ynHXDV4q~-<^RTUid!7j z%jg}MmH5X-Y+xsr&{A1X3Hct4%rlgUO%x1-6Z2CA37!;%~9VJ76FpnyBab zq2A)rW&`z!+8$_2j>{bn4p{IG4Bc2Fp-hFW<5c{yM(DrJ>5Ab=ge#j7a6LAgu@LUh zj9`!D!HVQw?I_HT?2m&T&wc(}Ir@b_ZY{1DFE-*QS0es7_1x!`~4h z8Cex)lr_3~=dUd&KgA4e@}#aI88v|EV_=D`D*$>L<^hq+Ec@ztl{oI@m-uSIFAPbX zQ*?^XTyGHPV!s~&Wfgz2V>n>1T*LZ>-EoNDYSQhB18U9HO zc{$+W=B025u^`H2^jMj+wS#VPHo2!q)KJc)gW-b~TnB>DJv3 zJSYC!_363N&Q}G#WZQvqGNXqS$govwy<7DH+taBYN(-kM583QH)d9IdGIB}otCRmo zUc3UVR(YyP)?rZDBp{IS^`auQ!smniJ`&hgaFGKA21Xz^-OVm<1eIz{6)+00)v8k%>f??^dnoh6(sFK~q>rF~e zcSdbsBJ()~h{s8?EzVBmaJ{8%bT?gyKMutsUhz|DJ6;r&^^aZY{P6V{1)d;Pd*$uT zI^gu<3yHtDUmJC|pKG3~z~3>~B-`+^d0TCvr2MC;6r{mM&s2-bBA!nvn_X}@JqrYD zi~v~QP!%0gQ$Q0GGt0uVc75-X?asoEgt}$#;;jM6S;e>1e5aP7NPLl;CI1Dp=W0v2 zOY5m`t~OG*%U9KaFa#;^rZs&{%fd8CuOi*T&*u^RUhu%Bu0HvC+de!!KA%z6+_Q|? z$4-eA^M{R}|AyiaAQ%q?{k2n1`NezwA2B%pVW<9I7&!mwG2fibXt%+D5c2XJ0nGFl z%(~qxJ|B?FnS!e+I}nEaTp2N6B8-O%F5>e=5vjOCwkm@bYBf}_*XxD%o?_*&SP3fNJaV~W1fvx)8@QJP>=itQp z4$$OdqNF8Mwz_FZ6XhJD)Vh745WUY8nw^Bv9mr&qkz+oBM;$m9$jI)a{r>Rz@YA!x zU1X?F%8J2R|1`-tWp0O@!oDBFq(@lpW+I=8?7f1kAwaJL?6hB$$WwP<;~;NUeBX7ov6CeGISH=wwNw5(GyT_{vOzQ1`j00>8EB`#pTVePCRW_C>HL}{U&ObH z@a;^s03(aF0>2p?GY)%xU&wrpV_SJI%?KZm?(|yH_H@6*JI;%z-=ip0%dgu7k=B#;-j|4Wqh{c_#_^XJRBDHtR!xg5Ac`r zvlkWGO@~Ti|EL~Rs%~l>UaIIlgpsc<9d>dQ7S3s$e}5tWSIA4La*t)fuSvfN;{R;F zU}fNFV?bwQXKVUfNOY^)C>^jNd{5WTPWmuthi3zH2ROe6?3qs_;lb>^GiAe-u3m@` zXw{G)Gr4`fT#w)X*{nj`i6zsCvi6s_T3lRVSPXADxQ(y2itV#%r5)r_ZrW z7L;>rVgkjzq_-_S(BMnzy&K99;Z=@WU&t^%gW6g8C=d(<-bYXAeVe365oR z!TC=kdb3){g|uCCWqOQ{`pz9c7*>Vu1Rtxvyg39@EZ%6R8!|aZq;{7wr5G`G*6I%P zk$A6UGVHV|m@M>0#zn}iuU)8!Cn$X$-kPlL@$Wzsb`(kROt8*W#zdM(K{5YPvmR`S zW}Fxo0%1D2RdFG8{BZf7c6u^2WzfOE7ISrZr*6`J#R98TSAB#;c~7wJmhH0e97`E9 z!bsxisN2EwmqZxLiYsmO(xsEW`=THI-q!fjq{b3K2<;}x+2{zSn}#9js)yN9d5v;| zd#mYaf9kY4FfwDC?=M#7F5>=S%J+HauP}Pp3OPl#FG;FG$QOcwhV&@c5Y1{5%sf5ZhK9afJTqET>N#E&$ zV=B(KVGxVi#Wc+BBE9_r-r5^0%5i7V6K)Ra1))U#%52>BAR9{Q9YPurMXb%^x#%Fv z2%@dputB$(FU%}^{^`YDX-cQEQP6}UF-3~KieIMPLd=v_mh>#4o-EfZvIbj(WuR}( z;D>6QsWUOBMY>%Dw%WV@tSyKYu)2`I`_ek5gooBtm-QDU&n(G-)8z*ib+B z3boYO^F9qOo$ff+rd8muIZ^U_;yh*l^h3YnSKWPImSnNk?!8JWc3zT28J>3Q%(AS& z++3!R<;Wg7?As%gO6^~06(AMfbF%}w2D?iu&BQW@Q~G*44+6@1juG});QM?X%iJ77 zzb(dsGE1L?(;^l=5U~7#JG1SqV)&j@Y{wqFw2M(u=mhgTvRibxjR^u{>L{aLVQC&F#;40mk9Ga{_OC zK>x3Yyh0=D)belo5cmsw`aiPP{v&<(Pte+bCJtAs{ffVd!!Kgk^|XH)h6A?-7AMcy zlj2C8i+5rf%POvzpc*I0zT+{=}XHPlC;vSYaAH?K$ zpQuY=`SlQCd>CagcLDR$6Fkny0=J}4#b0qt>44!u!t5V24&!E**3;M}u^R8~2im?6 z$`Wx@6WECSI#fCt+59+Fg-aw5L+$`3TEShLNhPXh9T*)p65uV>%c<(=r@>A=T7-Yn zvi|SwuNu-w@@XRuu5A>>#E#(pLE~DVVx>}Ob6T$JFTi<)!He@REMG(csY55k$R$L~ zAj5>qS}BRFry)9pAmvXtuR&j%NLEU!}~U=njG0JME5W2Dul^2<6WJ@`P^tp`RQs~X(=}ECBfa296x&} zLn@iJ_j;s|XQQfxI_ZPw5$WcaP02u`U>+N^*Q;?eP&% zQXKr0a3p!G=H#c!ff9_4HPFfuZhoZ0G|W%Ua^Iz>SrX8H-SCoA3Vaw?;n#2Z(6S2p*w6EBY}*D-4Ud%-|8E z0)vIH{-<5nXcyd7^UuOR_b%sprN<#|_JKxVu6S006%KVHW*= zp(g($NE}TJ?Cq^RX#d|t?*ELK;>3R2U*crwjSmzMw>qF=@e4s18b;k8M*S;j1~y!1 zC6d&Fbr8{QpM--|+4<%^*E)lV&+?xi3N;idHY>#;`hP9WUU}J&hMQn%T)SD*o%2Bur-l_x^;VBB zPwM4{`(o@3%_FJ+k)S;C;fx3v;xv86u%Neac!m1c?@qXX^}+`}i`<2Y35GT8n~+yC z61w$Yx4eL2rcFTByl1niKt0*C?iFf;fsJ=6+?>+}wphrQg}m_|m<%l-b*z_^O+`@i z@8(n~6C4Ol2+h-N`LX0Z0Ts&rotg=KydF5%BiLIo4k)~0ete5uD8z4b<&04#tl zU}Vw_>oY%K@XIwW)W{IOL#3eAo{<))aBj}JQvF0NQ~dNDj55th^=|iiT|mf*ISKi6 zom{~G*E3W^G^?QP_Y4*OEy1|||NS2S4-)Y||BY@Hncp%Dru#Os?uJMCO@Wy=*qg?Uk@K(S)DufXiUdn^M=wB1@KH2Y+4fIw4oO}Vu-a+_svV% zfxL1mNsKeHv^**?;^9$wFtnv)0@&UIvQ>Qy1EEP&Zgk)*E3mqzix|`QIk->UYU4QF1HtBZm|e$iPBg;}CQc%iw{V42`$%RyV;29# zeO=E)etk8`Bdw$X0vkPgKcxwF6I$rt&X6r*k$S=ErWvcs(gJ9j9IP%8kIGDv45vyA z$b8UA#LrzM2$fS0Emv$c&p85Ds-cg1`5N@vM9Fl1?&dKc5WWPO9V@weS0FP zta~HH2vWhONdfOlsP212Ge6yUnf11Lb<|#XFf=+ikv3uoBsj`~iAbC_fH*g9T3q)f zpjM+emg}?5NIYCZSAV%8PKtEap~5$VVT_I|x5nRl5RPYtHIXn#n#O+Twr$)fhtrX8 z6w*6pKB;_tcea(YdHT(-Xx*{2>U)T=T{3Yn5jsZ`J!(M`7%%78=xVL^q@ghbZ=~m} zk;YZb#t}{{`DINnVVreJ$KAH^wvus7h4ZEz#?CJ#jq=fsVFL>lLzh42x0P?Y*)*-t zV|+igB_njU1svp#5OI<_A3px;lt`bmv241jbj%00O|{=BPCJD-y5?cQW2h&1RiG#&>moAq7CcU2z9-2*t>ZVE|d zC{$#&k*HKY5YjXJF*11?9Fws-=VJrQBlENWAmU!5^z8Yg&{8?M^@hpLQSl*@``=`! z$)#z;!{0z4|D{M${0{-)XlHHhV*kHNM+>vx;&CZqDguZRX5{7prF|*cr8_pZVN|gc zGrS1D36_HC#g^(F(agnTY-vTnKOO_xg5rof zVvNom(7VYKjrzIM!BU)>{E79h%hI4y&jf%6p2jrAZF@KNNepAbwcGcv9R5{HZYsmRL5r?|htm2xZ)UnDxMfyNHyadN-O&ZjD3N&`}PUOXMg zzrn-bwNKO$#?=`c=57wqQv)agdpg_R$HP6bxxIYbG}HxKjPkiqyJ@EfTeUTq1Qy=q z0Um033aAgBol@-ev_o#Mm@@`*I}J16}6J%YXzdo02?dAV16|Q<#?M!p%`^ zIRm)=ifCIgc?d1<8sHrF)G4o;H6HKU>%Jn)j>b*lLJD-iP$$t00@>~tHqLgB4LRl4 z&%5|DqJRy?wj4NsJg}XFlv;uxgat9jd5}XmR^=GwG62e3Ajmb4~u6Ce~jH+^SFX8ZS z;Rc$H%Lay4Iml{PJ=zBgq78^-bZiTd!D{1Vuylxk4yU~U=8vJ1SEJ7q9n-|+GAg=G zzvyEdr5WaNwHu8K5Jn9&3@kD9BbD#I^Q0%dUL=v?L=#=P(_i6JcguQXlhgl;vv-Ql zELzq;W83Q3wv&$j*tV07Z95&?wr$(CZQJOZGwwKJ+_N9{!+n_VYtB_`R;{WJ9T(|N z2KM1QhnJ3@*i9;BR}9$X{C8{Py8l}IWxq9|9<%rM%|Tao6RO8($yDwf~00|Yy!I= z4ndItp=>NUJRq8u7_BUJfDh?)hh-7@;`UvD8k9;EiFA5_#}uN1QM^WKvSCA$vZbT5 zE#xZn60O{f%>2&-{3k!~Ec)S1H5|KHnOPT!SSSXpAgS?I$}=SJ^t66BIJciO7*TW8 zLlDe3sC<;XM+@v}MD-{M!1nju*B7*&WM$@vmmo;)yeEGsK!6+5Ujr3_8#g<|Y61B6 ziD3|Z!P8G+dLLz+Gw;3iQ=c=(YpE7CEM7y`_C4poU22b2?bB~GNjgDUMGhBxOg z8W$ndSat~7YiJz0HELuZ1goFy*3=a!!t*$;F|M|i8_}2y8;f-UTJWLd_FILLTIe@s zD;fnn4DN=PW-~gz1?Fj~loS#x;0F{}8U(%CAc6EL!iC-O!>tG3nES3VTX*SHVa1LMk9>bM6WQt z<~yCq6eEs)Y>lnwVYEoYKp(``1`dG^$kWfSWO(1ln}SO<nT0`Gwk{w#_ssMSWbK#e{0RHI&lX@koq^t9Lw(cm?=RX~(hjc6& z(|F^+_S(p3rx*dOZJQGm5 z1$S=SDlV+zwt8eP3K&4wo=_~jg`pIoWPuPU7j)g|Tgt_9Lq2co;pVD&?>sIl(PWk% zGF0(7w6*L3cFgxVeA)rAEgmb(9-4$Yp@)dMJU!}WVop&h?s&_?P0G!V`jwx!3i1_c zoxarx(scH+0TC+~FKLUiS|XOzmk96EkN9dq!5)S)ADWRRdN_j{!J;`b9fOo*5?7Y& zSDTZ@cy0Q3XZI&khn3g+Zj1a*!E^?{Zpz6=#WfV?)reiV zQE>jm#hT+t_7&I4MRj|%qSA<#D}8Lq3io3#VJ(dwL(ytR4$85#VOqqIKbw3{Bv=Qi zz$PYt+GjH*@59dVLNtBl<1DYI%q01hGusR;E9@=)Wv(XT9`wQq)6{_gU|(xaVBuL` zG-J5CJ3zphA;&naXd+ArQ9`)Ev}@*vsCAxE5pz>gfiwI%VH4fk!)uIU43guRaIq>2 zdDUY+D>R){83QJA2cGKw%(z^23sxZ$Cr8n&0alKZLrd|fDUjv=nFRbIL zUH0Zx4egE6$KDY`+?V$BgjG&2t|=HnKExL3OoMRGu_7zBF2-{g;Hb+{{8?i<(`H1-kgfpSVvvk!t~v=W^bu)Y#D;z zJyQExmfP1J7{3rira=%+V%ZoHK2GRS35&Q6g z5nUGRB};WaLk8$?!$=hL_&eoH2}5)9@q@eLcWqkX038;M)I4vxO!M;~Bc3f^YtNTk zMT&IIycy>>2Me?ls-Mj>*=!Vfz{Wd@Z`cZCLu<-F;R;74!R_MOy2EyaSMqQ))WTg7 zDtu;}I%wrSsq4H3p4i=N*gow!ECn{weTg~qj7YdZ-J6FG8~UvU{?M24b*IPExW+&l zf}ipi8&~8(A?vu*r$bcg>?~szEV3B!HME$)F86LSwt7*1Pcxl<>e~3WLB(y)+ooO;C z*+ApX;GPJQh2FJne5+t{v?x*15taO0@kkEnZX_-5FIZ@1o9evik=>aBFM&%{&e>RDD=D}&t>297^@ z#~)GW|mT=-_|$nsLW)(QH}*A2)T+eYBk`EOhY^K z#|;E=G4jyn$L?1Q^3UimQZ9!>wHNOPFPSckp_V{SCN!)MKXW|;vjU=F;_t2}+H$YC z$AC6kIkKM=`j1JFc@i}6@1Z1WIHdO2s8zW_3dk41Sco$dB;4o`-p1@J!Ck#U%}DPR z=0hchznEgnQz8`@?a&;ZPKaRyKM(|iVbC1-BB4A<#6ZEUS1?f$0tyxQ_aPghdY@68 zQi{{Ed%cML^3^PPU8WC=@h=3eUa@~mHCBb`+*?j_im@yatRRFA(0zKGhl-Bh zvHW3#s8`vpPM`wF!{dSJ>zx%Z{g2Piz$LPN)PI4#Qy{8EEjUxru`x!whTd2#LRTpx%}K+m2Z zbJL>xUveaO`UrVJJ#;C<$lcN-^U|@9O20nRbal2XfVeCXcLyPSAjnG6^l-Nqtr|(# zQUrOvlpF_)OOqhmQRulOdnQ3T9HXys2Sh9h&I^;N5+ZBY-9;15ktUPBIh~iWye4>K z{C)tj>FaQNy0}OBq=WIG(wr0_CFmxK{I$N?8uhdx9AFJQP8G~6Nc)s%wwoqFLqL>0 zN9JRek>;Hc2sC;K)}%vWb=z5ZCJ_o67<4rS3!<@6zlyw}QDrW_lIgnBFw20gr>egc zT&Gqz>$@B`AnjAs#O$=`A^=JyV-73}$q!YrkdD}-mA$;&3Bys(ONZ4eengp4IonbK zv`a?6>V$Cg6l-6SM3BXp`E$Sf>oPVE%l{~BiMJS9q?)rqwuhF!kV23h+pT%0CfBoKROpU zZErcKaWZ3bpSc*e_pTDKewbbY%^)OOrvo@Lb#3P<2Rsm>S-=;m?!*x*YVlG^$KR2Z z4tM}YA#n>(fOSaXo@B8*Ygo7Aq)`F59ix8ibh#k&v=H!wp+JNBK$`$7QuYKL-b&DPIG?ruM5g|8Cx0 zEpymls+UIAgG#Ja9dczK=L*G9#5@?2ORv4lA7*vRJU;9jx$z4tdL}{?USGlDTQ#sb9iFKph zi9G1H)SvdNRbYPwtdH?IIyn8@t6sIF#Aaz~MG95b3#ib6AnvugVC(IwWc?KVu(Db57>OtsiSoV^I1$~GHJ-}r*tDFkf5*c7l~ zzdaHR0=Bb2gZVo7h{tFT;_CtmBT{It!(J?-glu;4Jv3_;-CNq2QL+S`B+Ep& zNCn9_@-jwj3laCV`~;R!UDChztf96gYGfR_=JN_ucvR|7{yuMjxZ*@QA}>jIlxx|6 z^E$N8s1!CI-e`uFs`SzJ3+_mr#=A!U4Xr0EDy|B^`0dLDlICd9jmdWcsX10&!jy*L zv}=YkSzpPCsZEVLZU{$h+>(KO((p3xNM!<$4}^H>9K~0I?s%9Dv9d6x3jJT|Sq`agzMV0~V9I2?tPNph&5*`9^E^b>A zqSm`ZBUszyWtn{6EuAQ~$2m^PO=oFx|D&oK)2nw#X1^K zqWnWyef=#u%m=(Wy1YKLQ=V@8Mvg47h1Sa>O@qK#I#|9UUryP1}ZzG z%MtR6(evNp+|Ru1uLA>v%eK>cYhFW7qIp@Nl_h6Fxrk3k!F<6@-(iV2RFBeotO`#e zOC{loAur{FQ=FYnN-WOebU4zPd(!S_KQZftRIFR1W%4V>X44ovT{kuwgGy#yF-waj z*-30OqMZU)f%TsOs`Ft@9QGF!YPQ2qL1$66yczfmsHNN@@+1lczG#j#aOY)ja4(aE zoZi;Ts+4?9$mRhm_KEaluPc`7A6#tSK2{k+|+fYdYE*J zDmL}UByA3HHyV1p^=_({iS(DF1y#GF>DPSZ9dK-Vmy5Ku`t_R!c-DMaUh+4o&5Iex zBnsG~fLQj_dvYambems&QmVDlr^!2w?Z~B&O_JYRZd!R&MzlW2xww4lA1jYE8UzVT z)LVMVJbD|5)f^ZmOx2hfS^1Q8%26WUT6?5sqNP=`%;6+>YCJNZ^!>Hbuai!dgsTJ0 zXtmObha8-WSCi|(lpppA*+3I>wY4Hj6P{|yaKga?L#Cvwz1V7u^WP9}D2dOl`G+BR z0)Dm>i>73tEc$52sJ+Im2S^IchsKT{g}@K#{f&0&j8poh&?tEWj&Z_B5*9D~$QG4B zc{ab4yHr%sw6Ot3X`6svI!G2$HE2gr#f7)O(IQwYiP->+I<|gyg6h0F7~@HKu+^RgojRbbFDIKe01^QZxQNeh0H&UF`iXP)FOf)nFyjPoCheUaFXPyL474^1WI<4YFP?*)LH)`ZI4sO*>c*m~?8E)c^wcNq zoC?OXgU0E4b@|9kellg>4l)P85 zh=naVHo}*N98DpES_n2F3QXgyQS#t4zBC|+Ew^+q7Lx)qS=s5}M67U_qWBjn7yqg? zVc-KLsgc?$6@d-@YI7D+tEWPis8pUum{Zf|8z%!(h+hf4AG1H9w%+jFz-fk$!iG~o zRp|LJk(QVem>AHzA7$DDcjL(~)hz_P!zpMbNks8j z)eWdf_G4{brJqrtBbn;gl`}rvo(q9M4AU}B;bST)uS*9B^dZVlvc8c_s;qaJbRS}dDFRAfK~+|so_`OH?_o;(*rB1`!NJ=%6l?7^6HeRF-qWC3n|)B0u?dv! zqEm}aUU5j|3p4;P$RUpaFGhg9hp()a>43y*#QyfD?10p6F;ng>$5z$}#^L`LW7~4B zaHW%>WrL|oO)_<-OW^!c$+Wbruk`Ti9VBBTDH4T94Wlg{nnoel7!Xypvh>V^X@7M^ zuB;boUl3Z?+}5S~tgl?6^(qr>Jv#@?E}N#?Q#swt&&)(ybOn!=Hk7kU^1VUniF;zP zhLOK95mJ*dSeFBt_M8|F!{JVz}NGMki4 zMQSe08Wt|lY*rS@yr$Dx7^3*~Bo!tew#e;{y&ur196AS3OPIVY@>lY+jz9{&1(n79 zPE=A(Q#DY<`5ynnJ!Mre`A}v>{Jz;r_ZP6Ao)D$48K5;0dDB@nRk3BxK_}0YAt@vQ zEvL}k!f5#ifAgTJM5%SXSUt_M(qj-*MCph7XO#NDDKTxI-#`@}gUz1C)n~)`b%n5! zvPeMK`SwWQr@o`k4X=dwDMsG+HnpS_HBpjl0VGZs>A0OsT=_nppt(673W z*L9vnTI8M_?T-D1?j(L_W9-95Us~)HEbLVdDC0sgAh>v{d{akU3Y5idul4{#~` zmhJDhKL6y}ZtX!WmREU#~$(3fj}d;x7n;K5>pP3n7BHSHv=G+xe8a(5*U zN`kxrt_)fMe>f(xR@5AU5CJyUF?9@um705|t1eTUxLRJ7?f04`=upZMD(CaW)dEH2 zWGWgy&AnMbp_!K5c~tZcGE_}ry)b!+b#-6au1#?Zus@JodNq~V@48>Nc2qzjJgSvS z&VPdd5F5dY!Jz6egVges%ct|(5tJg+a*g}HT*J~N9S1i$v z2w`J8b@4F2ON$n-+)!f+^(b{`tng~cw6qRvuK%u3Z9Mc3uw>Y(wCT9@VnOK?x|NZ; zEr?nbZmryQUA!BSJ?ddjgWc}qqC?N|e(4dLSoxeiW@|j@(Q^(tb%gkos|X3rg-6<@ z`~fb1TB!h(90eP2w)3~63~C0iIx|&PFEqilSG^N!msqgZo(5q{OyMeewF&UC!b=%Y zn*f_hY>#4V$G)o@9?2Xw-OZ6LHia0z*&j;)%R z*hu2*dg)qq@~)i-qlyZ^8#LYogucFDe3yDeQzsfN! zs-2)E$UbTL=)v=vFq70@;+3!I+P*)g$vzpfd1~WLSYyPkODLyg^#DBVJ11{usO#bO8Aw>UX)#|24$kCZ1oQat+m16JdV!Mo)O?# z8U4q;)f&=IB+lRdXe#%M#zW{$eo?8HO7SA~QPSg?YYYuq4bO)S54~$KsJE-qAo~z#AKGg znXvOLGIP==m&~6s0^eZnc10$Cl`TbvtFVcGkq-~-qN4`ZA-syGs!LK}B}}jh-`A-m zs1d^Fvrc$>5mOLyD74OHN%r{$hcDWxw3p?bxw2rSycsx!x4_Xoy=?mAOpop9M!J(r zQcNE{$BiS6gPkis3b;NWHZRKP=3e2ief(DSEAAqo;nvhEF!+ub-Y%ShhctDu;jhal zjIgeb3P|f*TX`6V8qO-9gJXx|^1oPD~ z56^J4_rO2OP;cv@`uBHW{9!YbbjP>(&$Ht%-+WYLwR$)vPtGBdxhTz5Kh!(j2p8WBPl@`h*&iE*Yhok?w^_ z*LLod_UD7ZE2hz@9ORweaNlo=pfKWBnKb^_sxasr6AUPRiNrgNx0#OK1_0aLEzG}q zh|l>m*H^{t_KZAGfw6DtAeUV`G9J702lMz%7dzbd=4-jOp z_&?vs5emEDJZ~@JdkBxnXs11*Sc*{SD24YSh{_OA3aroBFFCGRb{7i^$GM@ z0=owqk`2J)#7fvy5U(0l@xIP~B`?tAiBG#djsz zpY29)nw6ypf>I&ezJ7?0*_hSBXR!zi^YriJhL|~J>`AA&g2^#IaxwWgLe7~IOLJ6V z9ev?jcHwfBT_9kz{oQ>H;Fy?CZk-^iZNH?-x&X!EqpQpeX2~Y92I9%j^)0A-5?oEy zy*Qdu)RZ)9GRy#X2mBqz8WGZk#W<1m?RGR6r$yw4@DdxoAO`N;#CbZh9yHFoO0A_5 zV{xe%rHSi3dzk2L} zYFtl)cQ~E4VvZAB4mKS(4&W2#N*h{m}x-L2^2|P0y3& zb^TZ7`uFjTc20mY?j;%r4`^v?CUODU>zP4QC*LIW^gXY)NbK-Z!84POc1A3>=MzfG zel@jaGw^Pj=iC>_J3!8RO|SKP5;VMZqc--H(#%-kBWs*=d$ffm_0BOzFUK<~Ps}(7 z{4DeB4?g=<3b$aTYMa^BNt z6RSIMdfiQ%V%upPC2?PTm_$B`;@)XM<-9PxJ4>&y+^XpOA7&nJ9F)zu7!7UU>#UGQ(9+Ycr6WMd?6oBJo+xVI~1E6O&~h*RH0zJ$GQkUQm({ z5MOqHn?a);EH;ZduNM0}o*8d5kZ>v>A>^ADGfhOk<3ycYDhCgpo9d11_ZZ&yc9Xx8 zvBsw71js~)(EcSNBXnTS1jqFRbM;97-gAb1=G^y<23U0eIZlld3L~2Pki-xW$po%| z3egr)SmV%h=rD!bb%XxhFZmIAjO#y7y&|JbQB8UZ}Na$1|4J|o(Z^>}JXxq#{Vw20*x z6>|_fN0R=2V2gkyUE2NXN8cVn7OU!1nX`EDM!P@R*1|E|P`U`g^YD*^r-@};N0sB_ zb8#gAu9JrSitudzGU>S*KXvVS z-Niw0-LeYdLm|@svIgTroZ)?ogkA83KZx+yA;sO}xffUa3>DGf=M%@|9-_V3eaqb{ zgiL+%jNJnGTrcrC@Yw~CHU6imH2jR6uJLl~F6Aw(wl~?5h?++8qY6rfp13H!xK5Bj z*8WKPm$Dc7<6EgM2>?<@>#KTUIzrEv2jMDDf zo0(NyFLfgJ(C`qFx32Uk{5$pJo{rEY??>@ye$++aQbfx$Mq5(RJ-VpzKP1pj7@CIer_%f%1hY@ zoX72SKNsg{j7JG2PATpp4#v*@=jQ|Z`q^9x0W5is89K9+f7+n2R0PvLvJ zK7LH`5zrkzFENCzI2$G+2;ZprU{egzJt|ZyEOBe7gGc{Rt!uLr{}%WOwAQYbN%rb} zcso~@ou)XhuUvgu`OK_oqUe`1wwRanwT=xp(f!Bub7bVHoF;xe)GI_ol;4eQW;$jl zGo*j~7@cIt;@)@xM5o`~SJ>Z2T|k9pJWsN)2EyKpzdUO@VK;mJi0H>b%mahj^IFyw zacACH=uqPN>AgN=+FAW3TXHgH8* zgx?MaqJmUAi@Y}Ny?E%k@XJxB`%bz=LB+-JCR2roFC$*cWiqB6QQ}|D+k79tzzy1> z$u*w^a&9Cr-=V?G7~2dwDlf6auMxTdYm#Zt$SwQwm@~{;Hr`J#ClG!^iL82>=ANj5 z*NzutkzUzNek7Dmro~^~JogtR&X+FRzaJ-E9erLBA>7ZmNUSezykmJY->p7<)llpa zSHE6t3w^hxzi`BRxx;RJq`O$;&TMPt#A3V`?hGK$z7d|p<0N`Z@%LN4DYR1pxgx%d ztnj_!RUt5QYhfTqiL|Vij^U_^0q57f+5vdtPgAbHA^XnF@CpV3@aERKU=Aa5;wdsc zHy=2Owsw0!v;Xv5;&wx-p+kBT){EEzlsLz>7 zksYT?=6jU)g7xIfQwkvm&XEV?{W2N)D_wXn&H@LmPNf$Z-N(v472M)-*Zm`dAsE?= z?H<3j*t=ag`(n1?lO=blH;Eq6>)Y>J>VsSkuJER#XLZYWcAxOXe1lYPGW&|R7Rq&w zoesI>j2PtxpGktCfEq-jI2?1&)Fe^(=)Mig2gO!ZO&F`fsXC4gv?l_j#dUTv;^}brY zhTjGgiN78LCN9{2x{n9NSbnb3*cc2>^Dib++a~0K^iK8yvs1#a%(sKAn_W^!@iV&Q zU-(~GeIU0DzuJ68xa%2s^W$totbEQ9&2zu#SSdd|k%2pJa^`M?U)O~h!l~&0GV$Mx z-nL&{$H-5$=SqU;TJE270jOiv1l^AlCWUEpp z7?_WU5^#Tg&WUF3e6srVszM%M97!Ymuu=E~yY>jC4m)DMEGoG|gEhvMJ&Nfi<^s$zZJW@*n_M>bQ0} zF52F@_+O)o|D_036ZkB~{~)5;e-`zBK}HG7i2(HEMD>K_WJOejmFcaG`J(F(`WfLw zUVMVdFkyOC-Z;~gvg-oV3HNuIG8=$sx3%1Z3kX`o#sH*npW=kQMvcmbW6G?It7K))Nhs)9EjdrckRioJ}OK(ODvako;2xcIyg?uxgQ^ zryho@!80~M#~R8G7D&p(|YEdbvi*B7B#_dg+=ZgF!7>7484BF6T z^b5yFO^2XyXZ3}7dih!T{^H6MMERUU91e$gHEzsPcK~FF-=6?&D~!nf>`_)cGA^w_ z`$@>uHUUbFU7j9V$IhzAwu@z4X|OmGzL`sC-MMr>MndiRJ{#I?ORL{7CTj%xk3ngX zmP?5jrUG4E`lDJtDU9mN^X_bU&vn)e0A=TqaQQpn14Aa0`z?dFq!(;?F_3UeW!_wj z!UkZ@axk)kEFb;l_s$b4JzPibtjSQhu7tc*TTSb5EHgfDnOy6$DsXFhPWVUZnKw2I zGKE>goF^xm&1FA{F}+0oZ#{{I<>q9u5MT2AF-@FV*WRpUBr}b_o!ux;4Tg|bO%zy6 zVG2*l^%n>IjfES?k`lh2Tk~3U)d}y>kGwMqw|`80{)6>K2WCy=@x%R(rTOoWS`#-r zD}(>BVEK9LO`19h8|=y6D>VXT1e^EaiktS!_#1fieg8R1ATG%ZeGW1`9eCwWib#7+X zc*QB1Bu{{4Gvi5<-`AaOw!SR4jN%qJ)>>JOs&dxAM{k;hk8H5s$ntH{wz2BCQjOSD znHW%Q37vA-P(4D)$?Lnf=xX=qo?c-&?_Q9p*u=@-FI)*;D%#NAp;Ag8U#zmSUSVIU za(*&o)}2rNcV=Y-Io1!EkDTBDfUhMF} z;5x(ssGHX7)aKLb>11a3$1+pX-A=8medPYVpF#7LNwUv!kNkz#Yx1TdKF&cPYaX(~q%ylJ#Oo`;0{LB3P9B(qo#7?J#zg zIdYaAYEVO(p%z*-)iNj8WQk3n#o5X_gg1xG8iu$y-B!#yea9YXR0!d4D8w`th>MjM zKl`V^m_P4giLQqgak*tv+Hk%(|5a1wD64GV#K_5Bn&`I+u}<^u1K>6yrj=JDP68pe^1K$qd@W<`p&3!l}iYY@YrMrSpAV)}}0+XSr(Npa%Br{v-y$+2ke z$<5at|DcNgFZl2bwpL`fj!~!J^^6Jd3$l{fYwtEN1T}nGKePheo9CTJTSPymOU=Ea z=fAB*)eeXCi${A)LfNgGHWP$Jy<+kfEJHyNVOEeZj?J=j-tUr(k6dkTfsa1Ce*&vy zg)$)IWyrIp9$y1-dqv2&4%#HoRaxs=J4BfIz(%KGTX9uT$QaPW#dxjqw%kCf1ID3D zmd4Vcaj-Znp`Ni=g4e>0Tnd|FSS_PgDXxO(ozuOW2LwRoqrdqRjgB|vZnEfFL$M$@ zyRV_3VV#vGn%Qq8;U{bNrvfcwWg|~5OmJXmi;b8I(2A^i@4H05aryCrgtLM%?EV@y zy{gtP^P0JBOdfdJ(2?& zr6r>(CutII8!8R;AtK5c1u-0d+;1U6%@ z&@qTs{Bh{L(kZ)`+QRGz#|e)RKYOzX(WwtD_d@4?yq_Yyj1E(xV#Jk4JNMpKwl8XB zqfI!xwLwNbb`fC|w{wL){ud)es~qp209rh1j;LtjBD;-pX^FZJFAA#B_9qfpUdv8w zA%vT`yj1nvQlt_qp%2XBz$|#;1>|VEi(3rm_xJtrz{o$<4g_*TC4H}t(9e{Jl$q=v zH-4?!toN~^#NyXnQAs|nT{{?RMj#yUtqb{sk${H{Ixk>`W|890?~gIEt~;|LH6=gC zD534$2W5+yvMG_+ikVCtT^TK(m?b@iM8r7Zp31m|9l2?;2_0T4Sc)yc$g535-fGJ7 zG)74tIo8W*lUm}?rUZ5FN*T~o5hluVi);jwOTud3ABtPP!Ab1Jzx=bz<{+3QgME5oH;}f{aVqDw7R@Z6yUR|t zwtq;Me;q=K9a`bEGuf(@zizQz4+?B42GKqH9 zWheJYZuc$@5{Q*kkte!qhuGn3w-Ko;V1>bXG$ zN%)S&oFZ<;ygIYKXV>bXj3O8}#*nYKtF6p`)Rv+Nrm%YlV7UG$q#aRr?;!dP-yk-S z_jD5a3XAA~$A_vB@?09?o6ZY$2bbE9(O7s_JXEofe!{RPo(f8+AuN^lBPb@A`RHsQ zsr8fvLRTS~4e`U7go_f0kAZZ5KR&C)yUd$d1yl~#_2f$=`hedyh;|TxDes-uo2}OL zu?ThU)vz~0J0)w?fVSiq!S1I%3o8xRhHCYn4?AaE?Xn2=$`dggYdZb#M;2g5hE{H*aUVTHbvIx|-kf3*Pzsf@gQNP!+*!@fm=fxR{F00$t|2AA{xZQ=9~-3bLoOXY z)5}o(qwzSnM#PQ=ED61Le=bC%rFXi!Hl)Ui$k^-%rotG!{pq@wTP(qSna}1U>YhqT ziP1X>T&kbf$DgWaOwy9HlYq~jS~4!;NsV1T_)DRBM%5Y=@0f0fHqwv9PSa@X?r`>rmKO4-Nsf)Yy z0gL?f87e5ueUN*mV%+TojXaNMjGyiZIW?VblnwK)yB^RQ(j`& zjseajJ9{?$p2 z#_yX& zKi0&|nib)l;HCI7(A*UjO@z=p2;_D-C@*!6M#aigMV~rF0!$t_sHic&4Z*6~sv0XG ztVK%m4%9{oGi7-hH%}wt%iE2Imd&g^kE!(hA~?wyd18V(J0}&96DkBjlDk33c_qnM z<%gQ2-w86;`|LXdVj?W2978eS#UzTh4N*<84qNYmX`Z1S{=LO28`--H_xvnripm}& zX6QpKDNit+LPOIgHr}_Fx~}xJl2M9uNr?)2*>} zMJ3rag{;f;e9Ze-p}Xv(9FMynk7$fkj^6LaKx<4QTu>R6XCzcvYgM;%z>U-&ZgbfC zo)W(j@nL;LDCUH7PnVFCqnTN?KM0-yrnAh7JspT@UL`TWtasIm#cW?jX4~Ec_L~9q zZ?Cw7+9Cd)u^n31wBx?X(OTOS_9w0yH3&X*+&r$PQ>%_Ew5%+nu~&|b#SU2vFMImt z&zF+ZEBf;Z10}1NdaaW_;?A>Nu%k)X0dyyv z-ZqR<#gh+b95xt)3Vo$k2;~tY418v+A#D@x-Y*R^C{O}j5xO;QH0F2i*)Sz@BeaiH zs@MKFK5G#CfDBg2$UcSTj|YGJ6P&!8F`*|u@}NZVLZz|Mk+gl)T!^`wv*xCpZoC7s z`GQJh86R0>Vpjr`#knu{{Zpv&`AfENkOreVjofSWuz8f|5D3NA_m3)gdN(DsDEa3m zHj_Q!4xHpu$$4Y+vkqCBhpMp~wQIraAmdkBIcemfud~BaHzJf<+DI+qfW#6B3tUkT zG~PZQ9dfbpM*SwC1*P`f8A(t$ZYpXW8Hd;r!$DmpdKlEQnQ!IL+&75Yu?__GP@s;3 zg%aj{8LK4Mh9v|%lIa>bpTT^SLXhPxeC=`#tJJSkVwXk^1}iLz9PE#6!*L?yotCy8wY`C|6~;$0)c0_nF>|SvL1q0WI0aRF>jjt2Ptte4AJ> zg02^;Ld=m-^6fW6z>tRCr+mnA!BlkpVD_w1yO$NXWX7I3PRXvVYSTr;P@E&=Gmppc5sfkmK>MY{qtVwR1J{SBTo#!CZo3 zx`D)Cx?i>C>1a9RYbcxi9$`7|I>Ie=F{T_`F=O<<*oO@4ubu zJyIMD`!F6uw6xP5TrioWeUb(oQCNPT+WnK|g?*VdiFQ0$MgiqO{3%@7@oi%+25{) zNv-~2V6a*+VR(_ak0`A7sTVA4T%6%eLQ^LrWuXw1U{=kFov zl4s*(37iJOMck8X2LcHiw?}7h=v9?khvv)dTmIJI&DRKCm+8kV0W`qMyY2BuxLS^+ z9YGw?yEz8GGZAAR)ds^~xN2?8M90ES!$m&Sg*vb*6aGVp8Q^tbB;kbCoO%@3{>^2Sv z)GvNb*7V)-ec)Z{-9=EceKor|?W}|+uGg^c6)@2{d*xEQwEfOLf(nyCi2%<wNYtQlrAXi zJ|s_)sdV{Mw;<3?cSQxr01xY7winw0u+pkDo zS4>4{JhyWH+TEE1#cXmVVb3e|ILhh!TBKtQP?MJs)|8@iwTWPm>P!Jwa3xFD6U+XJ zPNo{NsziP%95W^6a`JB$DrYFgE4DkZM|mX$)A7-?FsS?bS*I#sjx~EaXs%Q%pOq(S zhJ8ru?>poN3r|?BwNPbvj4m!!JMiob>*U6GnBZVe3*=0JW)t0H-Ax@BaqAwERazJ1 z`8qqUPTk9jj}~SgZX^md|n$kuEzE3XnaYV=HxPvc+II&Sa zz57}`6X`V$-BNJp(#E(Rbm)?X&|#|30+(;GO;8tIlLUbkjdUP1J>YOTl4mLmML1{G z!KaZ4d~5BC)sF4ZC~;Qps0EnjnM1lYRZ04N9q4w^LREF#DBE8}5a;jh&eO%9a8dRM zecx&JYgx80OWj}gyGMWTZNZ&K{~!rXRVd}R55d@EjLR{c!}`ehrCt#VW=_jYBpVs+d5@eY>`|Wd(=gspjMtne)?lYP5cY^=m!plzV0j4i!bD6WN(nla;~WC4;m( zqU6zS8wnjv(hk?%umi*D4?ZuT4E+_kjJ`MqcI@V0K}TbIL$Ae*BBC}9 zetCJ0oZ7-K-!>-9tHCR6%OC7f;bYbGVon-!Jqph=*TS@AMkI>Ol8B)pUaW>K%|V)l z42M_j>+06Vcd_bsI-=;?jX1&Vd-|po4chPQ)$XE*B%n#Wbp8;sso^9-gw-(_i3eu8 zSRILlBHNKpPlU3R$cCLH8eN{$NN^D?U7kqZ{%bo;$R2C#-N69Mxw0EqMxy8^%ZEeH zV5i_ZWpSBx`^2Eq;H|{eUmdl->hlB#-q% zZ_@*M4#seX75{Ut5430V<1%xW>Sq18d)gbDS~rHY$m+co`xFTeWJhk2#GAM6BwI+e zdWBsjlC3HhddxoDtiqu|-~LnwW1PfYJ98=N3Cm6BE<^f5sy83cjM2`5Gqc{j87X^x zh-b@GR?PoiHIkZdYrjx$2}aZwph>lmz^-{{Z$DDn;9HXu{7JN+XR+NSjkpk$} z5+C9DQRdOUDh^w7HjahwFvgcYK(ZUVg%D73?tsx@c zl%~w$qT93@d=E6Wt708{t`1a@ck-#XR}R*F$WC4RGf0@o#tyxxD=DaPfVZljGQWL94lqSByOw+4|nLM)P-7hMaY1!z@ z(qQ%%Iq)2U4pG*d3>*6k&xy1p8$mXg&OEEVGSG+q;XDnuV*EkJ`{rKxt40nanpGNy zIBJj3|q?e8aVUf|}xOGt#WY}_(a2I5h5^5XlD10m+e=;sFZ$?z}#tc@s zChA=u7tKvC;&YR>MR`hC@hunGc1Ex0u4~xC4N+t9oeD)~&)Lfzy98OW)T*eE8lE1p z`Gq?q6re9@o75P-=-zF$#+L5kbvL=?W%kfXWp`2ey3Tx&)PM#A-wH~R&aesH=gO^f~H|YR*mp{`1fBKcByOy;!ZNk?+(3N?vqhl zEMj5G+kW*m{At>_zOt#n+?iTx|C~De3m+NspPvuPdZMm}-_q4Ut=>5$V`(gS{x#E< zr(4xkHFSGZJ zD`M-n=zO+13EQ9Z4(W{W))PK(xcCg!dm)heCgxUAituKaNSjP!6FW9)8=7kftB~Yo zF@(Awb5W6-`y6p&YpcGdM=V7~k1a)pI+t2aE*grgmXNXuW|^^Z9QSGpJO8=DCpb*n z?Vm?^N~Kr{1rv)b;wQW^0+ZPdV0+SD+~n}$2#2AwfTF2l{FdaZc5U4_b^S+-vef^>!?KE z&7)Kb7Bx)q2Fg%c<Mo_JyAIw^Eff?Kz6nM8*yLae&>RnXfb(}AaQ zgNb|!sURobHn^$d#%rN_0A&UwG}+n0ElAt1gY70AfSQHWv~ zOo`>?)36B2em+NgZdKXbpk;0TqXQfH_`Z`W%LEx}P2S_u+%x3GXM;Dif^DzgpiHSj zMZ0%E0Hc`2pT~0`H(9=E9w8pr;>5Di%9N!_Ir#pDzJV*BS=yZujkN%NDt(wnNm})t zU19XWP9MbqlA=TYr(4{m>p&0-;lx&T+w^-HVS ztZy=1Ln^oYlD0;ln}=&)0f|WH13AyT{KAaLZ7H%QK&96@_Lay5q!>2!gUfw zaEE5@(sCEro<8RzK1Hg!9aqu$*KC`XsX@UKou9)DNPWxwidzXvmm;SLaf1CW`CaIX znjD?UgYhS?I@{&ExP<*zOGtmqpa(B8T~L(N-0M8OsGAYOdtE}Aw%E8F6Bg|sIG4;H z#i{A=&Byr7YTNb8MKZFXmX}bVPqSfuzGd=-(;!07UwZpuLY8GzIwdtVMlxsUJ~ake z#dfttXz^?OV8PsJC5Z_0sXO94<|Q=ZgJ3Vt?yHw)qC;*d!s6`h(4IHu-oD=OPDwLC zr$tpzl`c}dM5y$gF1LAD?sR8GKv}`#b@~1V1!%5|8Z)ESmBRdzs0KL+`wtdh)fPXT zjlB$e#3>vbaqV#Sc|gxpaB}XOZQbetrJXZ&W9*;^$uwUm*l; zyORLEql~|}eC2j?G4Zf=<@UC>3ys&!pO7aAoj%~CB&rr5j3ILz2yN7< zhkI_AgX^A~3p|^auv>>agnnN!{l9s2G^$$iMo;CvwKbls3&eli11@jCTV%sC`GU zWhPfJ0@+{<_pfoH*ie)YnN?HiSrJZm8~oL|E-?8?%g$Gdtfc%RTkeXO>rShNS$MWy zbBp^uaF(}0p=cs3ZbL^OQno_RX`=+C0J6T=EGK)Fqu;OZE7gcwR)d<)_QZa56-(w| zN~O{RKdUUPFk|3UG7%vhc*Zd&!Fw04L2xYEy(&@owhm-$qI5Blv;w>d$$ZuA)fTq* zhEBS-V?e~dNLD~q*NmL9a*XEzxVNL7E90F?;sBI_GqQpzeK`1D+r?p@Nc-(k9+OgK z^5)b?2yE`)c7drS4<$#yQY8)%h7rf8uxPaJq+P*a@P zHw#KiGUU!ZIur8xJ7wN7qxCd1&DdM#HP@ZxFxAL*A+>FIt>kJ~>Ixk{q!Ytnn>-l} z@f8olsU1S`64HHBlsvRG1a*l_<7IvF_&iyJ3;4vlnezy4d`K%C9eRv+=F$$OI4rM0 zR|s~mgtol;b@!f)KpFAtq*>d^91p#h){svs_7weE4<;~i%}U1lzh;tsIw&Tt7Swrf zI>&4Yx>OsfQpr4>jHdkT zP~6ojWj5N6C3%&wE1t{?Ds*=T0;`{Mx2ZnmiGK;KX+TB0hE4(;Jt!y=MZEGmkOjor(!tRM0T-_k%|5=BS*-NoX0KO2w3&7dHK08Q};7{>C{iBcTKVl(w;?d;;ysSws0?WF7 z*+@T3%#p_&kEv-MDlmd$fHpok=2(gT2>4^b{o{3wOc#8Dm*xp*ECe=wxK?1F9pnMb zZhs&bVh6E@IJm*xN#Xn>6moMMP1x|VCV3V3dhieURuDTUhzsJp03RopKYQa^0iB2g zjPjEmdFTHF{}}H!uD?=#@@Twv{&eMkoAAL=aINYuU-)cL0;@S4e`E-SH$rxh&+kxw zpU!}J)E+PyM8<0M(gXxj0S@`@$fu!V!QUwSfkhyX6d`vPHC!gJUz2oIJ00ojV)m~Y z$h^*6W*azRlRUvMdUoI2;!Xe>DvC~iiq5b$ho`_*df=`vB$ij zPfTg29A?OAoe)8IJWdaCBFPi7Zsa5)2qcN$>BwWc|CuFEQH-2^0f83{_#^x;k})7x z1=-6V5lbiN|BCf(E4SkqyPPE0WuePEkA-QT>dNWiIx4xP~>&u2q;hG>Coe=#*vB0cP|md z72u}kX=iH0y-Z{t@@+r_?_=BPyq`A(k#Wdh%OG$igQw$;eMzIH1OhI7fopf*Cjq#W JL?1bN_dl141l9lm literal 0 HcmV?d00001 diff --git a/packages/react/package.json b/packages/react/package.json index f4ee4f6b7960..c07bd8e584c4 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -60,6 +60,7 @@ "lodash.throttle": "^4.1.1", "react-is": "^16.8.6", "use-resize-observer": "^6.0.0", + "wicg-inert": "^3.1.1", "window-or-global": "^1.0.1" }, "devDependencies": { diff --git a/packages/react/src/components/Dialog/Dialog-story.js b/packages/react/src/components/Dialog/Dialog-story.js new file mode 100644 index 000000000000..c4066e5af415 --- /dev/null +++ b/packages/react/src/components/Dialog/Dialog-story.js @@ -0,0 +1,104 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import * as React from 'react'; +import { FocusScope } from './FocusScope'; +import { Dialog } from '../Dialog'; + +export default { + title: 'Experimental/unstable_Dialog', +}; + +export const Default = () => { + function DemoComponent() { + const [open, setOpen] = React.useState(false); + const ref = React.useRef(null); + + return ( +
+ + {open ? ( + +
+

+ Elit hic at labore culpa itaque fugiat. Consequuntur iure autem + autem officiis dolores facilis nulla earum! Neque quia nemo + sequi assumenda ratione officia Voluptate beatae eligendi + placeat nemo laborum, ratione. +

+ + +
+
+ ) : null} +
+ ); + } + + return ( + <> + + + + ); +}; + +export const DialogExample = () => { + function Example() { + const [open, setOpen] = React.useState(false); + return ( + <> +
+ +
+ + { + setOpen(false); + }}> +

Hello

+ +
+
+ +
+ + ); + } + + return ; +}; diff --git a/packages/react/src/components/Dialog/FocusScope.js b/packages/react/src/components/Dialog/FocusScope.js new file mode 100644 index 000000000000..152e6a39fef5 --- /dev/null +++ b/packages/react/src/components/Dialog/FocusScope.js @@ -0,0 +1,185 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import * as React from 'react'; +import { useEvent } from '../../internal/useEvent'; +import { match, keys } from '../../internal/keyboard'; + +function createFocusWalker(container) { + return document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, { + acceptNode(node) { + if (node.tabIndex >= 0 && !node.disabled) { + return NodeFilter.FILTER_ACCEPT; + } + return NodeFilter.FILTER_SKIP; + }, + }); +} + +function createFocusScope(root) { + const focusScope = { + getFirstDescendant() { + const walker = createFocusWalker(root.current); + return walker.firstChild(); + }, + focusFirstDescendant() { + const walker = createFocusWalker(root.current); + const firstChild = walker.firstChild(); + if (firstChild) { + focus(firstChild); + } + }, + focusLastDescendant() { + const walker = createFocusWalker(root.current); + const lastChild = walker.lastChild(); + if (lastChild) { + focus(lastChild); + } + }, + }; + + return focusScope; +} + +function useFocusScope(containerRef) { + const focusScope = React.useRef(null); + + if (focusScope.current === null) { + focusScope.current = createFocusScope(containerRef); + } + + return focusScope; +} + +function useRestoreFocus(container) { + const containsFocus = React.useRef(false); + + React.useEffect(() => { + const initialActiveElement = document.activeElement; + + if (container.current && container.current.contains) { + containsFocus.current = container.current.contains( + document.activeElement + ); + } + + function onFocusIn() { + containsFocus.current = true; + } + + function onFocusOut(event) { + if (container.current && container.current.contains) { + containsFocus.current = container.current.contains(event.relatedTarget); + } + } + + const { current: element } = container; + + element.addEventListener('focusin', onFocusIn); + element.addEventListener('focusout', onFocusOut); + + return () => { + element.removeEventListener('focusin', onFocusIn); + element.removeEventListener('focusout', onFocusOut); + + if (containsFocus.current === true) { + focus(initialActiveElement); + } + }; + }, []); +} + +function useRefs(refs) { + return React.useCallback((node) => { + refs.forEach((ref) => { + if (typeof ref === 'function') { + ref(node); + } else if (ref !== null && ref !== undefined) { + ref.current = node; + } + }); + }, refs); +} + +function useAutoFocus(getElementOrRef) { + const callbackRef = React.useRef(getElementOrRef); + + React.useEffect(() => { + if (callbackRef.current) { + const elementOrRef = callbackRef.current(); + const element = elementOrRef.current || elementOrRef; + if (element) { + focus(element); + } + } + }, []); +} + +const FocusScope = React.forwardRef(function FocusScope(props, forwardRef) { + const { + as: BaseComponent = 'div', + children, + initialFocusRef, + ...rest + } = props; + const containerRef = React.useRef(null); + const focusScope = useFocusScope(containerRef); + const ref = useRefs([forwardRef, containerRef]); + + useRestoreFocus(containerRef); + useAutoFocus(() => { + if (initialFocusRef) { + return initialFocusRef; + } + return focusScope.current.getFirstDescendant(); + }); + + return ( + <> + { + focusScope.current.focusLastDescendant(); + }} + /> + + {children} + + { + focusScope.current.focusFirstDescendant(); + }} + /> + + ); +}); + +if (__DEV__) { + FocusScope.displayName = 'FocusScope'; +} + +function focus(elementOrRef) { + const element = elementOrRef.current || elementOrRef; + if (element && element.focus && document.activeElement !== element) { + element.focus(); + } +} + +export { FocusScope }; diff --git a/packages/react/src/components/Dialog/__tests__/FocusScope-test.js b/packages/react/src/components/Dialog/__tests__/FocusScope-test.js new file mode 100644 index 000000000000..23c08fcb5a2f --- /dev/null +++ b/packages/react/src/components/Dialog/__tests__/FocusScope-test.js @@ -0,0 +1,10 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +describe('FocusScope', () => { + // +}); diff --git a/packages/react/src/components/Dialog/index.js b/packages/react/src/components/Dialog/index.js new file mode 100644 index 000000000000..55abeb9adf60 --- /dev/null +++ b/packages/react/src/components/Dialog/index.js @@ -0,0 +1,112 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import 'wicg-inert'; +import PropTypes from 'prop-types'; +import React, { useEffect, useRef } from 'react'; +import { FocusScope } from './FocusScope'; +import { useMergedRefs } from '../../internal/useMergedRefs'; +import { useSavedCallback } from '../../internal/useSavedCallback'; +import { match, keys } from '../../internal/keyboard'; + +/** + * @see https://www.tpgi.com/the-current-state-of-modal-dialog-accessibility/ + */ +const Dialog = React.forwardRef(function Dialog(props, forwardRef) { + const { + 'aria-labelledby': labelledBy, + children, + onDismiss, + open = false, + modal = true, + ...rest + } = props; + const dialogRef = useRef(null); + const ref = useMergedRefs([dialogRef, forwardRef]); + const savedOnDismiss = useSavedCallback(onDismiss); + + function onKeyDown(event) { + if (!open) { + return; + } + + if (match(event, keys.Escape)) { + event.stopPropagation(); + savedOnDismiss(); + } + } + + useEffect(() => { + if (open) { + // + } + }, [open]); + + if (open) { + return ( + + + + {children} + + + ); + } + + return null; +}); + +Dialog.propTypes = {}; + +if (__DEV__) { + Dialog.displayName = 'Dialog'; +} + +function Portal({ children, ...rest }) { + return
{children}
; +} + +function FullPage(props) { + return ( +
+ ); +} + +export { Dialog }; diff --git a/packages/react/src/internal/useMergedRefs.js b/packages/react/src/internal/useMergedRefs.js new file mode 100644 index 000000000000..3bdc34ddf417 --- /dev/null +++ b/packages/react/src/internal/useMergedRefs.js @@ -0,0 +1,28 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { useCallback } from 'react'; + +/** + * Combine multiple refs into a single ref. This use useful when you have two + * refs from both `React.forwardRef` and `useRef` that you would like to add to + * the same node. + * + * @param {Array} refs + * @returns {Function} + */ +export function useMergedRefs(refs) { + return useCallback((node) => { + refs.forEach((ref) => { + if (typeof ref === 'function') { + ref(node); + } else if (ref !== null && ref !== undefined) { + ref.current = node; + } + }); + }, refs); +} diff --git a/packages/react/src/internal/useSavedCallback.js b/packages/react/src/internal/useSavedCallback.js new file mode 100644 index 000000000000..9a4c4f757b47 --- /dev/null +++ b/packages/react/src/internal/useSavedCallback.js @@ -0,0 +1,31 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { useCallback, useEffect, useRef } from 'react'; + +/** + * Provide a stable reference for a callback that is passed as a prop to a + * component. This is helpful when you want access to the latest version of a + * callback prop but don't want it to be added to the dependency array of an + * effect. + * + * @param {Function} callback + * @returns {Function} + */ +export function useSavedCallback(callback) { + const savedCallback = useRef(callback); + + useEffect(() => { + savedCallback.current = callback; + }); + + return useCallback(() => { + if (savedCallback.current) { + return savedCallback.current(); + } + }, []); +} diff --git a/yarn.lock b/yarn.lock index 15d4557855b3..f539eec09b01 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10375,6 +10375,7 @@ __metadata: webpack: ^4.41.5 webpack-dev-server: ^3.11.2 whatwg-fetch: ^3.6.2 + wicg-inert: ^3.1.1 window-or-global: ^1.0.1 peerDependencies: carbon-components: ^10.30.0 @@ -34939,6 +34940,13 @@ resolve@^2.0.0-next.3: languageName: node linkType: hard +"wicg-inert@npm:^3.1.1": + version: 3.1.1 + resolution: "wicg-inert@npm:3.1.1" + checksum: d4553ea762ad5808b2d20990f05695d3272cf5d24ee9b0008d0edb0c69466eb6a24824ed932070553a2dc8b25eee8fcde780e073de89d640882d9da99f874944 + languageName: node + linkType: hard + "wide-align@npm:^1.1.0": version: 1.1.3 resolution: "wide-align@npm:1.1.3" From e9c5b7f032139465c12c722224009a9e8941368f Mon Sep 17 00:00:00 2001 From: Josh Black Date: Tue, 17 Aug 2021 17:09:19 -0500 Subject: [PATCH 2/6] chore: check-in work --- .../src/components/Dialog/Dialog-story.js | 100 +++++++++++--- .../react/src/components/Dialog/FocusScope.js | 6 +- .../react/src/components/Dialog/Portal.js | 0 packages/react/src/components/Dialog/index.js | 126 +++++++++--------- 4 files changed, 153 insertions(+), 79 deletions(-) create mode 100644 packages/react/src/components/Dialog/Portal.js diff --git a/packages/react/src/components/Dialog/Dialog-story.js b/packages/react/src/components/Dialog/Dialog-story.js index c4066e5af415..72c526b19b02 100644 --- a/packages/react/src/components/Dialog/Dialog-story.js +++ b/packages/react/src/components/Dialog/Dialog-story.js @@ -6,8 +6,10 @@ */ import * as React from 'react'; +import ReactDOM from 'react-dom'; import { FocusScope } from './FocusScope'; import { Dialog } from '../Dialog'; +import { useId } from '../../internal/useId'; export default { title: 'Experimental/unstable_Dialog', @@ -67,11 +69,14 @@ export const Default = () => { export const DialogExample = () => { function Example() { const [open, setOpen] = React.useState(false); + const id = useId(); return ( - <> +
+ + {/* trigger */} - { - setOpen(false); - }}> -

Hello

- -
+ {/* full screen background */} + { + setOpen(false); + }} + /> + + {/* dialog */} + { + setOpen(false); + }} + style={{ + position: 'relative', + zIndex: 9999, + padding: '1rem', + background: 'white', + }}> +
+ Hello +
+
+ +
+ +
+ + ) : null} +
- +
); } return ; }; + +function FullPage(props) { + return ( +
+ ); +} + +function Portal({ children }) { + const [mountNode, setMountNode] = React.useState(null); + + React.useEffect(() => { + // TODO: should this be configurable???? + setMountNode(document.body); + }, []); + + if (mountNode) { + return ReactDOM.createPortal(children, mountNode); + } + + return null; +} diff --git a/packages/react/src/components/Dialog/FocusScope.js b/packages/react/src/components/Dialog/FocusScope.js index 152e6a39fef5..ed2bf9aeb91a 100644 --- a/packages/react/src/components/Dialog/FocusScope.js +++ b/packages/react/src/components/Dialog/FocusScope.js @@ -87,7 +87,9 @@ function useRestoreFocus(container) { element.removeEventListener('focusout', onFocusOut); if (containsFocus.current === true) { - focus(initialActiveElement); + setTimeout(() => { + focus(initialActiveElement); + }, 0); } }; }, []); @@ -141,6 +143,7 @@ const FocusScope = React.forwardRef(function FocusScope(props, forwardRef) { return ( <> { - if (open) { - // + const changes = []; + const queue = Array.from(document.body.childNodes); + + while (queue.length !== 0) { + const node = queue.shift(); + + // If a node is the modal (dialogRef), do nothing + if (node === dialogRef.current) { + continue; + } + + // If a tree contains our `dialogRef`, traverse its children + if (node.contains(dialogRef.current)) { + queue.push(...Array.from(node.childNodes)); + continue; + } + + // If a node is a bumper, do nothing + if ( + node.hasAttribute('data-dialog-bumper') && + (dialogRef.current.previousSibling === node || + dialogRef.current.nextSibling === node) + ) { + continue; + } + + if (node.hasAttribute('aria-hidden') || node.hasAttribute('inert')) { + continue; + } + + // what is aria-hidden === 'false' + + // Otherwise, set it to inert and set aria-hidden to true + node.setAttribute('aria-hidden', 'true'); + node.setAttribute('inert', ''); + + changes.push(node); } - }, [open]); - - if (open) { - return ( - - - - {children} - - - ); - } - return null; + return () => { + changes.forEach((node) => { + node.removeAttribute('inert'); + // This mutation needs to be asynchronous to allow the polyfill time to + // observe the change and allow mutations to occur + // https://github.com/WICG/inert#performance-and-gotchas + setTimeout(() => { + node.removeAttribute('aria-hidden'); + }, 0); + }); + }; + }, []); + + return ( + + {children} + + ); }); Dialog.propTypes = {}; @@ -88,25 +109,4 @@ if (__DEV__) { Dialog.displayName = 'Dialog'; } -function Portal({ children, ...rest }) { - return
{children}
; -} - -function FullPage(props) { - return ( -
- ); -} - export { Dialog }; From dc2af7f039c2890a4d492253baae832381242097 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Wed, 18 Aug 2021 13:30:20 -0500 Subject: [PATCH 3/6] chore: check-in work --- .../src/components/Dialog/Dialog-story.js | 17 +------ .../react/src/components/Dialog/Portal.js | 12 +++++ packages/react/src/components/Dialog/index.js | 33 +++++++++++-- packages/react/src/components/Portal/index.js | 47 +++++++++++++++++++ 4 files changed, 88 insertions(+), 21 deletions(-) create mode 100644 packages/react/src/components/Portal/index.js diff --git a/packages/react/src/components/Dialog/Dialog-story.js b/packages/react/src/components/Dialog/Dialog-story.js index 72c526b19b02..68729dbfeafd 100644 --- a/packages/react/src/components/Dialog/Dialog-story.js +++ b/packages/react/src/components/Dialog/Dialog-story.js @@ -6,10 +6,10 @@ */ import * as React from 'react'; -import ReactDOM from 'react-dom'; import { FocusScope } from './FocusScope'; import { Dialog } from '../Dialog'; import { useId } from '../../internal/useId'; +import { Portal } from '../Portal'; export default { title: 'Experimental/unstable_Dialog', @@ -157,18 +157,3 @@ function FullPage(props) { /> ); } - -function Portal({ children }) { - const [mountNode, setMountNode] = React.useState(null); - - React.useEffect(() => { - // TODO: should this be configurable???? - setMountNode(document.body); - }, []); - - if (mountNode) { - return ReactDOM.createPortal(children, mountNode); - } - - return null; -} diff --git a/packages/react/src/components/Dialog/Portal.js b/packages/react/src/components/Dialog/Portal.js index e69de29bb2d1..4f3703e56027 100644 --- a/packages/react/src/components/Dialog/Portal.js +++ b/packages/react/src/components/Dialog/Portal.js @@ -0,0 +1,12 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import * as React from 'react'; + +const Portal = React.forwardRef(function Portal(props, ref) { + const [mountNode, setMountNode] = React.useState(null); +}); diff --git a/packages/react/src/components/Dialog/index.js b/packages/react/src/components/Dialog/index.js index 516ff823745b..431feed2d698 100644 --- a/packages/react/src/components/Dialog/index.js +++ b/packages/react/src/components/Dialog/index.js @@ -62,27 +62,48 @@ const Dialog = React.forwardRef(function Dialog(props, forwardRef) { continue; } - if (node.hasAttribute('aria-hidden') || node.hasAttribute('inert')) { + if (node.getAttribute('aria-hidden') === 'true') { continue; } - // what is aria-hidden === 'false' + if (node.hasAttribute('inert')) { + continue; + } + + // what if aria-hidden === 'false' + if (node.getAttribute('aria-hidden') === 'false') { + node.setAttribute('aria-hidden', 'true'); + node.setAttribute('inert', ''); + changes.push({ + node, + attributes: { + 'aria-hidden': 'false', + }, + }); + continue; + } // Otherwise, set it to inert and set aria-hidden to true node.setAttribute('aria-hidden', 'true'); node.setAttribute('inert', ''); - changes.push(node); + changes.push({ + node, + }); } return () => { - changes.forEach((node) => { + changes.forEach(({ node, attributes }) => { node.removeAttribute('inert'); // This mutation needs to be asynchronous to allow the polyfill time to // observe the change and allow mutations to occur // https://github.com/WICG/inert#performance-and-gotchas setTimeout(() => { - node.removeAttribute('aria-hidden'); + if (attributes && attributes['aria-hidden']) { + node.setAttribute('aria-hidden', attributes['aria-hidden']); + } else { + node.removeAttribute('aria-hidden'); + } }, 0); }); }; @@ -109,4 +130,6 @@ if (__DEV__) { Dialog.displayName = 'Dialog'; } +function getElementsToHide(dialogNode) {} + export { Dialog }; diff --git a/packages/react/src/components/Portal/index.js b/packages/react/src/components/Portal/index.js new file mode 100644 index 000000000000..8724dc6f3469 --- /dev/null +++ b/packages/react/src/components/Portal/index.js @@ -0,0 +1,47 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import PropTypes from 'prop-types'; +import { useState } from 'react'; +import ReactDOM from 'react-dom'; + +/** + * Helper component for rendering content within a portal. By default, the + * portal will render into document.body. You can customize this behavior with + * the `container` prop. Any `children` provided to this component will be + * rendered in the portal node. + */ +function Portal({ container, children }) { + const [mountNode, setMountNode] = useState(null); + + React.useEffect(() => { + const node = container.current || container || document.body; + setMountNode(node); + }, [container]); + + if (mountNode) { + return ReactDOM.createPortal(children, mountNode); + } + + return null; +} + +Portal.propTypes = { + /** + * Specify the children elements to be rendered inside of the + */ + children: PropTypes.node, + + /** + * Provide a ref for a container node to render the portal + */ + container: PropTypes.shape({ + current: PropTypes.any, + }), +}; + +export { Portal }; From 97fd6b6721ac66c350196a9df1d68f28965be34c Mon Sep 17 00:00:00 2001 From: Josh Black Date: Mon, 23 Aug 2021 16:56:42 -0500 Subject: [PATCH 4/6] refactor(react): update Dialog and pull-out helper components --- .../src/components/Dialog/Dialog-story.js | 2 +- .../react/src/components/Dialog/FocusScope.js | 189 ------------------ .../react/src/components/Dialog/Portal.js | 12 -- packages/react/src/components/Dialog/index.js | 176 ++++++++-------- .../__tests__/FocusScope-test.js | 0 .../react/src/components/FocusScope/index.js | 100 +++++++++ .../src/components/FocusScope/useAutoFocus.js | 23 +++ .../components/FocusScope/useFocusScope.js | 55 +++++ .../components/FocusScope/useRestoreFocus.js | 49 +++++ .../Portal/__tests__/Portal-test.js | 45 +++++ packages/react/src/components/Portal/index.js | 17 +- packages/react/src/internal/focus/index.js | 13 ++ packages/react/src/internal/useMergedRefs.js | 1 + 13 files changed, 393 insertions(+), 289 deletions(-) delete mode 100644 packages/react/src/components/Dialog/FocusScope.js delete mode 100644 packages/react/src/components/Dialog/Portal.js rename packages/react/src/components/{Dialog => FocusScope}/__tests__/FocusScope-test.js (100%) create mode 100644 packages/react/src/components/FocusScope/index.js create mode 100644 packages/react/src/components/FocusScope/useAutoFocus.js create mode 100644 packages/react/src/components/FocusScope/useFocusScope.js create mode 100644 packages/react/src/components/FocusScope/useRestoreFocus.js create mode 100644 packages/react/src/components/Portal/__tests__/Portal-test.js create mode 100644 packages/react/src/internal/focus/index.js diff --git a/packages/react/src/components/Dialog/Dialog-story.js b/packages/react/src/components/Dialog/Dialog-story.js index 68729dbfeafd..9c772f0e288f 100644 --- a/packages/react/src/components/Dialog/Dialog-story.js +++ b/packages/react/src/components/Dialog/Dialog-story.js @@ -6,7 +6,7 @@ */ import * as React from 'react'; -import { FocusScope } from './FocusScope'; +import { FocusScope } from '../FocusScope'; import { Dialog } from '../Dialog'; import { useId } from '../../internal/useId'; import { Portal } from '../Portal'; diff --git a/packages/react/src/components/Dialog/FocusScope.js b/packages/react/src/components/Dialog/FocusScope.js deleted file mode 100644 index ed2bf9aeb91a..000000000000 --- a/packages/react/src/components/Dialog/FocusScope.js +++ /dev/null @@ -1,189 +0,0 @@ -/** - * Copyright IBM Corp. 2016, 2018 - * - * This source code is licensed under the Apache-2.0 license found in the - * LICENSE file in the root directory of this source tree. - */ - -import * as React from 'react'; -import { useEvent } from '../../internal/useEvent'; -import { match, keys } from '../../internal/keyboard'; - -function createFocusWalker(container) { - return document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, { - acceptNode(node) { - if (node.tabIndex >= 0 && !node.disabled) { - return NodeFilter.FILTER_ACCEPT; - } - return NodeFilter.FILTER_SKIP; - }, - }); -} - -function createFocusScope(root) { - const focusScope = { - getFirstDescendant() { - const walker = createFocusWalker(root.current); - return walker.firstChild(); - }, - focusFirstDescendant() { - const walker = createFocusWalker(root.current); - const firstChild = walker.firstChild(); - if (firstChild) { - focus(firstChild); - } - }, - focusLastDescendant() { - const walker = createFocusWalker(root.current); - const lastChild = walker.lastChild(); - if (lastChild) { - focus(lastChild); - } - }, - }; - - return focusScope; -} - -function useFocusScope(containerRef) { - const focusScope = React.useRef(null); - - if (focusScope.current === null) { - focusScope.current = createFocusScope(containerRef); - } - - return focusScope; -} - -function useRestoreFocus(container) { - const containsFocus = React.useRef(false); - - React.useEffect(() => { - const initialActiveElement = document.activeElement; - - if (container.current && container.current.contains) { - containsFocus.current = container.current.contains( - document.activeElement - ); - } - - function onFocusIn() { - containsFocus.current = true; - } - - function onFocusOut(event) { - if (container.current && container.current.contains) { - containsFocus.current = container.current.contains(event.relatedTarget); - } - } - - const { current: element } = container; - - element.addEventListener('focusin', onFocusIn); - element.addEventListener('focusout', onFocusOut); - - return () => { - element.removeEventListener('focusin', onFocusIn); - element.removeEventListener('focusout', onFocusOut); - - if (containsFocus.current === true) { - setTimeout(() => { - focus(initialActiveElement); - }, 0); - } - }; - }, []); -} - -function useRefs(refs) { - return React.useCallback((node) => { - refs.forEach((ref) => { - if (typeof ref === 'function') { - ref(node); - } else if (ref !== null && ref !== undefined) { - ref.current = node; - } - }); - }, refs); -} - -function useAutoFocus(getElementOrRef) { - const callbackRef = React.useRef(getElementOrRef); - - React.useEffect(() => { - if (callbackRef.current) { - const elementOrRef = callbackRef.current(); - const element = elementOrRef.current || elementOrRef; - if (element) { - focus(element); - } - } - }, []); -} - -const FocusScope = React.forwardRef(function FocusScope(props, forwardRef) { - const { - as: BaseComponent = 'div', - children, - initialFocusRef, - ...rest - } = props; - const containerRef = React.useRef(null); - const focusScope = useFocusScope(containerRef); - const ref = useRefs([forwardRef, containerRef]); - - useRestoreFocus(containerRef); - useAutoFocus(() => { - if (initialFocusRef) { - return initialFocusRef; - } - return focusScope.current.getFirstDescendant(); - }); - - return ( - <> - { - focusScope.current.focusLastDescendant(); - }} - /> - - {children} - - { - focusScope.current.focusFirstDescendant(); - }} - /> - - ); -}); - -if (__DEV__) { - FocusScope.displayName = 'FocusScope'; -} - -function focus(elementOrRef) { - const element = elementOrRef.current || elementOrRef; - if (element && element.focus && document.activeElement !== element) { - element.focus(); - } -} - -export { FocusScope }; diff --git a/packages/react/src/components/Dialog/Portal.js b/packages/react/src/components/Dialog/Portal.js deleted file mode 100644 index 4f3703e56027..000000000000 --- a/packages/react/src/components/Dialog/Portal.js +++ /dev/null @@ -1,12 +0,0 @@ -/** - * Copyright IBM Corp. 2016, 2018 - * - * This source code is licensed under the Apache-2.0 license found in the - * LICENSE file in the root directory of this source tree. - */ - -import * as React from 'react'; - -const Portal = React.forwardRef(function Portal(props, ref) { - const [mountNode, setMountNode] = React.useState(null); -}); diff --git a/packages/react/src/components/Dialog/index.js b/packages/react/src/components/Dialog/index.js index 431feed2d698..3569f5ff4a77 100644 --- a/packages/react/src/components/Dialog/index.js +++ b/packages/react/src/components/Dialog/index.js @@ -8,7 +8,7 @@ import 'wicg-inert'; import PropTypes from 'prop-types'; import React, { useEffect, useRef } from 'react'; -import { FocusScope } from './FocusScope'; +import { FocusScope } from '../FocusScope'; import { useMergedRefs } from '../../internal/useMergedRefs'; import { useSavedCallback } from '../../internal/useSavedCallback'; import { match, keys } from '../../internal/keyboard'; @@ -17,13 +17,7 @@ import { match, keys } from '../../internal/keyboard'; * @see https://www.tpgi.com/the-current-state-of-modal-dialog-accessibility/ */ const Dialog = React.forwardRef(function Dialog(props, forwardRef) { - const { - 'aria-labelledby': labelledBy, - children, - onDismiss, - modal = true, - ...rest - } = props; + const { 'aria-labelledby': labelledBy, children, onDismiss, ...rest } = props; const dialogRef = useRef(null); const ref = useMergedRefs([dialogRef, forwardRef]); const savedOnDismiss = useSavedCallback(onDismiss); @@ -36,76 +30,9 @@ const Dialog = React.forwardRef(function Dialog(props, forwardRef) { } useEffect(() => { - const changes = []; - const queue = Array.from(document.body.childNodes); - - while (queue.length !== 0) { - const node = queue.shift(); - - // If a node is the modal (dialogRef), do nothing - if (node === dialogRef.current) { - continue; - } - - // If a tree contains our `dialogRef`, traverse its children - if (node.contains(dialogRef.current)) { - queue.push(...Array.from(node.childNodes)); - continue; - } - - // If a node is a bumper, do nothing - if ( - node.hasAttribute('data-dialog-bumper') && - (dialogRef.current.previousSibling === node || - dialogRef.current.nextSibling === node) - ) { - continue; - } - - if (node.getAttribute('aria-hidden') === 'true') { - continue; - } - - if (node.hasAttribute('inert')) { - continue; - } - - // what if aria-hidden === 'false' - if (node.getAttribute('aria-hidden') === 'false') { - node.setAttribute('aria-hidden', 'true'); - node.setAttribute('inert', ''); - changes.push({ - node, - attributes: { - 'aria-hidden': 'false', - }, - }); - continue; - } - - // Otherwise, set it to inert and set aria-hidden to true - node.setAttribute('aria-hidden', 'true'); - node.setAttribute('inert', ''); - - changes.push({ - node, - }); - } - + const changes = hide(document.body, dialogRef.current); return () => { - changes.forEach(({ node, attributes }) => { - node.removeAttribute('inert'); - // This mutation needs to be asynchronous to allow the polyfill time to - // observe the change and allow mutations to occur - // https://github.com/WICG/inert#performance-and-gotchas - setTimeout(() => { - if (attributes && attributes['aria-hidden']) { - node.setAttribute('aria-hidden', attributes['aria-hidden']); - } else { - node.removeAttribute('aria-hidden'); - } - }, 0); - }); + show(changes); }; }, []); @@ -124,12 +51,103 @@ const Dialog = React.forwardRef(function Dialog(props, forwardRef) { ); }); -Dialog.propTypes = {}; +Dialog.propTypes = { + /** + * Provide the associated element that labels the Dialog + */ + 'aria-labelledby': PropTypes.string.isRequired, + + /** + * Provide children to be rendered inside of the Dialog + */ + children: PropTypes.node, + + /** + * Provide a handler that is called when the Dialog is requesting to be closed + */ + onDismiss: PropTypes.func.isRequired, +}; if (__DEV__) { Dialog.displayName = 'Dialog'; } -function getElementsToHide(dialogNode) {} +function hide(root, dialog) { + const changes = []; + const queue = Array.from(root.childNodes); + + while (queue.length !== 0) { + const node = queue.shift(); + + if (node.nodeType !== Node.ELEMENT_NODE) { + continue; + } + + // If a node is the dialog, do nothing + if (node === dialog) { + continue; + } + + // If a tree contains our dialog, traverse its children + if (node.contains(dialog)) { + queue.push(...Array.from(node.childNodes)); + continue; + } + + // If a node is a bumper, do nothing + if ( + node.hasAttribute('data-carbon-focus-scope') && + (dialog.previousSibling === node || dialog.nextSibling === node) + ) { + continue; + } + + if (node.getAttribute('aria-hidden') === 'true') { + continue; + } + + if (node.hasAttribute('inert')) { + continue; + } + + if (node.getAttribute('aria-hidden') === 'false') { + node.setAttribute('aria-hidden', 'true'); + node.setAttribute('inert', ''); + changes.push({ + node, + attributes: { + 'aria-hidden': 'false', + }, + }); + continue; + } + + // Otherwise, set it to inert and set aria-hidden to true + node.setAttribute('aria-hidden', 'true'); + node.setAttribute('inert', ''); + + changes.push({ + node, + }); + } + + return changes; +} + +function show(changes) { + changes.forEach(({ node, attributes }) => { + node.removeAttribute('inert'); + // This mutation needs to be asynchronous to allow the polyfill time to + // observe the change and allow mutations to occur + // https://github.com/WICG/inert#performance-and-gotchas + setTimeout(() => { + if (attributes && attributes['aria-hidden']) { + node.setAttribute('aria-hidden', attributes['aria-hidden']); + } else { + node.removeAttribute('aria-hidden'); + } + }, 0); + }); +} export { Dialog }; diff --git a/packages/react/src/components/Dialog/__tests__/FocusScope-test.js b/packages/react/src/components/FocusScope/__tests__/FocusScope-test.js similarity index 100% rename from packages/react/src/components/Dialog/__tests__/FocusScope-test.js rename to packages/react/src/components/FocusScope/__tests__/FocusScope-test.js diff --git a/packages/react/src/components/FocusScope/index.js b/packages/react/src/components/FocusScope/index.js new file mode 100644 index 000000000000..038c431b7ab2 --- /dev/null +++ b/packages/react/src/components/FocusScope/index.js @@ -0,0 +1,100 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import PropTypes from 'prop-types'; +import * as React from 'react'; +import { useMergedRefs } from '../../internal/useMergedRefs'; +import { useAutoFocus } from './useAutoFocus'; +import { useFocusScope } from './useFocusScope'; +import { useRestoreFocus } from './useRestoreFocus'; + +const FocusScope = React.forwardRef(function FocusScope(props, forwardRef) { + const { + as: BaseComponent = 'div', + children, + initialFocusRef, + ...rest + } = props; + const containerRef = React.useRef(null); + const focusScope = useFocusScope(containerRef); + const ref = useMergedRefs([forwardRef, containerRef]); + + useRestoreFocus(containerRef); + useAutoFocus(() => { + if (initialFocusRef) { + return initialFocusRef; + } + return focusScope.current.getFirstDescendant(); + }); + + return ( + <> + { + focusScope.current.focusLastDescendant(); + }} + /> + + {children} + + { + focusScope.current.focusFirstDescendant(); + }} + /> + + ); +}); + +if (__DEV__) { + FocusScope.displayName = 'FocusScope'; +} + +FocusScope.propTypes = { + /** + * Provide a custom element type for the containing element + */ + as: PropTypes.oneOfType([ + PropTypes.func, + PropTypes.string, + PropTypes.elementType, + ]), + + /** + * Provide the children to be rendered inside of the `FocusScope` + */ + children: PropTypes.node, + + /** + * Provide a `ref` that is used to place focus when the `FocusScope` is + * initially opened + */ + initialFocusRef: PropTypes.shape({ + current: PropTypes.any, + }), +}; + +const bumperStyle = { + outline: 'none', + opacity: '0', + position: 'fixed', + pointerEvents: 'none', +}; + +function FocusScopeBumper(props) { + return ( + + ); +} + +export { FocusScope }; diff --git a/packages/react/src/components/FocusScope/useAutoFocus.js b/packages/react/src/components/FocusScope/useAutoFocus.js new file mode 100644 index 000000000000..64c3cc505b43 --- /dev/null +++ b/packages/react/src/components/FocusScope/useAutoFocus.js @@ -0,0 +1,23 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { useEffect, useRef } from 'react'; +import { focus } from '../../internal/focus'; + +export function useAutoFocus(getElementOrRef) { + const callbackRef = useRef(getElementOrRef); + + useEffect(() => { + if (callbackRef.current) { + const elementOrRef = callbackRef.current(); + const element = elementOrRef.current || elementOrRef; + if (element) { + focus(element); + } + } + }, []); +} diff --git a/packages/react/src/components/FocusScope/useFocusScope.js b/packages/react/src/components/FocusScope/useFocusScope.js new file mode 100644 index 000000000000..6b7f25ce9007 --- /dev/null +++ b/packages/react/src/components/FocusScope/useFocusScope.js @@ -0,0 +1,55 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { useRef } from 'react'; +import { focus } from '../../internal/focus'; + +export function useFocusScope(containerRef) { + const focusScope = useRef(null); + + if (focusScope.current === null) { + focusScope.current = createFocusScope(containerRef); + } + + return focusScope; +} + +function createFocusWalker(container) { + return document.createTreeWalker(container, NodeFilter.SHOW_ELEMENT, { + acceptNode(node) { + if (node.tabIndex >= 0 && !node.disabled) { + return NodeFilter.FILTER_ACCEPT; + } + return NodeFilter.FILTER_SKIP; + }, + }); +} + +function createFocusScope(root) { + const focusScope = { + getFirstDescendant() { + const walker = createFocusWalker(root.current); + return walker.firstChild(); + }, + focusFirstDescendant() { + const walker = createFocusWalker(root.current); + const firstChild = walker.firstChild(); + if (firstChild) { + focus(firstChild); + } + }, + focusLastDescendant() { + const walker = createFocusWalker(root.current); + const lastChild = walker.lastChild(); + if (lastChild) { + focus(lastChild); + } + }, + }; + + return focusScope; +} diff --git a/packages/react/src/components/FocusScope/useRestoreFocus.js b/packages/react/src/components/FocusScope/useRestoreFocus.js new file mode 100644 index 000000000000..ae41d8b1cd12 --- /dev/null +++ b/packages/react/src/components/FocusScope/useRestoreFocus.js @@ -0,0 +1,49 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { useEffect, useRef } from 'react'; +import { focus } from '../../internal/focus'; + +export function useRestoreFocus(container) { + const containsFocus = useRef(false); + + useEffect(() => { + const initialActiveElement = document.activeElement; + + if (container.current && container.current.contains) { + containsFocus.current = container.current.contains( + document.activeElement + ); + } + + function onFocusIn() { + containsFocus.current = true; + } + + function onFocusOut(event) { + if (container.current && container.current.contains) { + containsFocus.current = container.current.contains(event.relatedTarget); + } + } + + const { current: element } = container; + + element.addEventListener('focusin', onFocusIn); + element.addEventListener('focusout', onFocusOut); + + return () => { + element.removeEventListener('focusin', onFocusIn); + element.removeEventListener('focusout', onFocusOut); + + if (containsFocus.current === true) { + setTimeout(() => { + focus(initialActiveElement); + }, 0); + } + }; + }, [container]); +} diff --git a/packages/react/src/components/Portal/__tests__/Portal-test.js b/packages/react/src/components/Portal/__tests__/Portal-test.js new file mode 100644 index 000000000000..b7a35a839cb6 --- /dev/null +++ b/packages/react/src/components/Portal/__tests__/Portal-test.js @@ -0,0 +1,45 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { render, screen } from '@testing-library/react'; +import React from 'react'; +import { Portal } from '../'; + +describe('Portal', () => { + it('should render its children in the document', () => { + render( + + + + ); + expect(screen.getByTestId('test')).toBeInTheDocument(); + }); + + it('should support rendering in a custom container', () => { + const container = document.createElement('div'); + document.body.appendChild(container); + + function TestComponent() { + const ref = React.useRef(null); + + if (ref.current === null) { + ref.current = container; + } + + return ( + + + + ); + } + + render(); + + expect(container).toContainElement(screen.getByTestId('test')); + document.body.removeChild(container); + }); +}); diff --git a/packages/react/src/components/Portal/index.js b/packages/react/src/components/Portal/index.js index 8724dc6f3469..dff5729c0b91 100644 --- a/packages/react/src/components/Portal/index.js +++ b/packages/react/src/components/Portal/index.js @@ -6,21 +6,20 @@ */ import PropTypes from 'prop-types'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import ReactDOM from 'react-dom'; /** * Helper component for rendering content within a portal. By default, the * portal will render into document.body. You can customize this behavior with * the `container` prop. Any `children` provided to this component will be - * rendered in the portal node. + * rendered inside of the container. */ function Portal({ container, children }) { const [mountNode, setMountNode] = useState(null); - React.useEffect(() => { - const node = container.current || container || document.body; - setMountNode(node); + useEffect(() => { + setMountNode(container ? container.current : document.body); }, [container]); if (mountNode) { @@ -39,9 +38,11 @@ Portal.propTypes = { /** * Provide a ref for a container node to render the portal */ - container: PropTypes.shape({ - current: PropTypes.any, - }), + container: PropTypes.oneOfType([ + PropTypes.shape({ + current: PropTypes.any, + }), + ]), }; export { Portal }; diff --git a/packages/react/src/internal/focus/index.js b/packages/react/src/internal/focus/index.js new file mode 100644 index 000000000000..5ee20d3bcde5 --- /dev/null +++ b/packages/react/src/internal/focus/index.js @@ -0,0 +1,13 @@ +/** + * Copyright IBM Corp. 2016, 2018 + * + * This source code is licensed under the Apache-2.0 license found in the + * LICENSE file in the root directory of this source tree. + */ + +export function focus(elementOrRef) { + const element = elementOrRef.current || elementOrRef; + if (element && element.focus && document.activeElement !== element) { + element.focus(); + } +} diff --git a/packages/react/src/internal/useMergedRefs.js b/packages/react/src/internal/useMergedRefs.js index 3bdc34ddf417..e79721291935 100644 --- a/packages/react/src/internal/useMergedRefs.js +++ b/packages/react/src/internal/useMergedRefs.js @@ -24,5 +24,6 @@ export function useMergedRefs(refs) { ref.current = node; } }); + // eslint-disable-next-line react-hooks/exhaustive-deps }, refs); } From fc1caeabad7a0737c9fb93b99144468bcc20e050 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Wed, 1 Sep 2021 12:01:45 -0500 Subject: [PATCH 5/6] chore(react): hide dialog story --- .../src/components/Dialog/Dialog-story.js | 29 +++++++------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/packages/react/src/components/Dialog/Dialog-story.js b/packages/react/src/components/Dialog/Dialog-story.js index 9c772f0e288f..00461a685795 100644 --- a/packages/react/src/components/Dialog/Dialog-story.js +++ b/packages/react/src/components/Dialog/Dialog-story.js @@ -13,6 +13,7 @@ import { Portal } from '../Portal'; export default { title: 'Experimental/unstable_Dialog', + includeStories: [], }; export const Default = () => { @@ -46,6 +47,7 @@ export const Default = () => {
); } - return ( <> - + ); }; @@ -70,13 +71,12 @@ export const DialogExample = () => { function Example() { const [open, setOpen] = React.useState(false); const id = useId(); + return (
- +
- - {/* trigger */} - {open ? ( { right: 0, bottom: 0, left: 0, - zIndex: 9998, + zIndex: 9999, }}> - {/* full screen background */} - { - setOpen(false); - }} - /> - - {/* dialog */} + { @@ -132,7 +124,7 @@ export const DialogExample = () => { ) : null}
- +
); @@ -141,9 +133,10 @@ export const DialogExample = () => { return ; }; -function FullPage(props) { +const FullPage = React.forwardRef(function FullPage(props, ref) { return (
); -} +}); From 0dadd8403f4d09ccb30cc7c78f2fdd98f99f4375 Mon Sep 17 00:00:00 2001 From: Josh Black Date: Thu, 2 Sep 2021 13:13:55 -0500 Subject: [PATCH 6/6] Delete FocusScope-test.js --- .../components/FocusScope/__tests__/FocusScope-test.js | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 packages/react/src/components/FocusScope/__tests__/FocusScope-test.js diff --git a/packages/react/src/components/FocusScope/__tests__/FocusScope-test.js b/packages/react/src/components/FocusScope/__tests__/FocusScope-test.js deleted file mode 100644 index 23c08fcb5a2f..000000000000 --- a/packages/react/src/components/FocusScope/__tests__/FocusScope-test.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Copyright IBM Corp. 2016, 2018 - * - * This source code is licensed under the Apache-2.0 license found in the - * LICENSE file in the root directory of this source tree. - */ - -describe('FocusScope', () => { - // -});