Repository: zauberzeug/nicegui
Pull request #2762 (2024-03-25 10:06:57)

Add range control
Implementing this [feature request](https://github.com/zauberzeug/nicegui/discussions/1191) to add a range slider based on Quasar's [QRange](https://quasar.dev/vue-components/range).


Patch:
diff --git a/nicegui/elements/range.py b/nicegui/elements/range.py
new file mode 100644
index 00000000..69afe616
--- /dev/null
+++ b/nicegui/elements/range.py
@@ -0,0 +1,29 @@
+from typing import Any, Callable, Dict, Optional
+
+from .mixins.disableable_element import DisableableElement
+from .mixins.value_element import ValueElement
+
+
+class Range(ValueElement, DisableableElement):
+
+    def __init__(self, *,
+                 min: float,  # pylint: disable=redefined-builtin
+                 max: float,  # pylint: disable=redefined-builtin
+                 step: float = 1.0,
+                 value: Optional[Dict[str, int]] = None,
+                 on_change: Optional[Callable[..., Any]] = None,
+                 ) -> None:
+        """Range
+
+        This element is based on Quasar's `QRange <https://quasar.dev/vue-components/range>`_ component.
+
+        :param min: lower bound of the range
+        :param max: upper bound of the range
+        :param step: step size
+        :param value: initial value to set min and max position of the range
+        :param on_change: callback which is invoked when the user releases the range
+        """
+        super().__init__(tag='q-range', value=value, on_value_change=on_change, throttle=0.05)
+        self._props['min'] = min
+        self._props['max'] = max
+        self._props['step'] = step
diff --git a/nicegui/ui.py b/nicegui/ui.py
index 7074f18f..2082dc7b 100644
--- a/nicegui/ui.py
+++ b/nicegui/ui.py
@@ -60,6 +60,7 @@ __all__ = [
     'pyplot',
     'query',
     'radio',
+    'range',
     'restructured_text',
     'row',
     'scene',
@@ -173,6 +174,7 @@ from .elements.progress import LinearProgress as linear_progress
 from .elements.pyplot import Pyplot as pyplot
 from .elements.query import Query as query
 from .elements.radio import Radio as radio
+from .elements.range import Range as range
 from .elements.restructured_text import ReStructuredText as restructured_text
 from .elements.row import Row as row
 from .elements.scene import Scene as scene
diff --git a/website/documentation/content/range_documentation.py b/website/documentation/content/range_documentation.py
new file mode 100644
index 00000000..02e87489
--- /dev/null
+++ b/website/documentation/content/range_documentation.py
@@ -0,0 +1,26 @@
+from nicegui import ui
+
+from . import doc
+
+
+@doc.demo(ui.range)
+def main_demo() -> None:
+    min_max_range = ui.range(min=0, max=100, value={'min': 20, 'max': 80})
+    ui.label().bind_text_from(min_max_range, 'value',
+                              backward=lambda v: f'min: {v["min"]}, max: {v["max"]}')
+
+
+@doc.demo('Customize labels', '''
+        You can customize the colors of the range and its labels by setting them individually or for the range in total.
+''')
+def customize_labels():
+    ui.label('Color the entire range')
+    ui.range(min=0, max=100, value={'min': 20, 'max': 80}) \
+        .props('label snap color="secondary"')
+
+    ui.label('Customize the color of the labels')
+    ui.range(min=0, max=100, value={'min': 40, 'max': 80}) \
+        .props('label-always snap label-color="secondary" right-label-text-color="black"')
+
+
+doc.reference(ui.range)
diff --git a/website/documentation/content/section_controls.py b/website/documentation/content/section_controls.py
index 28a73a7d..a4f06c4b 100644
--- a/website/documentation/content/section_controls.py
+++ b/website/documentation/content/section_controls.py
@@ -1,6 +1,6 @@
 from . import (badge_documentation, button_documentation, checkbox_documentation, color_input_documentation,
                color_picker_documentation, date_documentation, doc, input_documentation, joystick_documentation,
-               knob_documentation, number_documentation, radio_documentation, select_documentation,
+               knob_documentation, number_documentation, radio_documentation, range_documentation, select_documentation,
                slider_documentation, switch_documentation, textarea_documentation, time_documentation,
                toggle_documentation, upload_documentation)
 
@@ -14,6 +14,7 @@ doc.intro(select_documentation)
 doc.intro(checkbox_documentation)
 doc.intro(switch_documentation)
 doc.intro(slider_documentation)
+doc.intro(range_documentation)
 doc.intro(joystick_documentation)
 doc.intro(input_documentation)
 doc.intro(textarea_documentation)


Test patch:
diff --git a/tests/test_range.py b/tests/test_range.py
new file mode 100644
index 00000000..69fb6f5d
--- /dev/null
+++ b/tests/test_range.py
@@ -0,0 +1,10 @@
+from nicegui import ui
+from nicegui.testing import Screen
+
+
+def test_range(screen: Screen):
+    r = ui.range(min=0, max=100, value={'min': 20, 'max': 80})
+    ui.label().bind_text_from(r, 'value', backward=lambda v: f'min: {v["min"]}, max: {v["max"]}')
+
+    screen.open('/')
+    screen.should_contain('min: 20, max: 80')