فهرست منبع

refactor list and menu items (#2855)

* move item, item section and item label to own file and add text to item

* add auto_close to menu and deprecate menu item

* add auto_close parameter to context menu

* replace menu_item with item in tests

* use new item in documentation

* use new item in header

* code review

* don't deprecate menu_item, but inherit from item and rework auto_close

* warn if the auto_close parameter of the menu and its items differs

* update tests and documentation to use the menu_item again

* remove auto_close parameter from menus

* simplify tests and documentation

* more cleanup

* remove ui.list where it's not needed

---------

Co-authored-by: Falko Schindler <falko@zauberzeug.com>
Paula Kammler 1 سال پیش
والد
کامیت
6e61721a3a

+ 1 - 0
nicegui/elements/context_menu.py

@@ -13,6 +13,7 @@ class ContextMenu(Element):
         super().__init__('q-menu')
         super().__init__('q-menu')
         self._props['context-menu'] = True
         self._props['context-menu'] = True
         self._props['touch-position'] = True
         self._props['touch-position'] = True
+        self._props['auto-close'] = True
 
 
     def open(self) -> None:
     def open(self) -> None:
         """Open the context menu."""
         """Open the context menu."""

+ 63 - 0
nicegui/elements/item.py

@@ -0,0 +1,63 @@
+from typing import Any, Callable, Optional
+
+from typing_extensions import Self
+
+from ..events import ClickEventArguments, handle_event
+from .mixins.disableable_element import DisableableElement
+from .mixins.text_element import TextElement
+
+
+class Item(DisableableElement):
+
+    def __init__(self, text: str = '', *, on_click: Optional[Callable[..., Any]] = None) -> None:
+        """List Item
+
+        Creates a clickable list item based on Quasar's
+        `QItem <https://quasar.dev/vue-components/list-and-list-items#qitem-api>`_ component.
+        The item should be placed inside a ``ui.list`` or ``ui.menu`` element.
+        If the text parameter is provided, an item section will be created with the given text.
+        If you want to customize how the text is displayed, you need to create your own item section and label elements.
+
+        :param text: text to be displayed (default: "")
+        :param on_click: callback to be executed when clicking on the item (sets the "clickable" prop to True)
+        """
+        super().__init__(tag='q-item')
+
+        if on_click:
+            self.on_click(on_click)
+
+        if text:
+            with self:
+                ItemSection(text=text)
+
+    def on_click(self, callback: Callable[..., Any]) -> Self:
+        """Add a callback to be invoked when the List Item is clicked."""
+        self._props['clickable'] = True  # idempotent
+        self.on('click', lambda _: handle_event(callback, ClickEventArguments(sender=self, client=self.client)))
+        return self
+
+
+class ItemSection(TextElement):
+
+    def __init__(self, text: str = '') -> None:
+        """List Item Section
+
+        Creates an item section based on Quasar's
+        `QItemSection <https://quasar.dev/vue-components/list-and-list-items#qitemsection-api>`_ component.
+        The section should be placed inside a ``ui.item`` element.
+
+        :param text: text to be displayed (default: "")
+        """
+        super().__init__(tag='q-item-section', text=text)
+
+
+class ItemLabel(TextElement):
+
+    def __init__(self, text: str = '') -> None:
+        """List Item Label
+
+        Creates an item label based on Quasar's `QItemLabel <https://quasar.dev/vue-components/list-and-list-items#qitemlabel-api>`_ component.
+
+        :param text: text to be displayed (default: "")
+        """
+        super().__init__(tag='q-item-label', text=text)

+ 1 - 53
nicegui/elements/list.py

@@ -1,11 +1,4 @@
-from typing import Any, Callable, Optional
-
-from typing_extensions import Self
-
 from ..element import Element
 from ..element import Element
-from ..events import ClickEventArguments, handle_event
-from .mixins.disableable_element import DisableableElement
-from .mixins.text_element import TextElement
 
 
 
 
 class List(Element):
 class List(Element):
@@ -14,51 +7,6 @@ class List(Element):
         """List
         """List
 
 
         A list element based on Quasar's `QList <https://quasar.dev/vue-components/list-and-list-items#qlist-api>`_ component.
         A list element based on Quasar's `QList <https://quasar.dev/vue-components/list-and-list-items#qlist-api>`_ component.
-        It provides a container for list items.
+        It provides a container for ``ui.item`` elements.
         """
         """
         super().__init__('q-list')
         super().__init__('q-list')
-
-
-class Item(DisableableElement):
-
-    def __init__(self, *, on_click: Optional[Callable[..., Any]] = None) -> None:
-        """List Item
-
-        Creates a list item based on Quasar's `QItem <https://quasar.dev/vue-components/list-and-list-items#qitem-api>`_ component.
-        The item should be placed inside a list element.
-        """
-        super().__init__(tag='q-item')
-
-        if on_click:
-            self.on_click(on_click)
-
-    def on_click(self, callback: Callable[..., Any]) -> Self:
-        """Add a callback to be invoked when the List Item is clicked."""
-        self._props['clickable'] = True  # idempotent
-        self.on('click', lambda _: handle_event(callback, ClickEventArguments(sender=self, client=self.client)))
-        return self
-
-
-class ItemSection(Element):
-
-    def __init__(self) -> None:
-        """
-        List Item Section
-
-        Creates an item section based on Quasar's `QItemList <https://quasar.dev/vue-components/list-and-list-items#qitemsection-api>`_ component.
-        The section should be placed inside a list item element.
-        """
-        super().__init__('q-item-section')
-
-
-class ItemLabel(TextElement):
-
-    def __init__(self, text: str = '') -> None:
-        """
-        List Item Label
-
-        Creates an item label based on Quasar's `QItemLabel <https://quasar.dev/vue-components/list-and-list-items#qitemlabel-api>`_ component.
-
-        :param text: text to be displayed (default: "")
-        """
-        super().__init__(tag='q-item-label', text=text)

+ 20 - 19
nicegui/elements/menu.py

@@ -1,12 +1,11 @@
-from typing import Any, Callable, Optional
+from typing import Any, Callable, Optional, Union
 
 
 from typing_extensions import Self
 from typing_extensions import Self
 
 
-from .. import context
-from ..events import ClickEventArguments, handle_event
+from ..element import Element
 from ..logging import log
 from ..logging import log
 from .context_menu import ContextMenu
 from .context_menu import ContextMenu
-from .mixins.text_element import TextElement
+from .item import Item
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
 
 
 
 
@@ -21,6 +20,7 @@ class Menu(ValueElement):
         :param value: whether the menu is already opened (default: `False`)
         :param value: whether the menu is already opened (default: `False`)
         """
         """
         super().__init__(tag='q-menu', value=value, on_value_change=None)
         super().__init__(tag='q-menu', value=value, on_value_change=None)
+        self._props['auto-close'] = True
 
 
     def open(self) -> None:
     def open(self) -> None:
         """Open the menu."""
         """Open the menu."""
@@ -44,7 +44,7 @@ class Menu(ValueElement):
         return self
         return self
 
 
 
 
-class MenuItem(TextElement):
+class MenuItem(Item):
 
 
     def __init__(self,
     def __init__(self,
                  text: str = '',
                  text: str = '',
@@ -60,20 +60,21 @@ class MenuItem(TextElement):
         :param on_click: callback to be executed when selecting the menu item
         :param on_click: callback to be executed when selecting the menu item
         :param auto_close: whether the menu should be closed after a click event (default: `True`)
         :param auto_close: whether the menu should be closed after a click event (default: `True`)
         """
         """
-        super().__init__(tag='q-item', text=text)
-        self.menu = context.get_slot().parent
+        super().__init__(text=text, on_click=on_click)
+
         self._props['clickable'] = True
         self._props['clickable'] = True
-        self._click_handlers = [on_click] if on_click else []
 
 
-        def handle_click(_) -> None:
-            for handler in self._click_handlers:
-                handle_event(handler, ClickEventArguments(sender=self, client=self.client))
+        self.menu = self._find_menu()
+        if self.menu:
             if auto_close:
             if auto_close:
-                assert isinstance(self.menu, (Menu, ContextMenu))
-                self.menu.close()
-        self.on('click', handle_click, [])
-
-    def on_click(self, callback: Callable[..., Any]) -> Self:
-        """Add a callback to be invoked when the menu item is clicked."""
-        self._click_handlers.append(callback)
-        return self
+                self.on_click(self.menu.close)
+            else:
+                self.menu.props(remove='auto-close')
+
+    def _find_menu(self) -> Optional[Union[Menu, ContextMenu]]:
+        element: Element = self
+        while element.parent_slot:
+            element = element.parent_slot.parent
+            if isinstance(element, (Menu, ContextMenu)):
+                return element
+        return None

+ 3 - 3
nicegui/ui.py

@@ -156,6 +156,9 @@ from .elements.icon import Icon as icon
 from .elements.image import Image as image
 from .elements.image import Image as image
 from .elements.input import Input as input  # pylint: disable=redefined-builtin
 from .elements.input import Input as input  # pylint: disable=redefined-builtin
 from .elements.interactive_image import InteractiveImage as interactive_image
 from .elements.interactive_image import InteractiveImage as interactive_image
+from .elements.item import Item as item
+from .elements.item import ItemLabel as item_label
+from .elements.item import ItemSection as item_section
 from .elements.joystick import Joystick as joystick
 from .elements.joystick import Joystick as joystick
 from .elements.json_editor import JsonEditor as json_editor
 from .elements.json_editor import JsonEditor as json_editor
 from .elements.keyboard import Keyboard as keyboard
 from .elements.keyboard import Keyboard as keyboard
@@ -165,9 +168,6 @@ from .elements.leaflet import Leaflet as leaflet
 from .elements.line_plot import LinePlot as line_plot
 from .elements.line_plot import LinePlot as line_plot
 from .elements.link import Link as link
 from .elements.link import Link as link
 from .elements.link import LinkTarget as link_target
 from .elements.link import LinkTarget as link_target
-from .elements.list import Item as item
-from .elements.list import ItemLabel as item_label
-from .elements.list import ItemSection as item_section
 from .elements.list import List as list  # pylint: disable=redefined-builtin
 from .elements.list import List as list  # pylint: disable=redefined-builtin
 from .elements.log import Log as log
 from .elements.log import Log as log
 from .elements.markdown import Markdown as markdown
 from .elements.markdown import Markdown as markdown

+ 1 - 3
tests/test_button_dropdown.py

@@ -4,9 +4,7 @@ from nicegui.testing import Screen
 
 
 def test_dropdown_button(screen: Screen):
 def test_dropdown_button(screen: Screen):
     with ui.dropdown_button('Button', on_click=lambda: ui.label('Button clicked')):
     with ui.dropdown_button('Button', on_click=lambda: ui.label('Button clicked')):
-        with ui.list():
-            with ui.item(on_click=lambda: ui.label('Item clicked')):
-                ui.item_label('Item')
+        ui.item('Item', on_click=lambda: ui.label('Item clicked'))
 
 
     screen.open('/')
     screen.open('/')
     screen.click('Button')
     screen.click('Button')

+ 1 - 3
tests/test_button_group.py

@@ -7,9 +7,7 @@ def test_button_group(screen: Screen):
         ui.button('Button 1', on_click=lambda: ui.label('Button 1 clicked'))
         ui.button('Button 1', on_click=lambda: ui.label('Button 1 clicked'))
         ui.button('Button 2', on_click=lambda: ui.label('Button 2 clicked'))
         ui.button('Button 2', on_click=lambda: ui.label('Button 2 clicked'))
         with ui.dropdown_button('Button 3', on_click=lambda: ui.label('Button 3 clicked')):
         with ui.dropdown_button('Button 3', on_click=lambda: ui.label('Button 3 clicked')):
-            with ui.list():
-                with ui.item(on_click=lambda: ui.label('Item clicked')):
-                    ui.item_label('Item')
+            ui.item('Item', on_click=lambda: ui.label('Item clicked'))
 
 
     screen.open('/')
     screen.open('/')
     screen.click('Button 1')
     screen.click('Button 1')

+ 3 - 3
tests/test_context_menu.py

@@ -5,12 +5,12 @@ from nicegui.testing import Screen
 def test_context_menu(screen: Screen):
 def test_context_menu(screen: Screen):
     with ui.label('Right-click me'):
     with ui.label('Right-click me'):
         with ui.context_menu():
         with ui.context_menu():
-            ui.menu_item('Item 1', on_click=lambda: ui.label('Item 1 clicked'))
+            ui.menu_item('Item 1', on_click=lambda: ui.notify('You clicked'))
             ui.menu_item('Item 2')
             ui.menu_item('Item 2')
 
 
     screen.open('/')
     screen.open('/')
     screen.context_click('Right-click me')
     screen.context_click('Right-click me')
     screen.click('Item 1')
     screen.click('Item 1')
-    screen.should_contain('Item 1 clicked')
+    screen.should_contain('You clicked')
     screen.wait(0.5)
     screen.wait(0.5)
-    screen.should_not_contain('Item 2')
+    screen.should_not_contain('Item 1')

+ 2 - 6
tests/test_list.py

@@ -4,12 +4,8 @@ from nicegui.testing import Screen
 
 
 def test_clicking_items(screen: Screen):
 def test_clicking_items(screen: Screen):
     with ui.list():
     with ui.list():
-        with ui.item(on_click=lambda: ui.notify('Clicked item 1')):
-            with ui.item_section():
-                ui.item_label('Item 1')
-        with ui.item(on_click=lambda: ui.notify('Clicked item 2')):
-            with ui.item_section():
-                ui.item_label('Item 2')
+        ui.item('Item 1', on_click=lambda: ui.notify('Clicked item 1'))
+        with ui.item('Item 2', on_click=lambda: ui.notify('Clicked item 2')):
             with ui.item_section():
             with ui.item_section():
                 ui.button('Button').on('click.stop', lambda: ui.notify('Clicked button!'))
                 ui.button('Button').on('click.stop', lambda: ui.notify('Clicked button!'))
 
 

+ 2 - 7
website/documentation/content/button_dropdown_documentation.py

@@ -6,13 +6,8 @@ from . import doc
 @doc.demo(ui.dropdown_button)
 @doc.demo(ui.dropdown_button)
 def main_demo() -> None:
 def main_demo() -> None:
     with ui.dropdown_button('Open me!', auto_close=True):
     with ui.dropdown_button('Open me!', auto_close=True):
-        with ui.list():
-            with ui.item(on_click=lambda: ui.notify('You clicked item 1')):
-                with ui.item_section():
-                    ui.item_label('Item 1')
-            with ui.item(on_click=lambda: ui.notify('You clicked item 2')):
-                with ui.item_section():
-                    ui.item_label('Item 2')
+        ui.item('Item 1', on_click=lambda: ui.notify('You clicked item 1'))
+        ui.item('Item 2', on_click=lambda: ui.notify('You clicked item 2'))
 
 
 
 
 @doc.demo('Custom elements inside dropdown button', '''
 @doc.demo('Custom elements inside dropdown button', '''

+ 2 - 7
website/documentation/content/button_group_documentation.py

@@ -19,13 +19,8 @@ def with_dropdown() -> None:
         ui.button('One')
         ui.button('One')
         ui.button('Two')
         ui.button('Two')
         with ui.dropdown_button('Dropdown'):
         with ui.dropdown_button('Dropdown'):
-            with ui.list():
-                with ui.item(on_click=lambda: ui.notify('Item 1')):
-                    with ui.item_section():
-                        ui.item_label('Item 1')
-                with ui.item(on_click=lambda: ui.notify('Item 2')):
-                    with ui.item_section():
-                        ui.item_label('Item 2')
+            ui.item('Item 1', on_click=lambda: ui.notify('Item 1'))
+            ui.item('Item 2', on_click=lambda: ui.notify('Item 2'))
 
 
 
 
 @doc.demo('Button group styling', '''
 @doc.demo('Button group styling', '''

+ 1 - 1
website/documentation/content/context_menu_documentation.py

@@ -10,7 +10,7 @@ def main_demo() -> None:
             ui.menu_item('Flip horizontally')
             ui.menu_item('Flip horizontally')
             ui.menu_item('Flip vertically')
             ui.menu_item('Flip vertically')
             ui.separator()
             ui.separator()
-            ui.menu_item('Reset')
+            ui.menu_item('Reset', auto_close=False)
 
 
 
 
 doc.reference(ui.context_menu)
 doc.reference(ui.context_menu)

+ 4 - 12
website/documentation/content/list_documentation.py

@@ -6,18 +6,10 @@ from . import doc
 @doc.demo(ui.list)
 @doc.demo(ui.list)
 def main_demo() -> None:
 def main_demo() -> None:
     with ui.list().props('dense separator'):
     with ui.list().props('dense separator'):
-        with ui.item():
-            with ui.item_section():
-                ui.item_label('3 Apples')
-        with ui.item():
-            with ui.item_section():
-                ui.item_label('5 Bananas')
-        with ui.item():
-            with ui.item_section():
-                ui.item_label('8 Strawberries')
-        with ui.item():
-            with ui.item_section():
-                ui.item_label('13 Walnuts')
+        ui.item('3 Apples')
+        ui.item('5 Bananas')
+        ui.item('8 Strawberries')
+        ui.item('13 Walnuts')
 
 
 
 
 @doc.demo('Items, Sections and Labels', '''
 @doc.demo('Items, Sections and Labels', '''

+ 1 - 1
website/documentation/content/menu_documentation.py

@@ -14,7 +14,7 @@ def main_demo() -> None:
                 ui.menu_item('Menu item 3 (keep open)',
                 ui.menu_item('Menu item 3 (keep open)',
                              lambda: result.set_text('Selected item 3'), auto_close=False)
                              lambda: result.set_text('Selected item 3'), auto_close=False)
                 ui.separator()
                 ui.separator()
-                ui.menu_item('Close', on_click=menu.close)
+                ui.menu_item('Close', menu.close)
 
 
 
 
 doc.reference(ui.menu)
 doc.reference(ui.menu)