From 82fa3c2af2e5dc3f477eb88048b7fdbac2b7b442 Mon Sep 17 00:00:00 2001 From: joebingham-wk <46691367+joebingham-wk@users.noreply.github.com> Date: Wed, 13 Jul 2022 13:18:56 -0700 Subject: [PATCH] Add documentation for wrapping JS components in Dart --- .gitignore | 1 + doc/images/ui_js_component/interop_ref.gif | Bin 0 -> 46331 bytes .../interop_reference_error.png | Bin 0 -> 31745 bytes doc/wrapping_js_components.md | 702 ++++++++++++++++++ lib/src/util/js_component.dart | 2 +- 5 files changed, 704 insertions(+), 1 deletion(-) create mode 100644 doc/images/ui_js_component/interop_ref.gif create mode 100644 doc/images/ui_js_component/interop_reference_error.png create mode 100644 doc/wrapping_js_components.md diff --git a/.gitignore b/.gitignore index e803f3ffb..c70d3dfb0 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ nbproject *.sublime-project *.sublime-workspace .idea/* +.vscode/* # Docs ignores _site diff --git a/doc/images/ui_js_component/interop_ref.gif b/doc/images/ui_js_component/interop_ref.gif new file mode 100644 index 0000000000000000000000000000000000000000..af4d75bcf8c7a1547b3ac7d835c8185b587838e3 GIT binary patch literal 46331 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 literal 0 HcmV?d00001 diff --git a/doc/images/ui_js_component/interop_reference_error.png b/doc/images/ui_js_component/interop_reference_error.png new file mode 100644 index 0000000000000000000000000000000000000000..9910882963687b4ba91bf84acc9bcfdcb9c03275 GIT binary patch literal 31745 zcmagFbyQp37X?V6Kylhaai_SuyA&&y;99J-NRgn$Til8kC~o25?o!-6xRg?)3GUA1 z>u=_dSu?XHD{CcrE4lC7bNAjy?oEW&TV*_Ka%>b76g-tT3OXn#H~c? zLOoJ#Jn$3+h38q1$1n3!j!&)yyi?CmP%uo97ytXhu0$&&8jThgO?Hx=g^Tj)(ajr0 zkr7*KQoX$|nFQwva&rGakDPk%so$o^dk#`2GV;fH75E$)fBqnKIDir;Ip?Og+Cf6n zYTLBQ^6craCwfe%kxUjdy$y$tDRy(zHN3uO7OWpfE=%e3nA6j)zjw`MuA+C7_E-=k z>vUJ=hLhHSV?<<#f@xhbe8pIApVjNJ2LWo_Y>yJts9-BIbskCrNyV~l*y%4@sa9|Ey2aAtgjB3r7*CArP=_W zPtS(w*)x?Gu?p+r+VcB53Df0are5B>(&9Ew$q9rO90hP}eSte+a^c}d^UJBv;JAsp zqcC;-b527E3O5!J+znwcSTgfTDPUIw+}NkvKI0LNjrchXGk#e$?J&xr19PSs(1#Rn z{PqJ=?FN5-4JHUMiF!}7+S3#H~X9IpiBd0Omwn+RIUYdys4+RueUxERzl&wWe@Aa<%5k}8~Ck1 zg1z{nMFtz@DXVcpe-N#emfVC7MrGDT=t5$`Lm=V8y60ylH}4O(;;*m@ZYt(`D1a7u z5crX4^(m2z3m09C-^Yhc5ICC#bdLDkY?ATyZv`ANZFACg=f8O8z4}@YhIeb%w$}$* z?czqwRNU5v46p+&O1*ovc2xEG`Asn=8eahAQ2k=~Ux3;VB~(rT3^V;0e~4>nq67E4uykZr9h%G=Zoip z62u6a7RxkoXBTj#F66uxFaxjBzQe@K1$-jhSPDyKOT-+6@8ws?j-bQhA6FVYd5Ku! z1A7(P*B^}^H2Y2_{l@D&AQKdhZ)=KCsqBhoXsoE-zDeE0o~l|Ha`KOaYrKF>>>y5| zPKU2sTl}@$UJhBAN|$~(5RCLiuBTgE_NAvkf^1&F)v-{?E+&Hs2h=zN1%xR7s9{D^ z4{9moJcYIT-lvbxQ;@boJJK@P1?zln3HNhC*Y*V*(~GJxXK+>VVj|#VyYwKteRN(p zls27i9}7xN^fzPKGrgo7Xb>U;H9AqUJ=#0!=*pVy4@?KsY0Rj%w10$B!zTBho;gkB zy@Q!%wxq!(5z#J!pGZ?bw$rroZ^mG!EwA{Iw5Xz8)73{p4-q=n+{oHGJ8rE27Pf<%Z7xVw_JO_N?$K>WcFYvDG3V z>r3-}Go-~9wBhqQPkqL^L}ck!xNEnL#o>*%1d;UYeh%PW8=tkLI@Vq$CPW^lPU(|s z{UyQ862!x(MO7UYtrre=)27ZnP^Kvsk7O4T#QQ)q>Rzi<`Ce5IwldEu|4#6Id%B1R zakE63xR)uJ5@hPV>!-=`Lo2%t0BSHK@KG~-HyVE8GpLPjlu( zF@jpG;&QX1@ihCR5$*;zq{Ws9B6(e!Mg?TpaQ3;vYP`%=M*0p#J6FuR_P2blXCA~7 zq;JFr^W%=%DPi`FV)-}M1=(`v@rh(|8#r7|goB_`KR;ZKAMW!G+XLE}nzatlHOaS5 zTK7*iwzM;6yexC9;<&r@SsQ~|Y26F0E`Uz3IWNeZDGnZdp>%Y1LnFVWmfP|>foS88 zXbc?f%#c|ShDlXYABIadc`gO*N$cpuW z?O(bemkoXGOvUFhBfn*h!piXm}g7Ym)K^ zXV2cr-g@MHnG6wM`kt=V{dc1ZZ^=LXYPA-FnvNqE;%XE@dmArqs(<#bd>&A$+is(N zK2;VlpC@gCPxWrOqT?#S_=G|p`}cljoW`z*>CJ30v$p9YDGb2~+;eBRcq;l>}UO73~b)ro$E+LPOBE(4){m1F4!9S{A}9bmM}UiKk$E(&U< zYhQ583fdu4;rHa)Ou29lgGAn(TyAS!o-*C^T1q z#F{$P(Sw|`*HLCBDW_Yrm~wn(eRRM*H@ZTWgDJQ})$E5Sy`fq(`C=3r`1o$)v9g-? zQaV)^k=mK_^31*QcYLE<6VP#gJz8usjU!eih==z@HXq1N-v7P0--b>8kJHejWxe=Q z&OFO|;vfMns`)mIr%GX0s8=C>FHMEzf8SHWxZ_`o`CO}wk)B$CvTLCJQj2!ltNbRb zf&_J;n~n8zP8fi`M!BOf1|vgv$m!dbj|`wor~u@=D#%HHzFvA3_5gdW-M-(g7O+`a z?c#?Hy5xqv)h&&M{5ou&b6R+VM{9WQ^ub&SGDj_d^V@oV2x=z2^E-Oo)V|&W{0as> ztlReqmrKX4TJ&5}`!Npa-yBeetaTZ#bAu5OT%0ky3OBRI7z>je!sF=`!{uf=9{%Xr zHcVK3>d&3sgdR3MPNa8UjL*E!M@TKi++YijA#S%;jef*yQ?c3C*PG_YzLY9GIdBaa z@}|!E_Pu!wBYHcxWz$DAvzh8c3yfO;WF%V7d}U>p6p#+~e8v&XVm&AT+zMS*r0KH` zfb*AF$xs*Gv!adZ+CdCSlaY%O6{AW-T&!V zdR0@^NNqsz9S1iyg~mlm2xR;^q<_|R3_&MoZ*ME_ZzA7)TeGl*!Lq(^>2a*6HU4}i z`{688pS&a66*j`!eb+T|na-y6Yd=xQqnVIecj~k>^(oZJ+Kw-xwU~ zaOXoy=)O0M+rV1S9sK6aX%N*oErM^Lw03g+Unf+Iry&}h?0&?s=86^x4r>x^Q-OjW zy8g6~N&P^0&xamw!NMQ>{+zw=aq-Ls2K(+Ek9O z6fEV3e{0-wxV&`o+}EpJf2D_{n-30+D4c}7>(hTqmDA!~@FsIC3$1q=Of@S)`ixd1 zlc1NiQC_(O$R622BcoD?RjxNEHayk^w7N3cco;R%%Eg-H-Rfa?! zQRTK!$|e`E;+bXf8$xPN=7t6L`*(6`HQX>vSvM2J!aJ@r=e09=zlPOU<%h%b=qOIC z&XOCw!P7k|^CN`7X`DNbR;HbGd{dAkA`hFVEpgCdcy(YGTj81ny7mI7WpNl(*Q26& z33NGvpCCWJ=edu9j#slg3#4QOh-wR}xKRFh9qIB3lU*Z(_w5ggUjARHIqg!Uc9)BJ58q(&Wo>r-jdmPzK z<}fN+BAZZAp)_W<%14MIJ~M8{=%go7-{;N%h|<=9Jcs=PxtD5bEy3ix?t_au=Z+BNd9M| z?@T-Y@7`R&kie`ucxdCj=s(x)cvokRk7`F@!qI5IHd%E1?21HIg16MaJ>+P8K#b}c zYyv0$X6S(&SnqfcxN8xJf>U{g+EJ!EEiIzW@CL!rS}3(7mkM{*wDkQ7bgv)smSy!` zGB+(XA{ntCgog zR&Bg_n)QM)wO3E{5u*cCY%ktG zsqN(R-^DP5X0CmKV=A4?`ur)3>a_W|76LDmgc2B9Pn$gfL{v7PeXW_Ot$MD5~}Y41+ZD`B%KO<>(Rj z->*>oAB1_I4_=vic)yansBL3Om|7_I`bTi2+TdOPG=@!~^yqI`%It<+@@mzxHk1%1-x3CM>J9Q2)XvRp?tnnZlDMYm3)}W;wia z=NJ3X3LP~ezQ6H(LG3HPTAB4>Nu&qFjQd;KRE9)VX%buS;$I06`#t}GbR2@hNwNHJ zZ;G(7K$}Wn5d6nzjs27YN#%xLc7+La`=1KLdS{VqUmp&y``!EkN#lq#9A?&0pp=DS zqVHlG>952xv8e9QQEQl0oO7hM=__=S&on@-X7$vt78Mvb?c};u&|6x8T^-GV@2S3G z1-Zb_rI~0XtfyOb6XhX7>6Dapa*LNM2TJW~Qq>ocTC0=)Nul&1oiR1cMtUTQo;1cU z662K#^@HwzKZ|oPQh?YlCRU+Sk$K!C;>ek0%Rw|CFf!O!TrC`jPLxY6Z~e{g3(z4S zGUz{cI5lo*MZ!^x*RZ7Kb7S?A$AL#F0LNEPw8#>gzJP#$JPRaO7*V|5_@YBA$Y5Kz z%3BVDSMf!WweVu zUHn%AiZ4&>8Wm|7B>eH73ApSGK^jX5FrQXb=7=?Dkgi{f0d9LQqV;0onE8JYflS^F zoBN;jR%vgkb6cKkWqv9*ki!RHKw9vszW-TgIoF9#PmFRB$4o_ z_MNtMzKyoz;mH;<^SGuAbG5~}T|o*X1^l3$}ioGUhL3EBl7jX?ufZ$5QWAj9Y+O&!a7?lAZdb0>aw z3!7b$X^^Hb@nF219@U6EFHpR7NREylY}B$o0-RRyFN5=|jvp>_u)Y;h~? z`_M$Bf@S{CV&pp2QKujYck+^gP&^JUoO9zegTv4%^Jtsv%2B7EsdXQEci9acH+)Qe zlX-b%f$($bc}P8Jfuk{ZF8KaDzANyQp?lj!w*G2=yHgS3;1hJk2Y8yULN4&2vE8dL zExy(>@6z7aP8%vglEk`PTz{kWoLRpa2COQ@qKzZry9k9(=?Pku?LlW5HE;~#0aE8eW#`vA=^v`(mz4Zab4Jkm5EU#^!1G9 zX>>WupI_OdbOyoEmDIK*7eA!cd}(v6d7Il35!WN6{64%staSpJy0|X7c3I?ag@mYS z(FlBr8(Yc`H~Rt)M9ox?(z-{Qj$^u%*e+rhp~4+@fUGN^u)=W(mGOmb&-RSTNJO}@4b z;5qT5G7!R__$2R*HagWe0COqt6m?w`GZ|yxe4rGU|w~&f{GP7Vg}SLMc+2v4aH3FlO-AE|c{-yejt^oD!n#@xJm z43{jj1sD@0E{SC*ieGB)O#j4agW+_Zv+Zn(`A2^%!ZpxoKV~4i@-Qa}uYps}mR~2j zs%C57OHx=YApJ+A$UJM07e7^F?rX9A>WelXV_yY*{$IQ(@@G1YuMUgf>mGVqeZ-kY zeNtQlM~lU2(dsH>-CE0${}^#IY(h;i-Ag|7BqWCECW)>^bIncGb(&i7j2^VZk}Z4w zV}zf#Haj`I8(A~3EtI}RSc)RojAk)~1=C6077f?si1@2DK6jp&g6#IPrz|}dY|8#M zC{<zKgiPO!M$NMP&6R^%cGyey43)*@BJ8 z3WQroQnBHmIlK`q^?!dvX}?Tk_XMW687e6&vh6Z&Z%b=HI?yIwnngXAb0McMDfaE7 z3VDq_-odl5kjcwwjPD_GL6EZk{T`wRjhC#IeE~REq_-ye%fHbUKI`3!U<_n8oX*0@ zinIq%wc`sLu{m&~CyD6kpN!2Ip5q7gJws-=h7wAUQIn)VYU`_zsTyAxg~SIa_p^M5 zH?gLy`Nzq&?GMi-6W^N@Lc!_YM$cAg5HShxs??9FxiuGo3H&^(+Q!46FHWecOyZCY?5fK#Z90c3=jEU3jPgkD8F0Y zP`(HDcc7}P_j0FDIZnKSy3fkwFf|o3)9QZ5zs?IkYX z;j#Jt$En~lF~w^EV8%J@by+hcK(u)OmTQHi23?=@a6nD-W?%lyNMO z9iddE-S1DT^4t&5<*nawXnp(ucaw?2ZsW+`yJY3_9v{MIq)_UZKo6y<%Tuc_#!WA7 zj_g&*zllZp8tWL-%Tpw_mVf-pfcML60d<*WqQU8y6 zlEP(CrChp?9Aq`c%(J@drG^j@B^WB~7#Lj#C|c>I`n^T?FJk^=PHy|Bzi%RiJujBK z7G|@Kewz;#2=!(LYLUzt2Wr)Z?)sEQ=D{?)ZvVRGZb;=92;hi=YX%rloi$n}lkrxb z(ItcRu?ME|@o$neAjML%{&^w~;6Wov>N@Vm!@4ya@ijPSq2p3ZIHZD885wDiD$`$>VI`AM*YMa4J%a zY%U(>yf7)Rf-Ev{F&!7_LKm__#{%O+qM9288RDCL&-RP+&avWTY^9R zoMp~KOb4hqRDlMHdi8jc5FE7*5r!xHU|geya?mSSX;@&gqNhIkljRB-qOLIbFWn=y zce1rbAee70{4Ec%bcBu<@M8B9M)&7g9F$;GbCD zI3xmLi~zy1Le8=<7)MG`h=z%wpKA{EkYcrmFV{tzuOhV-A3x-58(faQ@1n57REg{# z*??$V|JSUg$EJw!15jZ0k{mMUgYl16htYZ_m_a-e;I{;^r32{3Si2pMR#|2aSDu^& zdQTI1)(ct4Y(tMV1$D*vv<4N0O#`3UB-fH#sd2)tJIXX!zGId=FgBbn$y4=nMwqKY zrY{R$%wN}oG^?Cc5E5-6pIOsG_~yuqFcucB+=@=-=5gwyNPAfVMhwz1qlF@w&<-Ja z??8@EbZ4LD?}CF?Q2D8JNgml`b2A&~T8k%8F~j!U5Y;jn@AI_}F?B30C4~fn*k%^I z#Rb~yYp4FM_4SjR;;}w95X6tfZimQtq!*8dbI2@_7j3i=3yAcwYqYaYaOtIuU|hGe z;*oiWCd_V=2tkGN0U!4j@;XON(U9&Nk4ki}%G|t5{cB6sI0*`CY*zqnFRZ<9WB1c= zGvjJ=oqz&w@(^^9X~UcbTnPKxNq9e6)p(M0OoMT5fOrH%$=1n0_*7G2-2Na z>zaMmi7tL5BD{!mwS)2(E!s5Jw5nc&guH4yGDJPX1^`LzTo z1X3$*BZU*-V8cQ&6clB3NWqOVDoQ;3&3Pj#%9ZV*g9nveGRp@U)Z>pF zX_o=8jD^J~Y>i?UWPaa1#JtrHoAnq9u~B%jsap#anaX>-JHwge?VtH9w+MhCoVk@_ zw-FC{aBoMwMb2%*$GZ=(hA?h`S``3_YCiECyGCz$L*|@phIPPE>Cd&8sA&rKFgq|K zc=MvJ9T2>nsQj!+o3`N9BC@Msp&0N8im#C21+FP2CsS>tj zLSC9nqqVI0AJuw?LqLNqheEX!8u>~-uh@=}gawGole@V(09}^wsVw1_KPaCae2HFF z{rn90ZiYQdE{cM~B!g@*NR$wFn&3+V$u|zKWQyym)@>hh{jleaoHT{*1pvwK<{- z+LFx_N_U4HOp65IGZ+L2TEE(em+hf(p7RAd#84_3E4IkMVP{X;c_zIKN{CA6cq4Hc zsVTk!VkXs=CGB%5*+gFw;U6hatB<>RwjDPlSMwkny)cUi7sSvcWN8Ni|6v>%?4s@O{q<^&l`4f{QS~h zny3-)XDdOyz}#Uhw)@M~DIxs`IJw)~gL3C|xVzH?GpVo}0niiiZB599<4??u0uu+{yTz1dpIJC7aIXX+aepWD0nAwy@Lu|D_0 zSpPhPGX>UiR#BG?Bb0?X`WP$>)Y*JLmRQ9HIZ8%}-?wJl$SJ2Y+H( z822Kz4e|ZB*B@L7nCb2LH1zzS4P(n$9xrM6Yux*MN!env@N(%qvsg|MhdoEVEkMyw z8r1%)(!D_uWuk72>Fd)B(Shprxh4Mw1>=eSQorxyX!L>{Zw@&hbF*)nrgxEUPe!Zjx}T0t*3OB`1~w?f5g=J9Z!c6U3mFI4Y7Tm;>O0JpVYsf1M#IiJ$CA(uHzFKwNZV^V$IB33YBgg3J|F=GZ4 zHauYb6q{qz-S1=ws*e)8sXJDCP~VrE=k_i2ysvzVeSGC^8oy{4%Nt2u9DRR- zyV_9+=W&EkU@JVtdQD39k~+8o})>m>6K^Sx0H`s)(E!i-|-y-yfHJ ziYs$aX0KHq4?yj6uV*aA+CnPQd&^hy`4uS2@=136A6qzVFnoZ3U_wG6Wf8Xae6V&! z5WK= zfsN%$Z3X?FR@g^njZ@36-{<_5VV2)mGlp)6-0^oPNWq=-1_IQT@O^-P#!H>(psc02 zv27$3m#g)mbX%S(KM=$J?dFfet_2#JglG~t~Q#|9X~zM*)YY0~}YvS6)N z3U3zv3)+Fq!3|9NX$`RXZRhBtc&XV{E&P)XwG=h@oK-CE)1;SrQdBD(k=n3V5T6#~ zX|E&7b}8c~2z2W+_x07j5vB=cB>_oVq0V-kN`i|o;wb-5Ky>F=K2QImiE&`Mads52 zUkYS!Y5$38QwHnriVuOw>n{^5r@Y#L8TQ}kN&WIkx4_$rNdB3WY*6nz+ldVIND_Ev zDe?5iTmfO+`Zvu~aHeQ+-t)6a!1rnPTbwCnl&>_R_lNZVrecACQ!`2SeW^+u-WrcS zP%8HQ%ZNvW`dMksRO&6;pob##y@VOeZ=%H&b+}hPsTIJN*0e@_>aRl`h54cyB((+| zHPJ}K_hkC&C6IaGLtP0K`&1y);ho(%BBOL#9kwKOX!zxh;D0xhA+8XCiZ#$ zllYphy88M5e2XqwR5?9|AqjnVcdBx8V+gskeKGlc$BDe%Tyl?&p#a0kajJb8 zzuWQqB#KtJnzw0^p1QHEj+gvyX;@6LR3Xe;DC?uQ;iqQrjStsk6WK86N;#70T94u1 z9h+N9@n`xS{2=L4P47Pz`{r{Hoom-0ozUn&{t4PsQXk_)2&VrFp84%V_mw$~W?7i7 z?+FX^LV=*eF9+0pnZ6(h-U$c9GnQtHMjCY3I1KhasDgiPJo^v0&x#r}=&;dZ1{8<( zhiX6iA~!W2Y81a>@l2}F-D|JuAk69P&8}lpeT3F(?bLWh_rCGh@9$MPDUX*XzT3R! zUuo7`*Mh9X1DWz&zpvPwUZveiekd=u{p+ropQoTIn@ZS<>=9WghY!wY^q7a+wcs)XpQwKG&-7 zh<%;WdVEFVQ)?p2K>c!yX`{Xe;jDpuexL8`HQQ#pX?X(W5wviRXq~@gR@}^e;Ys9_ zFPVocak_OhP$|0fFfdM4Hiw7ygow)US8mF_l)7c+PkRx$&{ojw<%B+T7(Kh7v zTYZRW?EZrsF6Ymu{;)|`gH1Z$ZED?WYulL4Cn#FRAB|Ta4IaJf<@H-`MK@DRqI58B zz$LuX#Y<}N1=k-PL8ER$k*U=cp^f8NF8SY49-s{5YhjHE?Po4}0u*>>*nW(1tAo@XB7Xdma7hk{a$L)*xrC zl&tebrjPAKbZga3y~P1yLyu$PA433YsqDE&b67PrOZp)M z7^fQut1C6kcg;usK0uq7J^_%17QaEgHhu(Cx#bLNG%A3lMXrN4x>06Em1Ms05SW7iq$cJa-2E6`+h45JH z=IAwg2T%U-@t(?mljt4jS`n!!lW-S#3wF10QagA%Z`nf%r!5UD1x~wa9l5{X$j31} zc>Efz9dL>19;~tQblT(2U2yZY?fpc^2g+=2CjM$9FLa+dqbn53~=>v zg6#slLJ?afEcVOxit;n=1XAi@IZ4bmHoE&0DjOOzNd!}aJ~3MIbtmloJTjg&2_Ohj zj7Dh#A@c^_n3qb4QxVRiYjNdka@1y%aZj7S1`U;$h0=oVHLaOJ44dtlR%+JhP%*D* z?~DJW@z|aRQNk5vuN;i#;^8h5)}>eqEW643+DA~gn-;Yil2`PEB09jI7)NafRbepN z@$E;{x$69qHwv2VMpfBoS37r{-~ovlI^F_aNEA>gf!rjVeZ3pfOB$HwjR~2T68w|HE&u-7f_F?us^~g1typk0hgL?+q+> z;Ds?Ob#1n>Z_GLfGRQM;I7OqP)!S!&S>|Vx2c>>b@Avuff(x)cBy%nu1flX|Mx)8^O9GPjK zhghec5%AWpYn>}$xaPvwFgvP~{u9|ZFvoez?l}G2^UUJ}{N2G3X|=C|jqH1>ForO6 zp$Cb}aN<=kJ%2yyBHwI`M|A(hUSD2n=r?{-)cv2@gP-wV9~INBWZrID{@T_wj`@H& zdi-i6%+@OJVHr3;@38P;;WJ|={DFQP0l10Mv7V2+^E!T8JHP*nwk}m=V3rcoe*oR7 znzT!+H}KB^Htp$Jis3>=(JZSKx2|t!3y1v@fr{m)Kt(ERoKtJZ+p{kRuG;$zDN&p6 zrsk|!6s18&EBU9O{(}Y}OGwesL-+d~OIrsiH<>zJ<$+5Md`EOQv-xY1W_40A5R2~Z zkqdv4<&X6r_pWdaM(eh=_xc-j{s!^lIyIOagF?q`o{Nj=XO?5}NhE0Kd)KEjIh~;` ztEN8x+Tld?9*cK0`P4waWwlVY%$6?WhowGEYZ4?@P7F4~rN>r=ZjkZs21d7y;MHaJk5~gv8{LEGqAZBx_Q~`V9!T_) z7)gwV<(!?dqhvDdU!EX8Evg)8(Kfxr5``ZJNGnV{CQVCP;=;85DVr4LuA93qO#X@s zBpEEaqzvG1_8_>?Q~n01!mO@%%}vRk)2q*(+V$D&PWDTz>q7$SHx2xKOkwCJGZ65l zC)P`RB5|VwK{5AF=3HPZVZoJVhk8-^=Lbvqq1AOYZIK7Btpj z!r^3q`GhUH`bNVH$92`%3F?z~xj1+*$Aa?KT3Qzpy2JNB(=fJunkCI9Zh?a|06#g3 z?zaKw$oQs_i13XvcvTT9cRJr?z^r8O(YGDB5)XZb7$VtGZ-aIHXaIT7HLl}=qgc4RfUvBO+Q z>A4pQ7}wBMY#YGn$}w#WRMz(>IW<)J{_e7ho2GM3onKTb>B36qL8klJj&VdCZ42Dk z7F+KWOT96P z&fW_45gE-jWk(r0O&AApHy$Udg@fOjqmxyH}!fNhCPKctKO5hg_dNGB2?J12r zHbnpq)Tg>}Ggqf%Jlw%-T>vLu zJFg2#B^_Up@RfP|+N24l>ZJQu+|UG8pWL^Vz(qu;yb*G`k9VJpS$`}Ttas=^7?YkN z4Xdfto-lm65R;ukq)P^P>B6$Fe#3#-@RMvXB>J`1gqd@nSN65Ry!^5iSX#5&B7Vmg zaI!69M6ws-6s*GfLqE~@8k1u#f3E>~G-HZ*Z+P~3@S=0RW^vN$`7+bhZ8KoSjrz}) z>|jh6$OUlVpYMpV>6sK_thIG_Ri!k8Bf8`iJ{gy?6!bGD{;;IwE2^e%?WBkT2Zhfw zI2gQRRMwCSpL~zD+H9@`!Sqys(u7DCvqn_sI*wF10WC7wgumuNeu3Nk?rvpp9-lW{ z9a2vvI%dFB2fhXH@2YM-6)kYy=a?HXeJmbMxqj>1KRdv<^e)Q(I!jeY*f#YsmvouG zDpls*t&sszqF%RuzX84!RyKr37W<~=a*g9)D!TPyurkZf1H+%%%hJUzC;H%32?D;Z z`;8|40r?!_)^hlbn{Lw@m$8yXX=eLgEKqte_0}{u>2+r=9JOSpCiO-O*1V=@18uo> zdQi3Z4p{|;B7po;cCfR$?%`E`tFLISf2-Zu5%C+F-~(>3*9!Mj%l>3K;7+mnG0`G-kd$%9h&qy2wS|^cInbW3gfXh1S!+tDh$S|s?PJaKwHqY~a85-{=HPs&^+qXUBnb)n3%6GqW1_rYL zY_CIuZOht%5_oByk++Dxb-b$~=`tJ9nSD(46(-;miVSwNKNzxZ>O2m22?$qPnY*Ih z>d$+~_w7ORwH>@)$`1m4)nI+I99aWnb*+E6bw`~`#in`OeKn@9iDfQ9nHn0U%{KQU zdJy|X@C=g(>{BJ&%kDk+^_zD)iIR=wdmB2isOBC4jXpEjb1W|6?P=B60wqkDu(+S$@#Coe8A!l0}f40e~zBcJ;zoJK?4iR zbrixS=8A*r?MO-_kpfKb8=4Mmx^~>Mdm}|?pKwfoLH)Q0$T+u1K}s?Z`Ck+im(n|b zF;JPY12Dsq0V_%mV;FIL$uE^h5iN+n8q9PLh55h6&~Gb?C_W#FHCsBnsXNSvnEr2* z3X>Dta+fYeaDwWak9#>iZ<4QDT>9tc-k+8?XV7H;T)1G&Ao3){Yuu8>!xFJ9AqV9$ zX6RGIotwvE=8*&97QpXyRpwBR;}|gHx&)i<(Qq!XRe^sVLdDXV(1%qwkGh|r$@u2N zn-p3GU2NLQ>t2Kb`0&CLPkorUKst1RuN=b$@9d@!e2&^l;mG#-pA3INccL|O+HWP} zZxz5f=F;e*6n6Vg_I8^gt25-&O5pujY2jHh)`4Bc#O3Aq(8{m*?Z}|aQaV1)$G-`a zWd`^fre}A_M>j336^EbAXEjz$=QmCHt4wEd$%fa713O65ZN&qm6ix0{y3&?k+HP9s0l!_Hp<;Yd;hmU`EV}>JEPTD^x)^TQ~yV$u~vwhk~ z6UI-3o$Atdp^Cg%DE}m?6J^Kb=|b)ecY^d7`>pkpt5)DG@V_eYSiDK(%b)Cadih!V zM4FPD_1s6A){B-OY`ExJUh=jJPky5xp}zNf9FQLK6UWU%+_EQH_wuO4MB}3% zca+k;ExJzGsig@b_rNS(3gqo2kG%SSZioGM#UfgQ@@NX#5kR(c_$cE6CmS9Vlx`76 zjz5?va$vkI(e{0KzczJ>eM)2MqACbN*MovWSaF`2IPjvFWMnd0BF64d$5&&_pJHk+ z(?WRmdH6DIe5wr@`FxFGdn)qnRfnqH2EHHnT?%p$HG`+WC-EQkJku_|0vy91xIJf0 z_rJ5l-k1@fi0sH+69|p>O^0bu49Qfx{e^1G8gUndZ6NE*#6d~R0{v1^hY*l>0+zE= zK{;)TxAEw{^0}$u+_dt$b{&kR)UavwGB^+KOAnmPUjjBFjh?hJ~*G4-QM z9!>)>1asSd+;x4q0PvHu?UYX35P0uB_KXiOL9ra*=^pu9X+P;zi+J^R4SiKhfoMe=1it-wbZn@IyAtycp}D;`z_Z;9$kUhZG#mH5B;vo|Kh_7^Cx7Z+8l z*z&8BT-X2TIOVru%l98p3Ve3X{^U~ye)D9~f_V*t2*84Tzsbm=L7fJrAK3V})DSAV ztIY6^MK`?956}9hQrgB&hK+m^LWlf1F2A?kI2)6D8=)MV9z#c-U|VRSQ#cwv?G<%L@|g-)M6Nd%vx@MWK(prGBzF+E1> zC&WtWOYO*HP8_K%+esC*48~Wjyz%(9u%OH#eOVmUxogHE_5$S^8W>pIvCcI@s~*FW zT0s4|UGnadwhL>E*P>oa=I?MDH%-gN5)mrQ(Rnuzs@v$n?Eb=c+n~57isuB9eGQG$ zP2{(DVTd{k(8M@DLX`phmL`wu((85pa=u_sdvicjvBmJ1WlHYuy%w>*^@!mn!g3tH znd_k~hI4EaU32xoQ?wykGV6Y!V(Lo5w14}7X`cC;dWh++J^iNNB?kX!?UHE9?SW@u zXvuL!YSAqyD9G*2=Zkc%xchW)=T1J&Jk0q(mxQ6JvHU|zZuW5XSb62kQK)ftBHwp} z{CZRCz(eXwN$1yHGu#N~9I-^@A3PPyy_WKm(V%%HXf{}BFh@6ZV;0-M7Dc&m*r(gSXu6I;e7SJ7o2$&}8`rBmVq| z6x1+vx|MbN))JeLlUK{gLB_e0R3@AH38z0V1+yk|c6xsdUHb89;-B5xS7>xv65L*~ zrWl_Xa4D4_95pv=@BN2BR^BJ7+tAnwcd8p)&R(y{7bJ&n30%UBxxviyk;E)Z@!~Fm#qAV0`PlB zUB30K3!o#o2sCoKlao%(Q2TTT!0+8e8wISDU)|-C z=m_^3DH;xX+(G`X?3OH#U_C7Qnux3%=gr*fG;ol~6oD$BsnyQNS(t}{7S%4mtfDIy z#7SIB%EM@A(}aRiq0xzLB;%?~h5VD$|Iyc1hc)?y4I_yB1QeAL6;SDJkQR_`*cdfJ zN?KxcNf>l@*Vr={(j9_?(lI(zN=6AdI=+GL`_Fe>-*^7oo|E^v&)w%f7Ncc^e4~&3 zWI|HR?*DcQ>%hV6>CM2Cg7Ev*@n2sP0fEbGtu5YfEyR<9y*Tf_-5S>CBw+pu?&r{# zmqDD*UjZv6u8Gir?p~i~c%>P@!dmj5qpps8Cs4Cc8$^72jiFCm#BPEt^D_TDeMpOz zR~3<&W9GG+f07_L4=Iu2@SCc*)zKK0G7dSp6(dm}kgUI>0DH6J?|r~RH69!ELF@br zyGQ<_fAt9ttvqsl62I!$ph?ROBxYWb?^;fq4&LS2=cJCtZ>N)IUK->}#G?!~nSgr< z0N-YQKjCt7ujp4D{wPS(JEi6r@KpfvX-3wUG0e;XQC6t4-?F?}+ovp+INee%n zuCSu705t7fk_pGB`Y2bJY`0>vjXF-`3O_kw6 zx=-Hn-B>?B<{mS#OmGz}SHSm_aQMiQ3bM;$!moNA@R01$sv7JyKv z9?fD{yjY*>NO~!IdIj|`_&fGwgP*G%j@EO8bMO6v`7ih=I?YrYXO~+S%gCiN z(?c`p&-;6@8{X^Gc27vV?hYAA5){;Kp8K?72>ncJ7qO#ilS=6=ZKQ{3Hdw1_+rPU# zZySsW7InmC3TND#@%c|@-*!<9+tf&ylBMwGY-d{gskf0vr#&tu@o2dWTD}Uo9}lI1Z%A?RAqbSdr_GI~ak~=Lr!i648j`d} z4ZloFRdT8OuiVnMPwJ)6Bb7l>!6N!LJ;Q1{?-X6L&xB@{6sHRk{SGLq{>J=<@8Dai zbxwqqUH6`WhJu_p-{R$cfaMZA?9UmHyN(Zc=w$!sN#K zG9auf$N8MF66~CfL`m;aEG{hkATGU6vm*pJ(5pDtpVqK1 zTw*+vzqZ9E9u?`CwR}*o4>?hbP(M!JxTDpQoNUgg5VDA>A(V-g4nR`#`=NSuXh4DQ zSWaV1Is6Bbc0-jjnA9&ExHntW33C;}-?LQKl^q9HRvro7IzWye2uJcBE#E-fb8LDa z_U;#m&2bqtG#GZbO=;ALS9@vE>Pkz!ko*0)69-f3%+Ak5XtdfZ7W`L?1`~~{Ix1Ga z$v1A;e2YD7Zr5BMRvW;Byg0ds{OYZ|kRIAR#nLeMhY_zOQyxa2Csy8v4q}&j2ybw5 zQCUuW{bhw}-KaABWra+#mCI}%V<^!8N5ktrpT}sXwY|g}Psq|8!;>F{kKP?aUG6gO zd$VUzBl!4tlbRIr?7ee(XJ9+EYq&`8Jwz;{lAjMvLv`@Cft%1JyI>)zi za_IrvH&Z!6E6221Nt~w+ZnO0j(Q&-Hv!4hlok&@0<$5J?1+NdEn|Xo5SGUVQx_B*U z-VHEczym~jn|YGW>@N+k(C;omRkE*ki{V3gM{-xLb7a&F2I*UG22r`Uct0%RlR6C} z@aI@ZV~TH=z3*V4$qn)q*HV(Ia{Oyi5*u ziF0D+z#6T}9JX#LBk+uSE9pDNIji4tJ>^J(kgza7tl(&Xq>V{`Dh7jJEWM9IuGSy< z46g~Tx%>DHCx5Q}aWlI#dzRpqFL~Otgm~@2|CIK$ty)Sm@5^bg>1TYRuv-dQX7&VL ztKZT2^sIVu0ZKEdh&pZN;J)zH+>!qx1I+8mw6jbbs&0A&h1vI$)rN6Kc8W<&5V-kF z^p%Gw(q)d^T>dg%M(2noC-@xndQg7NY}}-z6Z1`0#xip<*ax4XyS-hBpDyQ3P0faW{Zy@#VD$P;mbH0ce$I@a`ES7n#mYG)s`}J{ zdbR*OG}r_*#+l3e+m~fGeNS$Sl_Vaf`YVDLD8TfP3v$yK9^euP6h?sWp}mJU8^r{u zv?L9*SwJqDGB-Za1_^bzZat|sHvBn3y9Vs+fxLY8e>{*Ss@k7S;rPz!?R9DP%EEQ> z;%Bz(Q`KK19(EmYs6N8>KeR{+<;|HQ(8j|D<+uCF__Lows?2_~XHXXRB>lhW3h?i`?CkLb6J`NOIk|`+3HasUhAa3YcL%3rlWhh^1bKKW|&`4Wy@eY1;aBz!y zM>FB3$E)NZ8D2H7%UomHh}nw3RCsQvq4^nOq}}q=(w`4SGsVL@XPez??>QY4YF`FG zwOZhZ!^G5kP1!C()^6%!-M8@8HS9AAA2$>T}ykwf)me z07$pt!l;+j=!V3tc78fN*eNkOe5_%98AH|n1Sg&Jm`AO>;Q@N=x051IwCp!Aw&b6~ zi8d4KZr=Yd?glz)%1;KHsB;G|h>=M$o}3;)(!avm(a;f(pMILmv{L#OJpH(U(vxAQ z&@Dyl;AWKSdz-dT7>YPIP5e|M^-wJ+m&_-LY}s`}En2$Vi3YyWo!oy?9KA6I#XYi@ zgNl8B4}R*kRRnTkBmA!p&VKg;iFmlpnEWkK;vvIC{CD;ex@z~vh1uR?Vg`5znBQ@@ zpiUm4b4by&I+!8tER;oG2XR}d!9(Q2sQ*xs-~_c~Z1Q7T{FMt3og(bm5!Ci;z}Un| zqhVh1?hp9lEmnB)#ji;fvMV|giclSXZys<9oxnqPE0OM*dE>ate+CsHt0ml%|8XzM zU`fXn-wZOOt?UIpM1RrDk0mB-6}Mw#RQOe5;`dC9IR@`1xful@^kx`8{+<7+HZ3zr zp{dbttNqGcLMbcgH-ZnDB%DL&Hm0ySavQLr*#ler4i{5FK`4Q{IY-H1g8$%$#m1)-5Ll{ zje9&olIx@D6M7Kypk#9Tirs1{4K?e$IlWWm#gMB1f{DG2JBa3uxhfv|@x9`L)*d55 z-j}n1A21ry($ZNc?R)X4a8{}JqVB+ ztK{OHGz<3Z_ZzV868|^fTxOWJ91717Q+g&tqWV7h^YUAis^YSd=R?6N+&)S&>qz3( zcbEC%30nFPNA}!oToHfj)fT<^1W`|FMI{kYhXU zgHgzy*`#?rDFFl4O2>z|z;Hx_sv3XSrMmB_0UL!}Amjajlqn`JPFeUv&}6@= zOiT8&*RP~DeU(+&mue(%n@>pzxnWF-zaJ)+YTQUKY)=hxHJ5mE14Q($JNCOomo{ym zhG(u%ht|1(W>~G~)A|My)Z8b`9LH}xn#rVvbAqm7Ya7hF<+u4uG$;b(sb7D z&D12nJDB$6Fm2ksjx_*z%ZrU-Bqn58k#IO&0)V1jW%{&Ux_ZQ zEQ@N={KlXsWI*6AoQ@^;1?;u!OAok}54VWC^Zs&mrYAkjQjM33AMR7|UbGmAgu8aW zUm!C6n=7Wj({LiCBUH6Kpo>QkR7g*-B8R13-W&ubbJyir|I(lz_%k&mJ&>eYmV>p6 zB)Wm%TBK|Q3DgR(UXlVa25ixBgUq3ItLvS-6LRR~W9KBgD_nSYk9m27>806?B0m;j zsa0jVn;5bAEwQtkF%wGgR&k^s_FC$~IQy&yqft&#mH3f@Ge^AKQXbT`VsN5G`y(E3 zMgum;a{mvU&*mBVqLdIK8lSX9M{j9M-hv!!XHjujetbcauIU=OT*&~7 z8j%;6q4s4$91`J|M2A6b&0zE&~^l`D#9$1&@3%iB=zLHgnxE(QyzA~_oL+E6Hair zOGquFBVL6&cB~-NTWE*N%~#lj&u$dVvGe?8uJmckTRgf2Ho<~1t!YRpyV+5^7cC`D zC)nR2a!&KJwX@$MFc6WAf-g<6*@m!QL`HGLj@XS`i!OY{vwJ70Q0N`gvTU*U|jpXA`j!{4J1eZRMVHSmwHO$ zi2NAz5mXbZbYdHwlSGR%5DEN}AyJA}R$PfwYndKJn8oGqq;{xi4mX!9#0Ai4@_{~N znB4%a(Gx^p>nS^L-?}`wz)=7b+pM~gHp^2R9$=%3=q2_^+b1(xzT4zAs&`rm(x55H z3Pjp1&(2wobXaIF2gIDG)o6OikSO%(-;E;;y&!mB2NqhmHm-b`Q})={EP+jpW(g zwG%KRD&=5>s$fO|7B027ARd01XCEKDVU&w!vjtVa^=Om9_-|+{@AMIP`dk}2Eq3K&TYl`2(6$SnB3Ro*jmRrbl%PQ7hbmP#-f za*H0!0n&Wp*jRJ>&C(Udh(7(r^tu~ym?w5foSiB(f3Ga?vic@k zF8ljhDvN7&k$+vZenOFBeYN+O9I}q#XP`h*s^_OxIm$g9LN8-&(0oM2E$dH#oaF+< zq8xUTdQs1CF-qtC02WFPYMEuja19^){;Hj&m;XVFHX`E$`VK?q%@d7@ts_Cpt*oYc zz2E~K>NRvOmj@#LUa7fitIDWAP*8LNk>?;23kZana?k0gorD&ME0ErlsK3RFNlw3bDl&sJ1mW zrhQEU+yU`$bRJxtySO}6wEB1~)%kU#cFU(>kq0uf=QnR!{Vs!{e`8Ckq^5CgD`1y1 zYT(2NGuFm0#QiJZyXi?cT53_t(-(M4f1ob=?x#t&rrN^gR8tD=P4lOb9To;>!NtOM zP%q!h9>E*BRP$f(w=#DK|N9o3S*>1ihqSJUY*43`{uUl9;;#49ZEnQ2YOK4RYruDp zNfjpa{Kof=nglWieL}pKz+uINV*u8g5j~s8cxPZxQ*B)Lv zk2*e7basoy&>w7@h`^ryNAR07S^vRBfBKv~>Crj8z#U>GrNWO)q*CzN*Xd{qQ@-?1 zM0oem8^ZdzJOqqcb|`OKb~-|CjseCAjNl$*og0JUsoC=5X^^HfXSGAqF*q7P5Y;{y&+uD61 z0%Yzho>AiAEpBGbrXyL&-!n+We5(JHQNK8MX?2k=o5v|s>}%b4iQT1(e1>1ta(98( zist5x)9&@=r+^KfMiT{DeZV@jfwN zXaX^Y`Y$#9b-OlobQYts>mj$%5j`N5R&*$W^hxI`GdP)Q)?FY~hvj1ojL2X@S&n{j zmopBq>oz4mQb*U6XBwb_vCDB$7LoUhRd(vq$2uP&wvMNW0a6I&s_ z>fd=9dB>Dh;X?Z!_-{pQ8aF4-q@|`a} zxL(=YTW;HcXk=~mov+6~&a5_reeKLRK@%a$f1-gkyLmHr@zG{WmgbgN0`$)eEWZD= zo5>3;6LKS@kC?>jMC_1W-_I!{9w@U~krz3FxL8Lrs$!qJa;0>Swcdkf+O}ZZ_Gk4Z zfr{;9*oo}fGYLbVLJ1I7t;>nX!e6dRI3x4<-G4FEUQJJ*{eUVM@#RymC zS^>;lQFbOs65`J?np$H29L!?4H?0YV7V3RUlA%MLeoW98tFq?>xA^-37^@LJ4wm;Y z`C}9S$?*5u(XsuXoo?LlRSW%;JURS0BhgN?_HAHkznu%iYL^N+DOFl-b>*;V?n7)< zujaz|R245>C&G~U?$sf9iT0Bgh!7cbHN7gpfI6~V;BC0p=X;|^!084^Q@;4gq;IRKPxwXANsl8iSGLaK6oKxs44 zRRD-r%c?KN_*4X}E9r`PYNCmRDyyCB@c(Uay_%Yx`hsui0k^?1bl_*|kd+A-*5mS< zX0jaNBRlh8s(nA zSb^P2=5t%bo7YL-fXJ8{05Z7&IG2lqM@EV-Db~5IW@`;jK2tTyp|97uz!JGLjw%t8 ze6GZEuzaXaQVxhA0{=DuW4E|zd)Qk)@^4KmzxY&yS#I2#^gy$9hDD2JSs+ea7~`dU zwFJN|$LRnGHr39iFne-G?aO1){OT$k;l5f5pJi#NuMApli6T%NcAyS$cP|~efmzVS z711bUD-zSw@F=O}&c!iGO#RunNPl}56ov+IGQd9Z26^%)4j|up{aeziaLnm~PYB9n z0wKQY^ev?#aiz$yd4M{a1|0beTo=9z1;dE@jupV3D}vOZtnN*O*cK320Fa#m0TiD! zl)n+z`Z)tWyG&qsIR8q>>I%ihGqy0hi;REUycVjU5bUdt9_a9W7l)+tQ%55#PZiKv zhu|#Y;o+&Xd|*sy+LlGFC}|kgcw~p)9|pzhDaiKK&O(mhi%3jJWA%sodkA1meQEr@ zw#GGb;!Sl0jGflpe7I-IZ;v@OMk5OQt>@13fi+jws5>a2QPI#g$w3>HVQap%sEr*B zC>>XOu_g|fI4zjWyxD;N;w$_4HHHiXb?^F^`6VBXb@F&%_TA~p%+@OAV@rrt~MZ?l+=*TL0cpeC8&DZb*;q=F>%Q{n=~{GM0!p#la_UjR9Ofu<6Ji zp=9u!B#_m5VZZ{H0*r2aJsC)dLB_-f7dxwi;pLv-tPXJ`QlpR^2p?qaJwTuu@1$5X zoZr&F9`bFprrlO7pxgNEe%xS?w;lLUkA_z^b*=IcEUDF#?XiK2CKH!v8Y;L^VSEk% z36OXrxZz7(E`-{tgJWChN3-tR8HaJ!-)BA?f}o|(y{j{_4*tGGvo({0;ROn1)?v*B z7^WykHj0f&vg-j~41tFls!P0}H<$x>2v#qa6Lw;T>qCd!SUeYvS*UOkcB0hg%L_?5 z0yK&BT=tAaKd1d&sdBzvRswAkNs}Dd1&!===(>+L%e|b|QiwNwNJ~=ILB;^)y-A3V zIplmohtV44!@MKjN2iG6)wIYH21n+7>airsj9BF_%IK(=>2`P*8mfJi==4vF8T_GB zm-QpfIYP*1-)1uEc%=5^qX1bE&b9dBzt__{<;}<2k^9HE&Qc%$I+SoJ8M-^PT`=Ut z8)G#I&e|Y(3Jxp;CrP>M5#=-`hp%E4{Kh!@Zbm_%Nm3t*H{jC?V3>lOqG3y0c5NBy ziW+Vc9l)l8sgT(Fv(I*rjSdFMfRZ5h`R!}mZ4Yb2+@-WnTn0>^z}uCK87L*)x5xc_ zvwlc|0}Yo-^b05#G;%b@tXjTM`cSHCQCis8b5h5in`4Um(3&+cs_jjGYy4pV!o z{CJi0I9T&ALTKja7E(+*O}6}v6A_?c89ZoFa(OMbyuH23&EB5?b2{M2Zu_B_&r?@A z|1woGY7f4*bYvAt`ECOj0_wEAU$#U9g5bCjJSIT6e)B*POue>afmH(!O>NdsizWD0 z{#9r9X_@+5anrfdovFtEL|+rqjKnmnQQz075j>oJj(J^xp^d8QoYln&qgOQn&1bso zF=d!@AI>;jWb74U1Vw!AjrrPBKnOp#3H#$GO?-S9+S56?w!1qV=ug+H3#ED%UVIN3 zjj|2z;~4(Yc)B^0N*wz8gZ0zt3wA4!@xYJKp6X*2V;k`nB5*zx(SKsu<)ht8n+N=k z9UvQ9xyEdTuz@aRc8 zE`IC(UFN=yAC(8ln%V7S4+M7B;GeZz)eU0ac028Y*(Y=SS!gvG|9uP%oWFOynxLKF z$A(n=mf%jAwo1$Ep!i zZJ8DK7?m}EfQ!D+^LsbEBZ=}Kk^~1qb+L=&{HLg!1UnjB_gEsLf9{U8x}i>u&rBVFNwClvKNv^3j7yvT zR#cZxrdaB*g+Sn+mTsRWDx}Y121lM=b@UV}+*pihW3k-L0yIM5YvBp48^DEy2wFgA z$TT107wP!ov6fUxQ7|a$&A0JI7i_S?qx&Q$b);8~|4f#g9PR*3%JIwGE3KC=z?v5c zv@?H)fNU#+-3+i~Qn*y&N~~QSihsWFZHlf97a6iKZ|ts`WEm!JKyPiubV!frsvi(- z6Q+FnVrlo!aTO>_W8YcY43c%!0xYOjtnPhO?pe6l>g}tt;!hqtaFSaC`(-!ro-p{d zUNyzTYr@bY#nuDQ$ie6G_I{KlKI*q6w!EgMjh1WlPE_Rg-jJc&W=@x+oZnp4ezc!3 z<+2CwNr~>IpDiJ7kgkzy@g_t)_VO#jJSTq5a#wF=GYYDadD_28F(TkOcV_D`6FM{% zN6<1Eq2TLk`Df<#@h?>4$M`AOFB`@KSt$rMqiQ*=_XT>noXL_#_xXx_ICtaL-!up_ zPHM+6{~^Q4sV?`ebiX}f4l~KOeZ>5peE|4k9DK)7INRpLUIZ+&2~@OUl1EgP6Y~n) z9;E?U!n$Mg*tzcoT23LQc{&r61JWaPu6KZ@*D_<@5()v`$rsgj5i8N7?0%`gxL4Mg zmhJAQIR&@k_YDzMdbmXxmkwAC1wR7WM3Bh4;iMS{FNpaEGt4y*% zb@1VMyMBx%Dw`{JByKx0#$HV`bjfgN%TL4W(cUr2M~$FmR-<xnFROE?0f(>41;AiZ9De>7lc1zJ8T}*3(xGR5MzrWeW+{7GQL}To07OYz{|g zVGOH;`$^g|z3n9a#5=)&Z}-#q$=VHj1UxAZV=zKC?)i9i|NHZ?1yxt5kX*(yy8r;(DotWnYLo6}RNr64bFLi9eIKs& z1usyX(_DcvWzt2I(x|}AyB%7F$xK8;bW^?S`p4L1i`PY)06JuqgbvnNqm<#lR^uDY zyQE>7P9D9AbZYgRwQ$TDrP=T3ZsYmppwZChFpsxjwCSwQ^R$_jtOqYu$}nlhkU~4n zv@vU+s+mV{+CS!)QsVvl-$-ESGOdzF`JV;a(o}L5+r|}E`u`SUtkBLm|0NYBx_3T? z-GZ*R0*9`a=xYA@M1yG&uC3&{KK0*-Sskp*LJ&Ck?z=UWEB5}mBQD?Pi?5sGyF5KC z{AUbmMvsMn`N8N^!ud|7o^O24*y`aSI4JMIvIp)k(9O4&Vnn@bINEfUUY`?vF9%NC zs$R7%lseS_0vKH#gV-tSVjyC^l4G3^Tqis7VBR)n3vDd8D`olR&OkHjuSUiA$u09+?GZ5fw%vZ11UdZRi?ow}M?;6x{VS0AlAOvOdCyVC>xM=k{Pk^bjBN>lz6V2#g-Re@y^_0S0 zVe>wSOK2|s?vSCrm?&2%n3P?q3yl?WOez{Ph#_gh=&@FZXkyedh6d6~Jtr=SQr9!s z`$iLmN7>47<`T_C?(_lbS;sNwTe|3!@HiZo!m0gvYlOm07_DnPpR3R?Yz=UdK2lIA z`|BeA1{Ch%n)6*Gd!yz%gg3()AZ$z>Og@V7$&_dy%H13Wt_?J zk&ha>bV%to@7CXbR>uR}&78Wxn}!6r=DY{_ zH-%nyL1Ej5LM&ymWZ z_TE-+O*O??Rthi|15Upvb>mzsM`JwDWe{fnW~7FFeO&IQ563c2XHZUv0cUOhu0x1% z{iH2v)Cc+Vo+P7IkD|^Q%Wa{1jwl}wD7x$UNSQx3{j%6PY32Ak6wSUe#;+>w$)H?O28Y_GF3_XG6aSabHEYh zt3VD*`Q=rJwgb7JC{X#0(Kc30bRRx|9D zv0O?orI-G=%VclKm9f0Q&^=B?v;rCY;k#(U?vKZwXc`(G=zGe&2EYw=YV40QhT+uE zygqDe2?oRSz{vLbfkhD)){3zf4mhn2T2hmnZE9^oVdAtc{3*h?)Su{N(_w2#EL0DR z;%E$xi!ERCni85{*OM^x*Tx4UA}?TeorZ!YMe&l4(8bbUo;_S2L* zQ!S@G+qs-nrmbOovQ#VF10%~)XUG&VYSlD4IK$%mtga({fT zx-Vry-|%TQXg(2=Cv<{{Cbh8A>z(vOGgQu;IPaDYqey=1`1Xqpls#x%N#Y^43ZjS{On#HiyKkzoqQO3xh{ zcb{U6U_%*Z^#S2lur8;dC^TK`{&+HwkwYZ;%bb$nS|WEvyCGzsn?Qm-Evo9Z37n5= zJWBcFy=_?$=7S@nj#)5g+4jmoZc$7M;bP z?bv=E617p)Y09O4wy!nzHHG8Zrj(26UnACC9HqT&1N~YIo?fXDUeU49qqdBvX^E{u z(hWodZrEUd?zMT#(CV|7I4a@ciLP?^k!W3s#ANL)DcE$o+Yu&9YJj2H{Q7_s(gRLX zN~UM^u&Usf8y;B&60>6A5*SxzFLv@5awQnIo8C?$@C>$T<(l|CkOLK2{m?*p_~PSQ zMd6o9fsZTwtyH5De?rj2d15ZrqqJZ+J7y3FZ4`*{TitsqO?Hg4{z-Z>-@U@!Rv_b5 z;+on(RcDRUkte`%8}l9Cin!7v{}juiw>V~z_=1{@kN1%stH%RKR(HesqF zT91v=UhqBKAQ{%zt5)0KT!>>I^Wc<%O28H_q@s!z8aqTkP$^bbE-q{S>;~p>?|rR_ zUh(5kwsXA5iHf*TZVZq!rZSZD^-rvaM3cUyE=a2_nB@Dyy5^;+tIGo&i0U}N6)v~ zK%m3k)(Dq)CGMI={!_-x@HBcvV{X+#MAvpkfKVHQHH ztkVGumltB2rGhegwYBmaq1T1k8n@cCaL@AL;YBq>6z^F=IhH9C6pTvC1mkt=JO;n= zNO^vp2~*!%c_+p*YL5bEDXfhEO5TMOlh|p4-Zf_q^<>_MSFqmQdD5f)oPo3^uk~B+ zuEhtB^(`sjot*Nk9HiKNhh)o$_~Nh%f(4hdC}2a;z>983V>3=V)(mlBzas?~jnik-=1CZ66zHBQeKv z-^by@mOciy)Q>IutAHmMs?W05>O^c9rj#eWnDzK^MRVO}*7Qd5*}z=Ve|qtv+>!sub0u^jho=tK?X1Ah zR|ob-w-*t4?O$jod!K=Yw%TgkABwtsofZM7YW1(xI_97{a_rOH|82w8$b*m%`-ctzeRXaRCjTb5E z?tA~?|IySgww+lVzW%oz_BrSOtN&lu!uvzrDSB!7@X_|;>)%I;_YY5@#`N#IyB`x7 zT+aY6^w1~Fne@lSM$2*hspK(lVk_!Hxv|Q;;5-i%-eEu>=HDdQ#HMD=SW-1YWPn;8!qT#a zR9PYPk*%9%ybu*>hc=};V4G$Z^KXd&T|FvL?CIbAk>BS^nKnSesHt^tB?b=oOiaL- z=EYWYO-0#!AO}$rZ#;SqX7-81=hrH3A4UJKpX`K5b`6#8#<}4q(RDd$D6NY&;`6Ob z-0%@gd~dQYk?iid$-C}vjr6(v43@0lllup?m*|$Jk!YNvZgWPD@L^GEPRfNOk}yb~ zDWS*Gsf&RP?K=oDNqvmWzexs&^=rQF`IbXxD)v0kp`dT&V*H@z*d+DE@rK8~1q)=g zr{$?<)(bBh6Lu3f5`<0q3%_Ure}xgP7CIJoWi%TBbI`xZo;jq(xmETGVV^iT#|TTuU_)$! z;5r->iFatZB)^?}>DDh(wFy%td`P!jrG9CdwZvCsXA=^)IU?oGpvjez?Orp_RasOw zXByAESq?v^mvAKtYuesC@TCAmp^kA(%H*01hpMym#)*Zq0&Mp7*wCB-u zXmOG+HP}B4`9zD{%?KTpovEOMUjxD(V%m;4z@`j|%k2C(4ij>A^7$)q+MeEpcomWt zTkr2zf^nBm8U*jSGo}3X&_)Z;0A-jj&R!O{cTjQVpQ5{r+T{A%&FtAFIo&b)4j_8{ z;D9B8{%bx)hJu(%^u2n-8;3z_h(F_>4qLyH@Wy&LpwZb`Gi7bHJABDTrCRnLwYf3Q znZ+N;RpsBn-nhzVRB^}Fd(3mo5M5Qw+UGRt)w<9yG}DME&bcG6t=9Qow)Wx<*tyzO zfbCGt5i^Oq3G7c5sD=G#2mME0=!sQRcMDfHKI2E0VfyIKrt%J!bIRrQ8>$fe4~zgh zM8QkV-a(v=!pa`Jv+B4LFs(#qWVD~rK&vj843J?;W}pJP&!uC5l%&1wnLoexWMGsA zePzVFYXCQ$`$4rax*8F_9SlSq7nEV>vN^7$)g$#@#~piMdyvelyXvE2twsa`I0jqU zFxcgiUk5rztTKbC_w}kl?yBY+QTOWf@FPb}VU}YFG&Yv4uT+r9mYAEDn0iJ!6zaybNhma!BMw9}N zg49kL#A92<8PRB4XIZNV>mw1X21#ag#{&@}pcYlD*}vS>@vf4%EJri0ogFK~9uV|; zqcVESGVpqoKUL-d*Seb?*4VI02UZjy;lsT9BFn81L+7ttzV!2NF2WOqDJ=8T#+nQN zQBW12pJ7+65-ehLS4Dfv&}p6v-(e~!@2_(PI8|33aDdBEXmDNCuRid6nqY?C{OE}s zcnuLr09=#5KSs9^)_rh*u3!9@=5(Kb`qnFSn1;2Bd)-~;wGZ~Qw|T4J#i~rZY>BHf zjzyRO;T!L>kUsMATS^(vCF0o8_c=8KfSiGOhY1eJu>!q+xDAk+l zVJ(GnJPR>%AQ)jFmt07e6(ZoY5x5l) z0`wFHEuf(=9juX8yvjh&&mnT~&>Qi_7dqIVuh6#roviPsoaRN(>A=*D|4FQSTa+5L z6ky!V4^YyQU;g)qk4Pw|IWNZHS+G`LRsgy`dkZ?^ad~$hKbm)#Yw)&!w2XVGy73yK zgc80m#o&-(&c_=P`KJs~r_l3R4@O*)V$qiX#R{dtF0bd-PWBb$^V<c}2ya zRschNEH*AUYpTee(r5@2=7bO%Xb?J4AY;Kft+>E-BnsJR@Y=M;Qght{vjr5An;3nI zCi6HQbm2zQe%&WtQ)0LrpfNi`zX=c}Mg?#|<_{Zmum$)`pv_%(NDDu|Orz>;-dcd< z{1Y`tnqd!GF;*egjU}q=Dn};&uf$_2JRw#wv|yHHhw};mvX`kN z0zF{jfNgzqg`gXrJTI?_BRbPUTb$@#Z&DCFafmA9>SosVvJI|I0R=!tXg-g>7T@$V{v(0G;#o+ zn&$7UNDuINg_93|WAth*68F5^=G#KdF9`Ub@bVH0`w%Xq;)>?M`GfB?Nf$XMgpd#6 zy!fI%n+?juqNgrUn2)$Na=xL~EFO<0gaY?OyQ5hRZ~4!&bHJw z%#RvATMSt ztzn`V#Q!ts+#Qm|I_4dlxNUu2JF4PORnu7|TyS4~SC-d!g_1Q3 F{y%9F+2Q~I literal 0 HcmV?d00001 diff --git a/doc/wrapping_js_components.md b/doc/wrapping_js_components.md new file mode 100644 index 000000000..af963118c --- /dev/null +++ b/doc/wrapping_js_components.md @@ -0,0 +1,702 @@ +# Wrapping JS Components + +This guide will walk you through the process of wrapping a JavaScript React component in Dart. + +- **[Intro](#intro)** +- **[Theoretical Explanation](#theoretical-explanation)** + - [Dart Level](#level-3-the-dart-level) + - [Interop Level](#level-2-interop-level) + - [JS Level](#level-1-javascript-land) + - [Conclusion](#theoretical-conclusion) +- **[Implementation](#implementation)** + - [Pre-Requisites](#pre-requisites) + - [Adding the JS Variable](#adding-the-js-variable) + - [Adding the Directives](#adding-the-directives) + - [Creating the Getter](#creating-the-getter) + - [Creating the Annotation](#creating-the-annotation) + - [Adding Prop Typings](#adding-prop-typings) + - [Props Conversion Example](#props-conversion-example) + - [Props Conversion Tables](#props-conversion-tables) + - [Standard Types](#standard-types) + - [Exotic Types](#exotic-types) + - [Supplemental Explanations](#supplemental-explanations) + - [Conversion through Getters and Setters](#conversion-through-getters-and-setters) + - [Converting JS Objects](#converting-javascript-object-types) + - [Converting Refs](#converting-refs) + - [Converting Conflicting Function Props](#converting-conflicting-function-props) + - [Using uiJsComponent](#using-uijscomponent) + - [Testing Your Dart Component](#testing-your-dart-component) + - [Test to Verify Component Mount](#test-to-verify-component-mount) + - [Testing Function Props](#testing-function-props) +- **[Conclusion](#conclusion)** + +## Intro + +The JavaScript community is full of great open source libraries that export React components. These JavaScript components can be consumed in Dart by using JS interop to wrap the component and expose a Dart API for it. This process ultimately looks like writing a new Dart OverReact component, with the exception that the Dart component is linked to a JS implementation instead of a Dart one. + +You must wrap the JS component in such a way that it follows OverReact's pattern of using `UiFactory` to create a props instance capable of building a `ReactElement`. This is possible with `uiJsComponent`, which takes in a `ReactJsComponentFactoryProxy` that points to a raw JS component included on the `window`. + +This guide as a whole walks through how to do that linking process and helps make developers aware of the pitfalls that can cause trouble. This section in particular includes a high level technical description of that whole process. Future sections dive deeper into each step to explain why steps happen as they do or explain things to watch out for. + +```dart +@JS() /* [1] */ +library some_library.path.to.component; + +import 'package:js/js.dart'; +import 'package:over_react/over_react.dart'; + +part 'arbitrary_component.over_react.g.dart'; /* [2] */ + +/* [3] */ UiFactory ArbitraryComponent = uiJsComponent( + ReactJsComponentFactoryProxy(_jsArbitraryComponent), /* [4] */ + _$ArbitraryComponentConfig, // ignore: undefined_identifier +); + +@Props(keyNamespace: '') /* [5] */ +mixin ArbitraryComponentProps on UiProps { + /* [6] */ + Map get aMapProp => unjsifyMapProp(_aMapProp$rawJs); + + set aMapProp(Map value) => _aMapProp$rawJs = jsifyMapProp(value); + + @Accessor(key: 'aMapProp') + JsMap _aMapProp$rawJs; +} + +@JS('MagicalJsPackage.ArbitraryComponent') /* [7] */ +external ReactClass get _jsArbitraryComponent; /* [8] */ +``` + +Each notable section of code is briefly explained below. These points are ordered from top to bottom and are not necessarily in the order it makes sense to write the actual code. Each of these sections correlates to a more thorough section later in the guide. + +1. Add the `JS()` annotation and a library directive. Because this is a file implementing JS interop, it is necessary to use the `JS()` annotation. When using a `JS()` annotation, it is also necessary to have a library directive. The actual library name is not important as far as the wrapping and interop go, but it should be unique to the file. +1. Add the `part` directive. Just like any other OverReact component, the file needs to have a `part` that matches the file's name. Nothing special needs to be done here as far as wrapping the JS component goes. +1. Invoke `uiJsComponent`. Note that the left hand side of the declaration looks exactly like a standard, all-Dart functional component declaration. However, on the right hand side, instead of using `uiFunction`, the API being used is `uiJsComponent`. +1. Wrap the JS interop variable with `ReactJsComponentFactoryProxy`. The JS variable (`_jsArbitraryComponent` in this case) is defined below. `ReactJsComponentFactoryProxy` is a wrapper that understands how to create a `ReactElement` (in Dart) from the JS component. +1. Set the prop mixin's namespace to be empty. OverReact namespaces prop keys by default, so we want to turn that off. For example, we want the prop `aProp` to use the map key `'aProp'` instead of `'ArbitraryComponentProps.aProp'`. +1. Add types for the props. In the cases where types map one-to-one from JS to Dart, this is really straightforward. There are more challenging cases to watch out for, though. For more information, see the [Adding Prop Typings](#adding-prop-typings) section. +1. Annotate the JS variable. This annotation should match the global JS variable that exposes the component to be wrapped. + + For example, in this case, the component comes from a library called `MagicalJsPackage`. The component's name is `ArbitraryComponent`, and in the JS world, could be used in JSX like: + + ```jsx + + ``` + + Together, that means that the reference needs to be `MagicalJsPackage.ArbitraryComponent`. If this is not done correctly, a runtime error will fire. Depending on the JS package setup, this could be one of the more complicated steps. For more information, see the [Adding a JS Variable](#adding-the-js-variable) section. + +1. Create the JS variable. This can be named anything, but our convention is to use `_js{ComponentName}`. + +> **REMINDER** that these steps are just a _high level_ view of the process. Even if it feels like everything makes perfect sense, it is **highly** recommended to read the entirety of this guide to avoid insidious pitfalls and runtime errors. + +## Theoretical Explanation + +As a whole, this guide gives tangible steps to wrap a JS component. Those steps are important to make implementation clear, but they are the enactment of an underlying theory that deserves its own explanation. + +Wrapping your JS component involves several levels of code working together. These are the same levels that OverReact uses to allow developers to use React in Dart. Because wrapping a JS component involves interaction with the lower levels, it's helpful to have a mental model for what those levels are and what they're doing. + +### Level 3: the Dart level + +We'll start at the top where you may be familiar with most of the concepts. The Dart level is all about using `OverReact`. OverReact is powered by the concept of a "props" instance. Props in OverReact, which are backed by the `UiProps` class, are ultimately the API used to create UI. To create new instances of your props class, a `UiFactory` is invoked. For example, take the component created in the [Intro](#intro). Invoking the `UiFactory` would look like: + +```dart +final propsInstance = ArbitraryComponent(); +``` + +Now `propsInstance` can be used to customize the element's props or to _build_ the `ReactElement`. To "build" the element is to create the `ReactElement` for the specific props instance. React can use that `ReactElement` to later render the component. Building is done by invoking the props instance. + +```dart +final aReactElement = propsInstance(); +``` + +Usually, invoking the `UiFactory` and building the `ReactElement` are done in immediate succession. + +```dart +return ( + ArbitraryComponent()() +); +``` + +This is important context because whether the component backing the `ReactElement` comes from Dart or JavaScript, to use it with OverReact it must have a `UiFactory` that can return a props instance capable of building a `ReactElement`. The API that enables this for JS components is `uiJsComponent`. `uiJsComponent` can take in a JavaScript component and give back a `UiFactory`. + +### Level 2: Interop Level + +[react-dart][react-dart] is responsible for the interop level, which connects Dart with JavaScript Land. Usually it's just an implementation detail of OverReact, but it comes to the surface in this process because the `uiJsComponent` relies on a react-dart API. + +When `uiJsComponent` takes in the JS component, the JS component must be wrapped in a `ReactJsComponentFactoryProxy`. + +```dart +UiFactory ArbitraryComponent = uiJsComponent( + ReactJsComponentFactoryProxy( /* JS Component*/ _jsArbitraryComponent), + _$ArbitraryComponentConfig, +); +``` + +react-dart has multiple proxies that extend from a class called [ReactComponentFactoryProxy](https://github.com/Workiva/react-dart/blob/bcc05cd110e7a72b0161558581cd4d07c1c68d89/lib/react.dart#L1311). The proxy relevant here, `ReactJsComponentFactoryProxy`, exists specifically for interacting with JS components. + +A proxy's job is to know how to convert Dart props to JS and create a JS `ReactElement`. Recall that for OverReact to work, a `UiFactory` must create a props instance that can _build a `ReactElement`_. This proxy is the piece that enables the _building of a `ReactElement`_. + +Usually OverReact uses APIs that obfuscate the need to create a factory proxy, but in this process it is a manual step. However, all it means is that when you invoke `uiJsComponent`, you'll pass in a `ReactJsComponentFactoryProxy` that wraps a raw JS component. + +### Level 1: JavaScript Land + +In order to have `ReactJsComponentFactoryProxy` wrap a JS component though, you need to be able to access your JS component in Dart. You will do this by using JS interop APIs to create a Dart getter that references the JS implementation. + +```dart +@JS('MagicalJsPackage.ArbitraryComponent') +external ReactClass get _jsArbitraryComponent; +``` + +That getter can then be used whenever it's necessary to reference the raw JS component, such as when instantiating `ReactJsComponentFactoryProxy`. + +In order for that getter to be valid though, it must be pointing to tangible JavaScript somewhere. The interop APIs look for that JavaScript as a property of the `window`. Your JavaScript component can be included in several ways, but it must happen before the `UiFactory` attempts to reference the interop getter. Otherwise, a runtime error will fire. + +One way to load the JavaScript is to use a bundle, like how [React is loaded in Dart][react-dart]. If you are wrapping a JS library without any customizations, it's likely the bundle already exists as a [UMD module](https://jameshfisher.com/2020/10/04/what-are-umd-modules/) or similar format. If you need to write your own JS or make customizations, it's necessary to create and load a custom build. If you have any questions around this, let us know in [Slack][slack]! + +### Theoretical Conclusion + +This section gave a high level view of how wrapping a JS component works in theory. You must wrap the JS component in such a way that it follows OverReact's pattern of using `UiFactory` to create a props instance capable of building a `ReactElement`. This is possible with `uiJsComponent`, which takes in a `ReactJsComponentFactoryProxy` that points to a raw JS component included on the `window`. + +The rest of this guide will reference these same concepts as it implements the code step-by-step. + +### Implementation + +With the theory in mind, this section implements the necessary pieces step by step. + +### Pre-Requisites + +Before jumping into wrapping your component, here is a list of things to do to get set up: + +- Make sure your package depends on `over_react: ^4.4.0`. +- Have a JS bundle and load the JS script in any applications that will use the component (in an `example` directory, test template, `app` directory, etc). **NOTE** that if your package is consumed by a different one, it is important to ensure that the consuming package(s) know to load the script in their applications too. +- Create a file for your component. The file name does not have to be unique for this process and can be consistent with existing naming patterns in your package. + +### Adding the JS Variable + +The first step is to create the interop getter to make the JS implementation accessible in Dart. The code written for this section is shown below. + +```dart +@JS() +library some_library.path.to.component; + +import 'package:js/js.dart'; + +@JS('MagicalJsPackage.ArbitraryComponent') +external ReactClass get _jsArbitraryComponent; +``` + +There are three pieces here: + +1. Adding directives +1. Creating the getter +1. Attaching the annotation + +#### Adding the Directives + +To get started, add the important directives to the top of the file. + +```dart +@JS() +library some_library.path.to.component; + +import 'package:js/js.dart'; +``` + +The `js.dart` import gives access to the `JS()` annotation. The `JS()` annotation is necessary because the file is defining interop APIs. Lastly, the `library` directive is necessary because the `JS()` annotation must be attached to a specific library. + +#### Creating the Getter + +Creating the getter is very simple. + +```dart +external ReactClass get _jsArbitraryComponent; +``` + +The code here can basically be copied and pasted from the code found in this doc, just changing the name of the getter to be something more specific than `_jsArbitraryComponent`. However, the name can really be anything. We have the convention of using `_js{ComponentName}` because: + +- It's private, which is preferred because the use case for the JS variable itself is limited +- It makes it clear it's the _JS_ version of the component, while also including the component's name and thereby coupling it with the Dart API + +This name of this getter doesn't need to match the name of the JS component because the actual link between Dart and JS is created [with the annotation](#creating-the-annotation). + +#### Creating the Annotation + +The annotation binds the getter to a global JS property (a property on the `window`) with the specified name. For example, a getter annotated with: + +```dart +@JS('ArbitraryComponent') +``` + +will return the result of the JS expression: `window.ArbitraryComponent`. + +However, we **strongly** recommend namespacing your components by nesting them within another object, as opposed to adding them directly to the window, to avoid potential collisions with JS APIs added by other packages. Consequently, if the namespace is `MagicalJsPackage`, your annotation would look like: + +```dart +@JS('MagicalJsPackage.ArbitraryComponent') +``` + +and would correspond to `window.MagicalJsPackage.ArbitraryComponent`. + +> Reminder: The JavaScript bundle for the JS must be loaded into the application, [potentially similar to how React is added][react-script], or else this interop step will always fail at runtime. + +To check that the name is correct, you can use the browser devtools to attempt to access the property on the `window`. If the property is valid and generally where you expect it to be then it's likely correct. + +Accessing the property in the devtools + +If the annotation does not have the correct name, rendering the component will fail with a runtime error that looks like: + +Accessing the property in the devtools + +> **One more reminder** that if after correctly creating the annotation, you find that it does not have a library prefix and only requires the component name (i.e. `JS('ArbitraryComponent')` and not something like `JS('MagicalJsPackage.ArbitraryComponent')`), you should **heavily** consider configuring your bundle to have at least a package namespace (i.e. the 'MagicalJsPackage' prefix). Loading components to the global namespace both pollutes that namespace as well as risks naming collisions. + +### Adding Prop Typings + +Next, you'll want to create the props mixin for your component. A props mixin declares props, types them, and in more advanced cases converts their values. In simple cases, you're just adding a property to the props mixin that lines up with a prop on the JS side, and [react-dart][react-dart] will handle converting that data from Dart to JavaScript. Meanwhile, other cases require manual type conversions to form the data in a way that JS can understand. + +This section will give a basic example of what writing this mixin looks like and then give additional examples of more complex type conversions. + +#### Props Conversion Example + +Start by adding your component imports to your file, if you haven't already. + +```dart +import 'package:over_react/over_react.dart'; + +part 'arbitrary_component.over_react.g.dart'; +``` + +These are just the standard OverReact component directives. Altogether, the imports section of your file should look something like: + +```dart +@JS() +library some_library.path.to.component; + +import 'package:js/js.dart'; +import 'package:over_react/over_react.dart'; + +part 'arbitrary_component.over_react.g.dart'; +``` + +After that, move on to converting the TS props interface to a Dart mixin. Say you are given a props interface (or type) that looks like: + +```ts +interface ArbitraryComponentProps { + anObjProp: Record; + content: string; + onClick: (e: React.MouseEvent, id: string) => void; + onBlur: (e: React.FocusEvent) => void; // the same type as React.EventHandler + size: 2 | 4 | 6 | 8; +} +``` + +In Dart, that would look like: + +```dart +@Props(keyNamespace: '') +mixin ArbitraryComponentProps on UiProps { + // START MAP PROP + Map get anObjProp => unjsifyMapProp(_anObjProp$rawJs); + + set anObjProp(Map value) => _anObjProp$rawJs = jsifyMapProp(value); + + @Accessor(key: 'anObjProp') + JsMap _anObjProp$rawJs; + // END MAP PROP + + String content; + + // START ONCLICK + @Accessor(key: 'onClick') + void Function(SyntheticMouseEvent event, String id) onClick_ArbitraryComponent; + + @Deprecated('Use onClick_ArbitraryComponent for proper typing') + @override + get onClick; + + @Deprecated('Use onClick_ArbitraryComponent for proper typing') + @override + set onClick(value); + // END ONCLICK + + void Function(SyntheticFocusEvent event) onBlur; + + num size; +} +``` + +Note that some props converted to Dart nicely (`content`, `size`), while others required more intricate conversions (`ref`, `onClick`). These intricate conversions are discussed more in upcoming sections. + +#### Props Conversion Tables + +These are tables that help illustrate the typing of a prop in TypeScript and Dart. If the library you're wrapping does not have explicit types in TypeScript for the components, it may have `PropTypes` that are fairly similar. If no types are available, erring on the safe side is best as Dart can throw runtime errors if the type is incorrect. Some conversions are explained further in the [Supplemental Explanations section](#supplemental-explanations). + +> **NOTE** that these tables are not exhaustive. They cover the major types that are likely to come up but don't include every type and variation that TS allows. + +##### Standard Types + +| TypeScript | Dart | Notes | +| ---------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `number` | `num` | | +| `string` | `String` | | +| `boolean` | `bool` | | +| `Array` or `T[]` | `List` | For example, `Array` and `string[]` would both translate to `List`. | +| `unknown` | `dynamic` | | +| `any` | `dynamic` | | +| `null` | `null` | | +| `undefined` | `null` | | +| `void` | `void` | | +| any union of different types | `dynamic` | The exception is if the union is something like `null \| boolean`. TS is non-nullable by default, so they must specify `null`, whereas interop will work fine just typing it as `boolean`. | +| union of `T` literals | T | For example, a union like `'foo' \| 'bar' \| 'baz'` is a union of all strings. In that case, the prop type would be `String`. | +| `(foo: string) => boolean` | `bool Function(String foo)` | | +| `React.ReactElement` | `ReactElement` | OverReact exports the `ReactElement` class. | +| `React.ReactNode` | `dynamic` | `React.ReactNode` is technically a union type. | +| `React.{T}EventHandler` | `void Function(Synthetic{T}Event event)` | Where `T` can represent a specific event. For example, `React.FocusEventHandler` -> `void Function(SyntheticFocusEvent event)`. | +| `React.{T}Event` | `Synthetic{T}Event` | Where `T` can represent a specific event. For example, `React.MouseEvent` -> `SyntheticMouseEvent`. | +| `{T}Event` | `{T}Event` | Native web events (i.e. not React's `SyntheticEvent`) map directly to events from the `dart:html` library. | + +##### Exotic Types + +Exotic types are those that may need more care to convert. Watch carefully for these types of props as the translation to Dart may seem intuitive when it actually requires special care. These cases are discussed further in the [Supplemental Explanations section](#supplemental-explanations). + +| TypeScript | TypeScript Example | Dart | Dart Example | +| -------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | +| Object types (`interface`, anon object types, index signature types) | [MUI Autocomplete's ChipProps][example_ts_interface], [MUI Menu's anchorPosition][example_ts_knowntypes] | See examples and the [Converting Objects section](#converting-javascript-object-types) | [RMUI Autocomplete's ChipProps][example_dart_interface], [RMUI Menu's anchorPosition][example_dart_knowntypes] | +| refs (`React.ForwardedRef`, `React.RefObject`, `React.Ref`) | [MUI TextField inputRef][example_ts_ref] | See example and the [Converting Refs section](#converting-refs) | [RMUI TextField inputRef][example_dart_ref] | + +##### Supplemental Explanations + +Some of the conversations above require additional explanation to clarify why the conversion looks the way it does. + +###### Conversion through Getters and Setters + +A solution you will see in several places, with slight differences, is the use of getters and setters on a props mixin. For example, you will see patterns like this: + +```dart +@Props(keyNamespace: '') +mixin ArbitraryComponentProps on UiProps { + Map get aMapProp => unjsifyMapProp(_aMapProp$rawJs); + + set aMapProp(Map value) => _aMapProp$rawJs = jsifyMapProp(value); + + @Accessor(key: 'aMapProp') + JsMap _aMapProp$rawJs; +} +``` + +Each piece of this code block is described further in upcoming sections, but from a high level, patterns like this are necessary to allow the component to receive raw JS data from the JS library. When rendering a JS and Dart components together, it's possible that the JS side will attempt to pass props into the component. This situation is complex and [a full explanation exists](https://sandbox.wdesk.com/a/QWNjb3VudB8yMDAx/doc/1ce2dddb987b4b25a2e0f3a7804fc4af/r/-1/v/1/sec/1ce2dddb987b4b25a2e0f3a7804fc4af_1165). + +The effect is that types which cannot be safely converted from JS to Dart should use a JS type (like `JsMap`) as the Dart type. Otherwise, it risks unexpected runtime errors due to invalid typing. To make it possible to work with these types, getters and setters are used to manually handle translation of the JS typed prop when necessary. + +OverReact exports [several utilities](https://github.com/Workiva/over_react/blob/master/lib/src/util/prop_conversion.dart) to help with this. That being said, due to the complexity of the issue, there are still edge cases that are not handled perfectly. To understand the behavior of the utilities better, you can read the [test cases](https://github.com/Workiva/over_react/blob/master/test/over_react/util/prop_conversion_test.dart) that exercise both basic and edge cases. Using these utilities in practice is covered in upcoming sections. + +###### Converting JavaScript Object Types + +Objects in JavaScript are very common and props frequently have some sort of object type. In TypeScript, it is common to see an object defined with an index signature or with specific properties. These should be handled in different ways. + +**Specific Properties** + +A TS object type can enumerate specific properties in different ways. + +```ts +interface ArbitraryComponentProps { + aDifferenceInterface: AnotherInterface; + aType: AnotherType; + aRecord: Record; + anonObjectType: { + propA: string; + propB: string; + }; +} + +interface AnotherInterface { + propA: string; +} + +type AnotherType = { + propA: string; +}; +``` + +When the properties of an object are known, the best conversion is to create a JS interop class. For example, given the TS structure of: + +```ts +interface ArbitraryComponentProps { + anonObjectType: { + propA: string; + propB: string; + }; +} +``` + +We need to create a type for `anonObjectType`. Because all its keys are defined, we can create a JS interop class to represent this prop. That would look like: + +```dart +@Props(keyNamespace: '') +mixin ArbitraryComponentProps on UiProps { + AnonObjectTypeObject /* [6] */ anonObjectType; +} + +@JS() /* [1] */ +@anonymous /* [2] */ +class AnonObjectTypeObject /* [3] */ { + /* [4] */ external factory AnonObjectTypeObject({ + String propA, + String propB, + }); + + /* [5] */ + external String get propA; + external set propA(String value); + + external String get propB; + external set propB(String value); +} +``` + +Breaking this down into its parts: + +1. **Annotate the class with the `JS()` annotation.** This tells Dart that this is an interop class that represents a JS class. +1. **Annotate the class with the `@anonymous` annotation** This means that the class does not have a tangible constructor to use when creating the class. When the TS type being converted is an _object_ and not a formal _class_, this will usually be the case. This pattern tends to be more common in JS than using formal classes. +1. **Name the class anything.** The name is not significant to interop, but we have the convention of naming it very closely to the prop it's representing. +1. **Create a factory for the class.** Because there is no constructor, this is how developers can create instances of the class to pass into the prop. There should be a named parameter for every property on the class. +1. **Add getters and setters.** This allows data to be read and written to the instance. +1. **Use the class as the type for the prop.** Now that the class is defined, the props mixin should use it as the type for the relevant prop. + +Depending on the TS type, there may be variations to the listed steps. If you encounter a type that looks similar to this but slightly different and you have questions regarding conversion, reach out to us in [Slack][slack]! + +**Index Signature** + +An object defined as just an index signature (on a props definition) looks like: + +```ts +interface ArbitraryComponentProps { + anIndexSignatureType: { + [aKey: string]: boolean; + }; +} +``` + +The differentiator between this object type and that of the previous section is that the prop type does not have known keys and values. Anytime this is true, the best conversion is a Dart `Map`. However, `Map`s need special handling. In order to allow the data to be converted to a form JavaScript can understand, interop utilities must be used. Converting any one of those map props looks like: + +```dart +@Props(keyNamespace: '') +mixin ArbitraryComponentProps on UiProps { + Map get aMapProp => unjsifyMapProp(_aMapProp$rawJs); + + set aMapProp(Map value) => _aMapProp$rawJs = jsifyMapProp(value); + + @Accessor(key: 'aMapProp') + JsMap _aMapProp$rawJs; +} +``` + +> **For more information** on why getters and setters are used here, see the [Conversion through Getters and Setters section](#conversion-through-getters-and-setters). + +The steps are: + +1. Create a `JsMap` property that will represent the raw JS data for the component. This is necessary because the raw JS is not in a consumable form and should not be accessed directly. +1. Add an `@Accessor` annotation to the property with a key that matches the prop name. This will link the `JsMap` property to the JS prop. +1. Add `Map` getters and setters named the same as the prop. + - The getter should use `unjsifyMapProp` to convert the JS property to a Dart `Map` + - The setter should use `jsifyMapProp` to convert the Dart `Map` to JS. + +###### Converting Refs + +If you read the "Index Signature" section of [Converting Javascript Object Types](#converting-javascript-object-types), this section will be very similar (just with different interop utilities). Say you have props that look like: + +```ts +interface ArbitraryComponentProps { + aRef: React.Ref; + anotherRef: React.RefObject; + aForwardedRef: React.ForwardedRef; +} +``` + +Refs are special JS object types that need to be converted to and from Dart using interop utilities. Converting any one of those ref types looks like: + +```dart +@Props(keyNamespace: '') +mixin ArbitraryComponentProps on UiProps { + dynamic get someRef => unjsifyRefProp(_someRef$rawJs); + + set someRef(dynamic value) => _someRef$rawJs = jsifyRefProp(value); + + @Accessor(key: 'someRef') + dynamic _someRef$rawJs; +} +``` + +> **For more information** on why getters and setters are used here, see the [Conversion through Getters and Setters section](#conversion-through-getters-and-setters). + +The steps are: + +1. Create a `dynamic` property that will represent the raw JS ref property for the component. This is necessary because the raw JS is not in a consumable form and should not be accessed directly. +1. Add an `@Accessor` annotation to the property with a key that matches the prop name. This will link the Dart ref property to the JS prop. +1. Add ref getters and setters named the same as the prop. + - The getter should use `unjsifyRefProp` to convert the JS property to Dart. + - The setter should use `jsifyRefProp` to convert the Dart map to JS. + +###### Converting Conflicting Function Props + +Some function props may conflict with props that are built into the UiProps base class, which get mixed in from `UbiquitousDomPropsMixin`. This will happen if: + +1. The component specifies a function that is typically reserved for `UbiquitousDomPropsMixin`. These are typically event handlers on HTML elements, such as `onClick`, `onChange`, etc. +1. The prop's function signature is not the same as the version on `UbiquitousDomPropsMixin`. This typically takes the form of adding extra parameters that the standard event handlers do not have. + +This causes problems in Dart because if the type of the function signature does not match what Dart expects, it will throw a runtime error. Here's what that could look like in TS: + +```ts +interface ArbitraryComponentProps { + // note that the `id` parameter isn't usually part of the `onClick` handler signature + onClick: (e: React.MouseEvent, id: string) => void; +} +``` + +In Dart, to work around that, you would do something like: + +```dart +@Props(keyNamespace: '') +mixin ArbitraryComponentProps on UiProps { + @Accessor(key: 'onClick') + void Function(SyntheticMouseEvent event, String id) onClick_ArbitraryComponent; + + @Deprecated('Use onClick_ArbitraryComponent for proper typing') + @override + get onClick; + + @Deprecated('Use onClick_ArbitraryComponent for proper typing') + @override + set onClick(value); +} +``` + +The steps are: + +1. Override the getters and setters for the prop and deprecate them. Overriding is necessary because `UiProps` already contributes the property. + > This isn't necessary to avoid runtime errors +1. Add a new property that has the correct type for the function. We recommend using the original prop name and adding a namespace suffix (i.e. `onClick` becomes `onClick_ArbitraryComponent`). That way, even though the property can't be the same name as the original prop itself (i.e. `onClick`), it allows developers to begin typing the name of the original prop and use autocomplete from there. +1. Add an `Accessor()` annotation with a key matching the name of the original prop. This links the namespaced prop to the JS property it is replacing. + +### Using `uiJsComponent` + +The last step in your component file is to wire everything up using `uiJsComponent`. + +```dart +UiFactory ArbitraryComponent = uiJsComponent( + ReactJsComponentFactoryProxy(_jsArbitraryComponent), + _$ArbitraryComponentConfig, // ignore: undefined_identifier +); +``` + +This is very similar to a functional component declaration. For comparison, functional components look like: + +```dart +UiFactory ArbitraryComponent = uiFunction( + (props) { + return Dom.div()(props.title); + }, + _$ArbitraryComponentConfig, // ignore: undefined_identifier +); +``` + +Areas of similarity between this and a functional component declaration are: + +1. The typing on the left hand side of the declaration is created the same way (`UiFactory`) +1. A generated config (`_$ArbitraryComponentConfig` here) is necessary and ignored by the analyzer + +The differences are: + +1. The function being called is `uiJsComponent` instead of `uiFunction` +1. `ReactJsComponentFactoryProxy(_jsArbitraryComponent)` is passed in instead of a function + +The most notable step is number 2. For more explanation on why that's happening, refer to the [Theoretical Explanation](#theoretical-explanation). Generally though, the syntax is basically boilerplate that can be copy and pasted from component to component, just changing the JS variable to match the new component. + +When implementing this step, you may notice that the constructor for `ReactJsComponentFactoryProxy` has several parameters (besides the JS component getter) that affect prop translation behavior, but we don't recommend setting them, even if it seems like they may fix issues you're encountering. Instead, reach out to us on [Slack][slack]! + +### Testing Your Dart Component + +Now that you have a fully-fledged Dart wrapper for your JS component, you'll want to add some tests! Hopefully the library exporting the JS component has adequate tests for the actual functionality, so the tests to be added on the Dart side are those that exercise the interop. We highly recommend two different tests: + +1. A test to verify the component mounts without errors. +1. Tests for any function props. These are particular error prone and are covered in more detail below. + +More test cases may be necessary, or simply preferred, but those should be considered the baseline to start from. + +#### Test to Verify Component Mount + +This is largely a sanity check to be sure interop is working as expected. It's ultimately verifying that: + +- the JS component is found on the window. See the [Creating the Annotation section](#creating-the-annotation) for more information +- the Dart component can build (i.e. no boilerplate errors) +- any default logic the component runs on mount is compatible with the Dart interop + +This test may look something like: + +```dart +test('ArbitraryComponent renders without error', () { + final errors = []; + rtl.render( + (ErrorBoundary() + ..onComponentDidCatch = (error, _) { + errors.add(error); + } + )( + ArbitraryComponent()(), + ), + wrapper: StrictMode, + ); + expect(errors, isEmpty); +}); +``` + +The breakdown of this test is: + +1. Wrap the component in a basic error boundary that will catch potential errors + - Be sure to use the `StrictMode` component to catch errors that will only show in that context +1. Render the component +1. Verify the list of errors is empty + +#### Testing Function Props + +Function props can be deceptively tricky to wrap correctly. Fundamentally it's important to check that all the parameters can get converted from Dart to JS as expected, but explicitly testing it via UI interactions also ensures that the JS component invokes it as expected. JavaScript allows for a function to be called with any number of arguments, whereas in Dart calling a function with more or fewer arguments than it expects is a runtime error. + +To guard against that, it is recommended to write a test for every function prop that was wrapped. One of these tests may look like (using [React Testing Library](https://github.com/Workiva/react_testing_library)): + +```dart +test('onClick', () { + final events = []; + final view = render( + (Button()..onClick = (e) => events.add(e))(), + wrapper: StrictMode, + ); + UserEvent.click(view.getByRole('button')); + expect(events, [isA()]); +}); +``` + +This test is: + +1. Rendering the component in a strict mode wrapper +1. Clicking the component +1. Checking that the callback is called without error +1. Checking that the arguments to the callback are what we expect + +Most function prop tests can be that simple because typically the most important thing is just that the function fires as expected without dismounting the React tree. + +### Conclusion + +Hopefully wrapping your JS component goes smoothly! However, even simple components can have highly unique situations that cause challenges. If you encounter any of those challenges, please reach out to us in [Slack][slack]! + +[slack]: https://workiva.slack.com/archives/CEFTMBPAA +[example_ts_reactelement]: https://github.com/mui/material-ui/blob/d73fadbb12cc48a981a39d4ccaeacd8021ab9efa/packages/mui-material/src/Chip/Chip.d.ts#L19 +[example_dart_reactelement]: https://github.com/Workiva/react_material_ui/blob/4fc41fe8393b424beac513ec99a98be5f09bd1ab/lib/src/components/custom/wsd/chip/chip.dart#L36 +[example_ts_reactnode]: https://github.com/mui/material-ui/blob/d73fadbb12cc48a981a39d4ccaeacd8021ab9efa/packages/mui-material/src/Chip/Chip.d.ts#L64 +[example_dart_reactnode]: https://github.com/Workiva/react_material_ui/blob/4fc41fe8393b424beac513ec99a98be5f09bd1ab/lib/src/components/custom/wsd/chip/chip.dart#L69 +[example_ts_interface]: https://github.com/mui/material-ui/blob/d73fadbb12cc48a981a39d4ccaeacd8021ab9efa/packages/mui-material/src/Autocomplete/Autocomplete.d.ts#L92 +[example_ts_knowntypes]: https://github.com/mui/material-ui/blob/d73fadbb12cc48a981a39d4ccaeacd8021ab9efa/packages/mui-material/src/Popover/Popover.d.ts#L47-L51 +[example_dart_interface]: https://github.com/Workiva/react_material_ui/blob/4fc41fe8393b424beac513ec99a98be5f09bd1ab/lib/src/components/mui_material/autocomplete/autocomplete.dart#L75-L83 +[example_dart_knowntypes]: https://github.com/Workiva/react_material_ui/blob/4fc41fe8393b424beac513ec99a98be5f09bd1ab/lib/src/components/mui_material/menu/menu.dart#L83 +[example_ts_ref]: https://github.com/mui/material-ui/blob/d73fadbb12cc48a981a39d4ccaeacd8021ab9efa/packages/mui-material/src/TextField/TextField.d.ts#L98 +[example_dart_ref]: https://github.com/Workiva/react_material_ui/blob/4fc41fe8393b424beac513ec99a98be5f09bd1ab/lib/src/components/custom/wsd/text_field/text_field.dart#L175-L187 +[react-dart]: https://github.com/Workiva/react-dart +[react-script]: https://github.com/Workiva/over_react/blob/82cacd3c12ab99b7a6fd9ddb0698a0cc844aa8a8/example/builder/index.html#L36-L37 diff --git a/lib/src/util/js_component.dart b/lib/src/util/js_component.dart index 33622c5a5..6e1d8d953 100644 --- a/lib/src/util/js_component.dart +++ b/lib/src/util/js_component.dart @@ -3,7 +3,7 @@ import 'package:react/react_client/component_factory.dart'; /// Creates a Dart component factory that wraps a ReactJS [factoryProxy]. /// -/// More in-depth documentation for wrapping JS components is coming soon. +/// See the [complete guide to wrapping JS components](https://github.com/Workiva/over_react/blob/master/doc/wrapping_js_components.md) for more information. /// /// Example: /// ```dart