From 36f707261ce06095410597a80b8347caae9748a7 Mon Sep 17 00:00:00 2001 From: luna Date: Sat, 29 Mar 2025 08:12:36 -0500 Subject: [PATCH 1/6] Added numberinput to web and textual backends. --- docs/reference/api/widgets/numberinput.rst | 12 ++- docs/reference/images/numberinput-textual.png | Bin 0 -> 1098 bytes docs/reference/images/numberinput-web.png | Bin 0 -> 1183 bytes textual/src/toga_textual/factory.py | 4 +- .../src/toga_textual/widgets/numberinput.py | 82 ++++++++++++++++++ web/src/toga_web/factory.py | 4 +- web/src/toga_web/widgets/numberinput.py | 40 +++++++++ 7 files changed, 134 insertions(+), 8 deletions(-) create mode 100644 docs/reference/images/numberinput-textual.png create mode 100644 docs/reference/images/numberinput-web.png create mode 100644 textual/src/toga_textual/widgets/numberinput.py create mode 100644 web/src/toga_web/widgets/numberinput.py diff --git a/docs/reference/api/widgets/numberinput.rst b/docs/reference/api/widgets/numberinput.rst index 77b532a693..cd5c17a437 100644 --- a/docs/reference/api/widgets/numberinput.rst +++ b/docs/reference/api/widgets/numberinput.rst @@ -35,13 +35,17 @@ A text input that is limited to numeric input. :align: center :width: 300px - .. group-tab:: Web |no| + .. group-tab:: Web - Not supported + .. figure:: /reference/images/numberinput-web.png + :align: center + :width: 300px - .. group-tab:: Textual |no| + .. group-tab:: Textual - Not supported + .. figure:: /reference/images/numberinput-textual.png + :align: center + :width: 300px Usage ----- diff --git a/docs/reference/images/numberinput-textual.png b/docs/reference/images/numberinput-textual.png new file mode 100644 index 0000000000000000000000000000000000000000..296a64a7eaae6c3c9ba9205a13900a0bd59eeda2 GIT binary patch literal 1098 zcmeAS@N?(olHy`uVBq!ia0y~yV5|YM?Ks$gqz1zRNg%~i?Bp530R%N1DIGw*WQl7; zNpOBzNqJ&XDnoi+er~F6adJ^=YF=?he#v|0{dPb_x*$c)MX8A;nfZANA(^?U48e&d z3PzR+zKKN&Mg~Tv3I-Nd2Bua<#tM$U3eGMeq2jNj85o!sd%8G=RLpsMH#Q?UUFP`5 z_;V)>dCi==*6C`5uxll`vZZ>69_(ydEk0W*Z*|;>JpmIoN+((+`nc+|?7hGl7O-sb zk0yl|trlh*S*>5!WJX@)(sJFhuWao$lVz447p=&a&baa><=OwgF-u=wezNn|JoWRO zwN4u&bW|A}?iCl=GBP+A9Gdr>x6(+c^GSjhL&J8#{^Q0{$GPMgdN}ya74E7tFgUzZ z73wsXIu8<4_`#6Ca*&-tnZu4j!tn#+0gfhq1~-8k27{&t%ngDpWGFFwqxkG*&FV># zSARV_y>r!!m{m@tuj{g#``+JNdf0camuFDLu6=9oE_6NWyx#fW-osI!zxtVeXEje| zIHAtSc0WA+NbvX8sNCOA=T&hZ&;E8vG-~U`i+9(Xty~u!v?;Xx+S+UR%1-P5ziH-L z8?)c%Xs`H{X*_w`7$)R1@%~$EY_QKj^SRfbgx^z%%2pCKfkOV?8+;#){*|jc6@F@ZO!@_rJ0Dlg30#%_{B`M4bMs*4 zh8p2Ja{Gdp_=|s{}hT6c?n{;FLwwWJn^riKg8Gi7?)q)J`5MSDUaN!~$ S?=`@D&fw|l=d#Wzp$P!3SZgXgFbngSdJ^%m!Ep$a#bVG7w zVRUJ4ZXi@?ZDjycVRRreIUr48av(A=GBqGDI65#kIx{dJK}{e-L{wD)p85a)1Nccq zK~#90?VNu|++`fcUtdpd+1+{Vye%a>aVSHL7}9D*I!0r(vK5TlYJ{-ZA0>xB5d+_u6 z^Lf6{mp8olJkRs}!K20F@i@YLPhPGA2&+RxDT0Vn1QDeOB1#cNlp^FWZHPv1bL-YD zp-=?bxn~DW({MO!xZOn*6g-gnn#Ipi(dcc)$N!+Xcq2|{Ar^}iTsAPx1%knObbXrg za!*QG38E$^|H5Xo;c`8c)u6&13}cQ+BtmKFBT0P;B@+roa5@XK7EDOQ=`17^ilpqz z+v_Oc>5py#5Rb=Yy2~LJOWs}g1^KnRpC?eUKFg7WdnNzRtIuBJP53yA6>IZGq{hinx8|&d?pF!oOwJixUA!=rTf$LCz*vp%{?04F# zt6}stjtvemda#Phq^h!0QoV(egf^Y7FNayz(8|8@w5L>#^o!cChZEoLNouxQ0n0k9 zsiwXZg!?|D>-Vj^?cIXZQ%0t~MZ{vnq6VXb-*Bc6wl(dbY&GM|F}gahP_*|nJ6#z~ zs0@v26K2fn7)*vdOEc`_GIF)}D>J=+hTq-=JUscS#* z)#)R&gxff=zhZ@{EWpKcSFqN3sdr{BooZxs>T{J>;VK6ne}+dNY@)HVlZ(4sd0{yL z`Xzqy+j#1LtWAqa|0|@KoAm$qGZRZ5&&n!FvBz}undOHYLzfw~R`XmJeXZ@-eLlK94bxo6RhBi6X)d5?TFSlzQ4WWV zU~oPwp@d|D!Fe1GTgtu!QEqn;x;~9z%t>Ws5z}12Fy_$pY25DAA9%E-r;Ew)1=;7G xk1r7JUu4&(2qH=mM3f?kC`AxaiXfsC;UC75JGi$%-;n?S002ovPDHLkV1n9{B4q#o literal 0 HcmV?d00001 diff --git a/textual/src/toga_textual/factory.py b/textual/src/toga_textual/factory.py index d41f6da7fa..d002d18b39 100644 --- a/textual/src/toga_textual/factory.py +++ b/textual/src/toga_textual/factory.py @@ -23,7 +23,7 @@ from .widgets.label import Label # from .widgets.multilinetextinput import MultilineTextInput -# from .widgets.numberinput import NumberInput +from .widgets.numberinput import NumberInput # from .widgets.optioncontainer import OptionContainer # from .widgets.passwordinput import PasswordInput # from .widgets.progressbar import ProgressBar @@ -69,7 +69,7 @@ def not_implemented(feature): # "ImageView", "Label", # "MultilineTextInput", - # "NumberInput", + "NumberInput", # "OptionContainer", # "PasswordInput", # "ProgressBar", diff --git a/textual/src/toga_textual/widgets/numberinput.py b/textual/src/toga_textual/widgets/numberinput.py new file mode 100644 index 0000000000..5efb340e2b --- /dev/null +++ b/textual/src/toga_textual/widgets/numberinput.py @@ -0,0 +1,82 @@ +from travertino.size import at_least + +from textual.widgets import Input as TextualInput +from textual.validation import Number + +from .base import Widget + + +class TogaInput(TextualInput): + def __init__(self, impl): + super().__init__() + self.interface = impl.interface + self.impl = impl + + def on_input_changed(self, event: TextualInput.Changed) -> None: + self.interface.on_change() + + def on_input_submitted(self, event: TextualInput.Submitted) -> None: + self.interface.on_confirm() + + +class NumberInput(Widget): + def create(self): + self.native = TogaInput(self) + self.native.type = "number" + self.min = None + self.max = None + + def get_readonly(self): + return self.native.disabled + + def set_readonly(self, value): + self.native.disabled = value + + def get_placeholder(self): + return self.native.placeholder + + def set_placeholder(self, value): + self.native.placeholder = value + + def get_value(self): + if self.native.value == '' or self.native.value is None: + return None + else: + if self.native.value != '-': + return float(self.native.value) + else: + return self.native.value + + def set_value(self, value): + try: + if value is None: + self.native.value = "" + else: + self.native.value = str(value) + except AttributeError: + self.native.value = "" + + def set_step(self, step): + pass + + def set_min_value(self, value): + self.min = value + self.native.validators = [Number(minimum=self.min),] + + def set_max_value(self, value): + self.max = value + self.native.validators = [Number(maximum=self.max),] + + + @property + def width_adjustment(self): + return 2 + + @property + def height_adjustment(self): + return 2 + + + def rehint(self): + self.interface.intrinsic.width = at_least(len(self.native.value) + 4) + self.interface.intrinsic.height = 3 \ No newline at end of file diff --git a/web/src/toga_web/factory.py b/web/src/toga_web/factory.py index 3aaa3da4fc..a0a2c7ff74 100644 --- a/web/src/toga_web/factory.py +++ b/web/src/toga_web/factory.py @@ -20,7 +20,7 @@ from .widgets.label import Label # from .widgets.multilinetextinput import MultilineTextInput -# from .widgets.numberinput import NumberInput +from .widgets.numberinput import NumberInput # from .widgets.optioncontainer import OptionContainer from .widgets.passwordinput import PasswordInput from .widgets.progressbar import ProgressBar @@ -66,7 +66,7 @@ def not_implemented(feature): # 'ImageView', "Label", # 'MultilineTextInput', - # 'NumberInput', + 'NumberInput', # 'OptionContainer', "PasswordInput", "ProgressBar", diff --git a/web/src/toga_web/widgets/numberinput.py b/web/src/toga_web/widgets/numberinput.py new file mode 100644 index 0000000000..1ecb01e84e --- /dev/null +++ b/web/src/toga_web/widgets/numberinput.py @@ -0,0 +1,40 @@ +from .base import Widget + +class NumberInput(Widget): + def create(self): + self._return_listener = None + self.native = self._create_native_widget("sl-input") + self.native.type = 'number' + self.native.value = None + self.native.onkeyup = self.dom_keyup + + def dom_keyup(self, event): + if event.key == "Enter": + self.interface.on_confirm() + + def get_readonly(self, value): + return self.native.readOnly + + def set_readonly(self, value): + self.native.readOnly = value + + def set_step(self, step): + self.native.step = step + + def set_min_value(self, value): + self.native.min = value + + def set_max_value(self, value): + self.native.max = value + + def get_value(self): + if self.native.value == '' or self.native.value is None: + return None + else: + return float(self.native.value) + + def set_value(self, value): + self.native.value = value + + def set_text_align(self, value): + pass \ No newline at end of file From 05d1ad6e2ecd7d0e63130eac9dbf042ddbf82af5 Mon Sep 17 00:00:00 2001 From: luna Date: Sat, 29 Mar 2025 08:16:04 -0500 Subject: [PATCH 2/6] Added changes file --- changes/3299.feature.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 changes/3299.feature.rst diff --git a/changes/3299.feature.rst b/changes/3299.feature.rst new file mode 100644 index 0000000000..c5127be9e5 --- /dev/null +++ b/changes/3299.feature.rst @@ -0,0 +1 @@ +Added numberinput to web and textual backends. From 576b0f8639451b176682d35b9b26c61f2b614486 Mon Sep 17 00:00:00 2001 From: luna Date: Sat, 29 Mar 2025 08:20:59 -0500 Subject: [PATCH 3/6] Fixed pre-commit issues --- textual/src/toga_textual/factory.py | 1 + .../src/toga_textual/widgets/numberinput.py | 18 ++++++++++-------- web/src/toga_web/factory.py | 3 ++- web/src/toga_web/widgets/numberinput.py | 7 ++++--- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/textual/src/toga_textual/factory.py b/textual/src/toga_textual/factory.py index d002d18b39..8020cace6b 100644 --- a/textual/src/toga_textual/factory.py +++ b/textual/src/toga_textual/factory.py @@ -24,6 +24,7 @@ # from .widgets.multilinetextinput import MultilineTextInput from .widgets.numberinput import NumberInput + # from .widgets.optioncontainer import OptionContainer # from .widgets.passwordinput import PasswordInput # from .widgets.progressbar import ProgressBar diff --git a/textual/src/toga_textual/widgets/numberinput.py b/textual/src/toga_textual/widgets/numberinput.py index 5efb340e2b..a03c8c572e 100644 --- a/textual/src/toga_textual/widgets/numberinput.py +++ b/textual/src/toga_textual/widgets/numberinput.py @@ -1,7 +1,7 @@ from travertino.size import at_least -from textual.widgets import Input as TextualInput from textual.validation import Number +from textual.widgets import Input as TextualInput from .base import Widget @@ -39,10 +39,10 @@ def set_placeholder(self, value): self.native.placeholder = value def get_value(self): - if self.native.value == '' or self.native.value is None: + if self.native.value == "" or self.native.value is None: return None else: - if self.native.value != '-': + if self.native.value != "-": return float(self.native.value) else: return self.native.value @@ -61,12 +61,15 @@ def set_step(self, step): def set_min_value(self, value): self.min = value - self.native.validators = [Number(minimum=self.min),] + self.native.validators = [ + Number(minimum=self.min), + ] def set_max_value(self, value): self.max = value - self.native.validators = [Number(maximum=self.max),] - + self.native.validators = [ + Number(maximum=self.max), + ] @property def width_adjustment(self): @@ -76,7 +79,6 @@ def width_adjustment(self): def height_adjustment(self): return 2 - def rehint(self): self.interface.intrinsic.width = at_least(len(self.native.value) + 4) - self.interface.intrinsic.height = 3 \ No newline at end of file + self.interface.intrinsic.height = 3 diff --git a/web/src/toga_web/factory.py b/web/src/toga_web/factory.py index a0a2c7ff74..fda40241e8 100644 --- a/web/src/toga_web/factory.py +++ b/web/src/toga_web/factory.py @@ -21,6 +21,7 @@ # from .widgets.multilinetextinput import MultilineTextInput from .widgets.numberinput import NumberInput + # from .widgets.optioncontainer import OptionContainer from .widgets.passwordinput import PasswordInput from .widgets.progressbar import ProgressBar @@ -66,7 +67,7 @@ def not_implemented(feature): # 'ImageView', "Label", # 'MultilineTextInput', - 'NumberInput', + "NumberInput", # 'OptionContainer', "PasswordInput", "ProgressBar", diff --git a/web/src/toga_web/widgets/numberinput.py b/web/src/toga_web/widgets/numberinput.py index 1ecb01e84e..964264137f 100644 --- a/web/src/toga_web/widgets/numberinput.py +++ b/web/src/toga_web/widgets/numberinput.py @@ -1,10 +1,11 @@ from .base import Widget + class NumberInput(Widget): def create(self): self._return_listener = None self.native = self._create_native_widget("sl-input") - self.native.type = 'number' + self.native.type = "number" self.native.value = None self.native.onkeyup = self.dom_keyup @@ -28,7 +29,7 @@ def set_max_value(self, value): self.native.max = value def get_value(self): - if self.native.value == '' or self.native.value is None: + if self.native.value == "" or self.native.value is None: return None else: return float(self.native.value) @@ -37,4 +38,4 @@ def set_value(self, value): self.native.value = value def set_text_align(self, value): - pass \ No newline at end of file + pass From 5be075714b3ef452687138e5a3cf465f1868b36a Mon Sep 17 00:00:00 2001 From: luna Date: Sun, 30 Mar 2025 13:05:33 -0500 Subject: [PATCH 4/6] Changed how numberinput checks for values outside min and max and removed unneeded on_confirm handlers --- docs/reference/images/numberinput-textual.png | Bin 1098 -> 2174 bytes docs/reference/images/numberinput-web.png | Bin 1183 -> 2084 bytes .../src/toga_textual/widgets/numberinput.py | 15 ++++++++++++--- web/src/toga_web/widgets/numberinput.py | 17 +++++++++++++---- 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/docs/reference/images/numberinput-textual.png b/docs/reference/images/numberinput-textual.png index 296a64a7eaae6c3c9ba9205a13900a0bd59eeda2..1c7ff2ec9cb0d3f7de55ffe01febc11866f5dd19 100644 GIT binary patch literal 2174 zcmd6o`!^GeAIG;bW};*(&7RzziZ5x(av4v?uu1MTEm1a7+SD+rnM+9_=1~ZXcC?b_ z7F$9%JxFTqlv~S?gb7h|dG`GuzCV0__ll99jLkmBvFnVIl5HUtkiTzHnjN~T&7MpzUI(5 z0y;h(s>NxZ_uOmVJ(GOW>~Zm*R4CA_KpPT`D1>wIabxh!c# zCtQL&E<7oG*O8O9u=FI~oD4nU?KMoUoyZ~=Sb9dfn6TTp?ia9!Wy6D8lleu4dmT29 z8e4v8&(fs1_GLxB$D2lbaa#K_n7VXYrad>Y{p1u8v&U@WBW3|6e_dXED zd06F|hLxCJI!h4R8`aoPcg5Re!&rI|gDMPUTLx!(*^@ecO(wEs#BmmuuAdnnl{Ky+ zUwQ9Icfzb?fM8M#FsZq#r@D@kstqfOABUnK>(SpnT7pMF+du-a!0@v`E?yU2J?aJ& z0Fs(6H6MhoJ&zw4{y+*0TaFGK@Qw!s9Pl`Ka{97#VD+WGv`G91bd483Xq-6UYF)@* z382aye;IlN1|Ev_JFS7=Ss4%dqKbmNc^Djx2NHlv34dy+8tOIzlI+&4LTEBaPqiRHV_{6hIAKN!3^~0~UbvvDnrS1y3ArM)R zCUc00ob(>0)u7}tGra%m;beG;K6T0L#qUFQw3eYa`|!EzkWRe|eVYMyl3tQ3R`R`t z15=^F_YiH&kda4C4RC3?u17v<<{w2|z)(^_&4Hl)qUIKM z{;@fkY6)bj+x6%!k)!Ij1gCHjKysUpIh+PiHHc}WN4p_D5S3`&KJVLvlJXI{9sp2qD;|~x0UV>n);b`uZd-EU7TKL2{JduW-sb9UqQ)Zr3bwi zBYkY4jJS0Anb6z8jy(+)SHT?TZVq#8qsqWCe6)+z0<%Hz*|AE3j%-9*szX&aNTB!G z0b69>)nr-5Vrb~cbu|6x+<3AqOj|g?K-j>JXiU93_Idcxr!CgX!g(SSHdS5#z7%JD zX1r26R)=!V{~QA9^Edl2%^ZaJ#N)PQBY51(Us_-oOq!?baVy>VTfPcpBTmha3~%`~ zQvyWW8}QDKtrNGY%}JKgyV7FIcJtRyr*JLm2fdW@yDiH0!Y8K-2NjSDyZD<4T;8`G zurW<+A+mOkEsvfP*1H2sz}F>+1ob3Nz*phbz!WvGFJ%5AwxZfw7G>P=j4#}}Z-;@1 zp1U@iXrhcNw$ZDO+aB9ueCHii$C7n0Zu2(kBAvu&_0sJ(-@-r*`$2lvaP#JdpxVAK zA(!@j@EebR{us6|bUD}d*q&bgbn%v=ZVuj?-Co|dd=grqvtgw^w}zK4N=^{r3fBck z_h@hLe?j^^n_TjFNsW6sC>B{o+5)qeI?)Qdf@I2zsB8730&&Z{k4?20LS+)4rlvUA zd+Bo!R4lRRkCUQ@t>xDkx|r!5r3Ey9`Ch=;+AyIih#%;{e>N+nRL-O{YA7;_W7P>j zsJUvp3Mg+On&1M2rkFJ8?ud>4s0}8|0Zg{9Oatl58HwWr{B#25$pM__VZx1XmkV}i zPJ^f2V>vDN!`>O{R>_6rBZUUbG-}e#RQc!d|28VZdEEc&?o!}0Bd68{CuL)eo5 literal 1098 zcmeAS@N?(olHy`uVBq!ia0y~yV5|YM?Ks$gqz1zRNg%~i?Bp530R%N1DIGw*WQl7; zNpOBzNqJ&XDnoi+er~F6adJ^=YF=?he#v|0{dPb_x*$c)MX8A;nfZANA(^?U48e&d z3PzR+zKKN&Mg~Tv3I-Nd2Bua<#tM$U3eGMeq2jNj85o!sd%8G=RLpsMH#Q?UUFP`5 z_;V)>dCi==*6C`5uxll`vZZ>69_(ydEk0W*Z*|;>JpmIoN+((+`nc+|?7hGl7O-sb zk0yl|trlh*S*>5!WJX@)(sJFhuWao$lVz447p=&a&baa><=OwgF-u=wezNn|JoWRO zwN4u&bW|A}?iCl=GBP+A9Gdr>x6(+c^GSjhL&J8#{^Q0{$GPMgdN}ya74E7tFgUzZ z73wsXIu8<4_`#6Ca*&-tnZu4j!tn#+0gfhq1~-8k27{&t%ngDpWGFFwqxkG*&FV># zSARV_y>r!!m{m@tuj{g#``+JNdf0camuFDLu6=9oE_6NWyx#fW-osI!zxtVeXEje| zIHAtSc0WA+NbvX8sNCOA=T&hZ&;E8vG-~U`i+9(Xty~u!v?;Xx+S+UR%1-P5ziH-L z8?)c%Xs`H{X*_w`7$)R1@%~$EY_QKj^SRfbgx^z%%2pCKfkOV?8+;#){*|jc6@F@ZO!@_rJ0Dlg30#%_{B`M4bMs*4 zh8p2Ja{Gdp_=|s{}hT6c?n{;FLwwWJn^riKg8Gi7?)q)J`5MSDUaN!~$ S?=`@D&fw|l=d#Wzp$P!3S+%#Vsn|yb-LUEo zXipJhRuKraC&>wo@=PfH#sys7uBx&ZCRV1KgH?h@uGrucU3QsZ3WX|JQMv1Zje~=R z)DsV$PdT~-8LqJ5M2gSpI)d1PXGkk3IOA!l1qHf$ z>Zs$^RkgMJVjH4b`9&>YVF4G=7hn$nWiSEx8o(RyCYfC>s|WD+}Lwi-fqKc{kS9$|ol$RUYV6-JYJE9U|b+ zvj?hI7#gdO@(=wh9CCL)rJy7QgLc88v=Mj9|Ly?%EJ53*^EMD~pD%Q@VZUyazKOM_ z1Q4^$17+4TN_06&wak*{acDgoUVAXLcr0wl^&klV_j~j21a;aK9^7j^T9Wdn&hD@W z_2#u{dU2HaT7xsF9 z6V~09-2Z&p@x*I(wn0?4o!A&f91slciaJFiyO8P7=q9;NVv4FV61s2q0uxyZJutfS z>%Cd(Obhhb*rkn0V%3pQVfV$-tvp%U-992eIl@0)HUTZh`6>XWPcMS~a{&1p&U zEWre`#!bgF#oytS+4eQM1=gkVb-!zB>tErW_tOEAMaju|ng+;|_W-!(MXl|)Zc8Hs z^6Z!h-#WI2`he5ZmDGEbkdyo1MTVCZ$8m`b=YCc~^P-TOiYH$}&MX`=vK%{?1Xi+| zvA8g)1q-me^4{KU<*P`tSMfg*1*TzOzNN0erSK*)qFM%aYF70kxAgIz3#;RG=r@~q;uV$ zzxKK}Zh5FQC@g(pZeW^3W>RZgBHD}tg`08#`Xbr~r`KfZesW3Caom}=%nW{7uj{W| zqx|Jki0j4v2Y^DFH;HB*69rGqipvr7d-gXT(WCYZj0BfRfbQF3C#>hcmXX1DdtNR!eUn(672Mz5pw}p@ zk(#Gitp{ml>mXKx3g9>~w+RE{2*Ev-_3>c!+$>WGa|2Mz^PT#A|BXkb?~3 zz9hZNAOf8jUtVe6!|Kq?glPT4+0*N%B79X5hysb=M4mlo{cz@Qnhlqs2_mwwtKO&!xw~gy8xwL+1NX|@o5ufqQP3D^(a0I z7~f|56oyJETFnUNzljpGMjzw&tnCb2AR`MDKU0WJA2|&HrWCBYhay*CKOS@Qlj5|i zK$S|J%fzvwu8tQr<_)%XeW)*}shgv%hcaQ0Ve6_Y0j=QgO4eM`F0q*H{UX$mY1g4Z zP-dQ_`^=X=tUFmxR&6)!BIPfZUIg%K$_YSEa$(NhoSTs#)aLoV`A=Z=SRs4qhr`FY zPJVrB&+p>l$oxOPR9DW|YI2(j()C537(qN3bhSWt4SNjfb_Xi^SZ^n!>c&B{#?+3X z&Z_r9rCw|Xc~!^PImMr^V793&oMaDa{2aaK1I%Z5t8%=Zf2Q%nyNNDC3E=dsusnV6(37LKNaPe vt5jWZ#C;-B&tI)T-v delta 1124 zcmV-q1e^P$5T6MliBL{Q4GJ0x0000DNk~Le0001L0000s2nGNE055F}8<8PQe_?bW zGC3ekVR9fcFfuhDFgQ9eHaasfAVEzaLqt?n0iOB*00a0*L_t(|ob8-{NZe%{$6sGh zZrR;=?Yu1|JaH&PjTq8uMLI@fw6Yb9+G>Qb*dHZ~29ZL${V|JX{oxi2p<|H|g@Th+ zW7J}ZhTsO93~sZu#4P-x^Lw6mf5)wZuh$nPyqk8T1GkH=-Y z%OMs^-d*RtIuBJP z53yA6>IZGq{hinx8|&d?pF!oOwJixUA!=rTf$LCz*vp%{?04F#f2(2iHI5AqF?z6y z%A~5YQ&PQ!l7u#$t}lmK*U-wo^0cQ^j`WM#u!j@h?@4O5S^>*Ctf{8H6omUeqwDvr zyzSkB)Kf;LzD2}h#G(eHgWqtb54JV!plmhc%rUw;uTZr2G&@}xO{feR6cwPic|WJF z0R^^2OfWhM;~mWv9ptlyIXl-IRW}5e)8LR>VT|G zi%I`0q?w!a|M)W#OCHb4DoU}(bo80!hZ{qe8MRjPWQ}NAe@q5MU819{oijt>gyyO7 zNy4~rExS30@vDD~n5Txyg0v@7n)Hj>{30(u7Nh^uqjX%jjQ_?k{b$=b);Wf&?m4#q zD{A80ahi^N!BundV1tQ2!&o;yOi|i{sZILVD2s=Et?k%-KDvFabOmErogQi%S~z-e z2mcjRZ0a}se94bxo6RhBi6X)d5?TFSlzQ4WWVU~oPwp@d|D z!Fe1GTgtu!QEqn;x;~9z%t>Ws5z}12Fy_$pY25DAIv;qnrKgL@@derEo{ujO?q6ir qrwAfS5k! None: self.interface.on_change() - def on_input_submitted(self, event: TextualInput.Submitted) -> None: - self.interface.on_confirm() + def on_input_blurred(self, event: TextualInput.Blurred): + if self.impl.get_value() == "-": + self.impl.set_value("0") + + if self.impl.get_value() is not None and self.impl.min is not None: + if float(self.impl.get_value()) < self.impl.min: + self.impl.set_value(str(self.impl.min)) + + if self.impl.get_value() is not None and self.impl.max is not None: + if float(self.impl.get_value()) > self.impl.max: + self.impl.set_value(str(self.impl.max)) class NumberInput(Widget): @@ -80,5 +89,5 @@ def height_adjustment(self): return 2 def rehint(self): - self.interface.intrinsic.width = at_least(len(self.native.value) + 4) + self.interface.intrinsic.width = at_least(10) self.interface.intrinsic.height = 3 diff --git a/web/src/toga_web/widgets/numberinput.py b/web/src/toga_web/widgets/numberinput.py index 964264137f..efb2f00f5a 100644 --- a/web/src/toga_web/widgets/numberinput.py +++ b/web/src/toga_web/widgets/numberinput.py @@ -7,11 +7,20 @@ def create(self): self.native = self._create_native_widget("sl-input") self.native.type = "number" self.native.value = None - self.native.onkeyup = self.dom_keyup + self.native.onblur = self.lost_focus - def dom_keyup(self, event): - if event.key == "Enter": - self.interface.on_confirm() + def lost_focus(self, event): + print(self.native.value == "-") + if self.native.value == "": + self.native.value = None + + if self.native.value is not None and self.native.min is not None: + if float(self.native.value) < self.native.min: + self.native.value = self.native.min + + if self.native.value is not None and self.native.max is not None: + if float(self.native.value) > self.native.max: + self.native.value = self.native.max def get_readonly(self, value): return self.native.readOnly From 517efff34f47e1e4d09f83f56609a609abc47489 Mon Sep 17 00:00:00 2001 From: Luna Meadows Date: Sun, 30 Mar 2025 13:06:00 -0500 Subject: [PATCH 5/6] Update changes/3299.feature.rst Co-authored-by: Russell Keith-Magee --- changes/3299.feature.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changes/3299.feature.rst b/changes/3299.feature.rst index c5127be9e5..8fea0d5b67 100644 --- a/changes/3299.feature.rst +++ b/changes/3299.feature.rst @@ -1 +1 @@ -Added numberinput to web and textual backends. +The web and Textual backends now provide a NumberInput widget. From 599d84f45b49e7f46070c97a47ccc0076a9d6692 Mon Sep 17 00:00:00 2001 From: luna Date: Tue, 1 Apr 2025 12:35:51 -0500 Subject: [PATCH 6/6] Changed image for textual numberinput --- docs/reference/images/numberinput-textual.png | Bin 2174 -> 1611 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/reference/images/numberinput-textual.png b/docs/reference/images/numberinput-textual.png index 1c7ff2ec9cb0d3f7de55ffe01febc11866f5dd19..ef08b7c808845916dc6f3105870d7a2cb0b2e9e1 100644 GIT binary patch literal 1611 zcmb7EcTm#@6#gNjj7tv`95hM@I3Nll8wG@bj7kC-1jC3ZVH%=IXh38Y&>$rU5dSC>+`p{?Z*Gq>6;~95$OB4U8Gpi#8+x_8>zRH~`((62}0}szZUx--Hhpi)G^D z<4q0aTaIEd7@xt%EU^O56^dKE-N@2A7#+J4*{nfzOZcC9-!?Bqc?lN48Nz^GbO-z& zHrg6)8{X6LT-TkT3{sxcaE8e^o}Vty8!r!6aF zq#f($3u5qCk8*{Xu-e+azKf(8y36X8Vg-yn1`|p8rR~k=Oc6d0sg&@ZHgOJ0!&sI6 zi3lmpj)E-<-9VrDSiB+iX+EzZA2HCg{JO;R<>BrA9#nff0edXljs-+msIh5gySca{@O!mp%Ix;+N*`Hh|J{w;qS z5QDq=wpR|C7TJZEJa?;crwd{Yb;<)9*0ah21agb-bLq<_;^SdJuNwE)GmSUoikQK= zqxU=T+3p|DLkB1WE}QC&n4nD*(I;16I1>uV%l3juGXsZP!yGSX7ls|_Vy(kDbF>>} zEyaE7Bk6tJJrbqSd4vQHRE3LLSEi*)Qf+fdiQ0nTk-G?`L%*YI&YH#buW)Hwmh?Z1 zmhkIX;~hpV=L&ZeA)|b%g46C9KKJo(k7uUjPy`kN)p_S{W=|bCQgqw0TM$$eLx-bbW(>$n-nL4c=p}U2!9-Mh(gM{)Sh{Qwg|xc5`zAN1@xe}4s@@cf@Iw2+SwZJ7we%I*h!^J|HPmW`&{4SL~tTdpLnxLxfu<>!+ zKEDXF%&0kj6;!=zZ6Qq=#9+O+Cwe!w2KH>eddsfH**Z3Q`?UNQfRn&{0k>fQ{1 zjfQD!^Hc1yTU%Roq88R~=N{Io7oqp)mT&9gDpz;hr`e*;Al=kVOhVu>D_#&Oz$EkppLqmUqSW^pJ>)3H!>ZM z>0^+!OdKurVsY}qfbM*{mihInmBid!Nz94ny;hC=wzgBd4i$flF=eA2Cond3pJlFE z+W?u0O27#lsc1PRiSL6bEsll99jLkmBvFnVIl5HUtkiTzHnjN~T&7MpzUI(5 z0y;h(s>NxZ_uOmVJ(GOW>~Zm*R4CA_KpPT`D1>wIabxh!c# zCtQL&E<7oG*O8O9u=FI~oD4nU?KMoUoyZ~=Sb9dfn6TTp?ia9!Wy6D8lleu4dmT29 z8e4v8&(fs1_GLxB$D2lbaa#K_n7VXYrad>Y{p1u8v&U@WBW3|6e_dXED zd06F|hLxCJI!h4R8`aoPcg5Re!&rI|gDMPUTLx!(*^@ecO(wEs#BmmuuAdnnl{Ky+ zUwQ9Icfzb?fM8M#FsZq#r@D@kstqfOABUnK>(SpnT7pMF+du-a!0@v`E?yU2J?aJ& z0Fs(6H6MhoJ&zw4{y+*0TaFGK@Qw!s9Pl`Ka{97#VD+WGv`G91bd483Xq-6UYF)@* z382aye;IlN1|Ev_JFS7=Ss4%dqKbmNc^Djx2NHlv34dy+8tOIzlI+&4LTEBaPqiRHV_{6hIAKN!3^~0~UbvvDnrS1y3ArM)R zCUc00ob(>0)u7}tGra%m;beG;K6T0L#qUFQw3eYa`|!EzkWRe|eVYMyl3tQ3R`R`t z15=^F_YiH&kda4C4RC3?u17v<<{w2|z)(^_&4Hl)qUIKM z{;@fkY6)bj+x6%!k)!Ij1gCHjKysUpIh+PiHHc}WN4p_D5S3`&KJVLvlJXI{9sp2qD;|~x0UV>n);b`uZd-EU7TKL2{JduW-sb9UqQ)Zr3bwi zBYkY4jJS0Anb6z8jy(+)SHT?TZVq#8qsqWCe6)+z0<%Hz*|AE3j%-9*szX&aNTB!G z0b69>)nr-5Vrb~cbu|6x+<3AqOj|g?K-j>JXiU93_Idcxr!CgX!g(SSHdS5#z7%JD zX1r26R)=!V{~QA9^Edl2%^ZaJ#N)PQBY51(Us_-oOq!?baVy>VTfPcpBTmha3~%`~ zQvyWW8}QDKtrNGY%}JKgyV7FIcJtRyr*JLm2fdW@yDiH0!Y8K-2NjSDyZD<4T;8`G zurW<+A+mOkEsvfP*1H2sz}F>+1ob3Nz*phbz!WvGFJ%5AwxZfw7G>P=j4#}}Z-;@1 zp1U@iXrhcNw$ZDO+aB9ueCHii$C7n0Zu2(kBAvu&_0sJ(-@-r*`$2lvaP#JdpxVAK zA(!@j@EebR{us6|bUD}d*q&bgbn%v=ZVuj?-Co|dd=grqvtgw^w}zK4N=^{r3fBck z_h@hLe?j^^n_TjFNsW6sC>B{o+5)qeI?)Qdf@I2zsB8730&&Z{k4?20LS+)4rlvUA zd+Bo!R4lRRkCUQ@t>xDkx|r!5r3Ey9`Ch=;+AyIih#%;{e>N+nRL-O{YA7;_W7P>j zsJUvp3Mg+On&1M2rkFJ8?ud>4s0}8|0Zg{9Oatl58HwWr{B#25$pM__VZx1XmkV}i zPJ^f2V>vDN!`>O{R>_6rBZUUbG-}e#RQc!d|28VZdEEc&?o!}0Bd68{CuL)eo5