Forráskód Böngészése

form support more inputs (#1554)

Thomas Brandého 1 éve
szülő
commit
cd47815a4d

+ 16 - 2
reflex/.templates/web/utils/state.js

@@ -383,12 +383,26 @@ export const preventDefault = (event) => {
  * @returns The value.
  */
 export const getRefValue = (ref) => {
-  if (!ref || !ref.current){
+  if (!ref || !ref.current) {
     return;
   }
   if (ref.current.type == "checkbox") {
     return ref.current.checked;
   } else {
-    return ref.current.value;
+    //querySelector(":checked") is needed to get value from radio_group
+    return ref.current.value || (ref.current.querySelector(':checked') && ref.current.querySelector(':checked').value);
   }
 }
+
+/**
+ * Get the values from a ref array.
+ * @param refs The refs to get the values from.
+ * @returns The values array.
+ */
+export const getRefValues = (refs) => {
+  if (!refs) {
+    return;
+  }
+  // getAttribute is used by RangeSlider because it doesn't assign value
+  return refs.map((ref) => ref.current.value || ref.current.getAttribute("aria-valuenow"));
+}

+ 1 - 0
reflex/compiler/compiler.py

@@ -28,6 +28,7 @@ DEFAULT_IMPORTS: imports.ImportDict = {
         ImportVar(tag="preventDefault"),
         ImportVar(tag="refs"),
         ImportVar(tag="getRefValue"),
+        ImportVar(tag="getRefValues"),
         ImportVar(tag="getAllLocalStorageItems"),
     },
     "": {ImportVar(tag="focus-visible/dist/focus-visible")},

+ 12 - 6
reflex/components/forms/form.py

@@ -22,12 +22,18 @@ class Form(ChakraComponent):
             A dict mapping the event trigger to the var that is passed to the handler.
         """
         # Send all the input refs to the handler.
-        return {
-            "on_submit": {
-                ref[4:]: Var.create(f"getRefValue({ref})", is_local=False)
-                for ref in self.get_refs()
-            }
-        }
+        form_refs = {}
+        for ref in self.get_refs():
+            # when ref start with refs_ it's an array of refs, so we need different method
+            # to collect data
+            if ref.startswith("refs_"):
+                form_refs[ref[5:-3]] = Var.create(
+                    f"getRefValues({ref[:-3]})", is_local=False
+                )
+            else:
+                form_refs[ref[4:]] = Var.create(f"getRefValue({ref})", is_local=False)
+
+        return {"on_submit": form_refs}
 
 
 class FormControl(ChakraComponent):

+ 2 - 1
reflex/components/forms/numberinput.py

@@ -88,8 +88,9 @@ class NumberInput(ChakraComponent):
             The component.
         """
         if len(children) == 0:
+            _id = props.pop("id", None)
             children = [
-                NumberInputField.create(),
+                NumberInputField.create(id=_id) if _id else NumberInputField.create(),
                 NumberInputStepper.create(
                     NumberIncrementStepper.create(),
                     NumberDecrementStepper.create(),

+ 54 - 2
reflex/components/forms/pininput.py

@@ -1,10 +1,12 @@
 """A pin input component."""
 
-from typing import Dict
+from typing import Dict, Optional
 
 from reflex.components.component import Component
+from reflex.components.layout import Foreach
 from reflex.components.libs.chakra import ChakraComponent
 from reflex.event import EVENT_ARG
+from reflex.utils import format
 from reflex.vars import Var
 
 
@@ -66,6 +68,26 @@ class PinInput(ChakraComponent):
             "on_complete": EVENT_ARG,
         }
 
+    def get_ref(self):
+        """Return a reference because we actually attached the ref to the PinInputFields.
+
+        Returns:
+            None.
+        """
+        return None
+
+    def _get_hooks(self) -> Optional[str]:
+        """Override the base get_hooks to handle array refs.
+
+        Returns:
+            The overrided hooks.
+        """
+        if self.id:
+            ref = format.format_array_ref(self.id, None)
+            if ref:
+                return f"const {ref} = Array.from({{length:{self.length}}}, () => useRef(null));"
+            return super()._get_hooks()
+
     @classmethod
     def create(cls, *children, **props) -> Component:
         """Create a pin input component.
@@ -81,7 +103,21 @@ class PinInput(ChakraComponent):
             The pin input component.
         """
         if not children and "length" in props:
-            children = [PinInputField()] * props["length"]
+            _id = props.get("id", None)
+            length = props["length"]
+            if _id:
+                children = [
+                    Foreach.create(
+                        list(range(length)),  # type: ignore
+                        lambda ref, i: PinInputField.create(
+                            key=i,
+                            id=_id,
+                            index=i,
+                        ),
+                    )
+                ]
+            else:
+                children = [PinInputField()] * length
         return super().create(*children, **props)
 
 
@@ -89,3 +125,19 @@ class PinInputField(ChakraComponent):
     """The text field that user types in - must be a direct child of PinInput."""
 
     tag = "PinInputField"
+
+    # the position of the PinInputField inside the PinInput.
+    # Default to None because it is assigned by PinInput when created.
+    index: Optional[Var[int]] = None
+
+    def _get_hooks(self) -> Optional[str]:
+        return None
+
+    def get_ref(self):
+        """Get the array ref for the pin input.
+
+        Returns:
+            The array ref.
+        """
+        if self.id:
+            return format.format_array_ref(self.id, self.index)

+ 52 - 8
reflex/components/forms/rangeslider.py

@@ -1,10 +1,11 @@
 """A range slider component."""
 
-from typing import Dict, List
+from typing import Dict, List, Optional
 
 from reflex.components.component import Component
 from reflex.components.libs.chakra import ChakraComponent
 from reflex.event import EVENT_ARG
+from reflex.utils import format
 from reflex.vars import Var
 
 
@@ -55,6 +56,26 @@ class RangeSlider(ChakraComponent):
             "on_change_start": EVENT_ARG,
         }
 
+    def get_ref(self):
+        """Get the ref of the component.
+
+        Returns:
+            The ref of the component.
+        """
+        return None
+
+    def _get_hooks(self) -> Optional[str]:
+        """Override the base get_hooks to handle array refs.
+
+        Returns:
+            The overrided hooks.
+        """
+        if self.id:
+            ref = format.format_array_ref(self.id, None)
+            if ref:
+                return f"const {ref} = Array.from({{length:2}}, () => useRef(null));"
+            return super()._get_hooks()
+
     @classmethod
     def create(cls, *children, **props) -> Component:
         """Create a RangeSlider component.
@@ -69,13 +90,23 @@ class RangeSlider(ChakraComponent):
             The RangeSlider component.
         """
         if len(children) == 0:
-            children = [
-                RangeSliderTrack.create(
-                    RangeSliderFilledTrack.create(),
-                ),
-                RangeSliderThumb.create(index=0),
-                RangeSliderThumb.create(index=1),
-            ]
+            _id = props.get("id", None)
+            if _id:
+                children = [
+                    RangeSliderTrack.create(
+                        RangeSliderFilledTrack.create(),
+                    ),
+                    RangeSliderThumb.create(index=0, id=_id),
+                    RangeSliderThumb.create(index=1, id=_id),
+                ]
+            else:
+                children = [
+                    RangeSliderTrack.create(
+                        RangeSliderFilledTrack.create(),
+                    ),
+                    RangeSliderThumb.create(index=0),
+                    RangeSliderThumb.create(index=1),
+                ]
         return super().create(*children, **props)
 
 
@@ -98,3 +129,16 @@ class RangeSliderThumb(ChakraComponent):
 
     # The position of the thumb.
     index: Var[int]
+
+    def _get_hooks(self) -> Optional[str]:
+        # hook is None because RangeSlider is handling it.
+        return None
+
+    def get_ref(self):
+        """Get an array ref for the range slider thumb.
+
+        Returns:
+            The array ref.
+        """
+        if self.id:
+            return format.format_array_ref(self.id, self.index)

+ 18 - 0
reflex/utils/format.py

@@ -434,6 +434,24 @@ def format_ref(ref: str) -> str:
     return f"ref_{clean_ref}"
 
 
+def format_array_ref(refs: str, idx) -> str:
+    """Format a ref accessed by array.
+
+    Args:
+        refs : The ref array to access.
+        idx : The index of the ref in the array.
+
+    Returns:
+        The formatted ref.
+    """
+    clean_ref = re.sub(r"[^\w]+", "_", refs)
+    if idx:
+        idx.is_local = True
+        return f"refs_{clean_ref}[{idx}]"
+    else:
+        return f"refs_{clean_ref}"
+
+
 def format_dict(prop: ComponentStyle) -> str:
     """Format a dict with vars potentially as values.