Ver código fonte

Support named slots in Element.move (#3319)

* add optional parameter target_slot to Element.move

* add demo "Move elements to slots"

* add test test_move_slots

* code review

---------

Co-authored-by: Falko Schindler <falko@zauberzeug.com>
Niklas Neugebauer 10 meses atrás
pai
commit
30bcf66d7e

+ 17 - 4
nicegui/element.py

@@ -498,19 +498,32 @@ class Element(Visibility):
             slot.children.clear()
             slot.children.clear()
         self.update()
         self.update()
 
 
-    def move(self, target_container: Optional[Element] = None, target_index: int = -1) -> None:
+    def move(self,
+             target_container: Optional[Element] = None,
+             target_index: int = -1, *,
+             target_slot: Optional[str] = None) -> None:
         """Move the element to another container.
         """Move the element to another container.
 
 
         :param target_container: container to move the element to (default: the parent container)
         :param target_container: container to move the element to (default: the parent container)
         :param target_index: index within the target slot (default: append to the end)
         :param target_index: index within the target slot (default: append to the end)
+        :param target_slot: slot within the target container (default: default slot)
         """
         """
         assert self.parent_slot is not None
         assert self.parent_slot is not None
         self.parent_slot.children.remove(self)
         self.parent_slot.children.remove(self)
         self.parent_slot.parent.update()
         self.parent_slot.parent.update()
         target_container = target_container or self.parent_slot.parent
         target_container = target_container or self.parent_slot.parent
-        target_index = target_index if target_index >= 0 else len(target_container.default_slot.children)
-        target_container.default_slot.children.insert(target_index, self)
-        self.parent_slot = target_container.default_slot
+
+        if target_slot is None:
+            self.parent_slot = target_container.default_slot
+        elif target_slot in target_container.slots:
+            self.parent_slot = target_container.slots[target_slot]
+        else:
+            raise ValueError(f'Slot "{target_slot}" does not exist in the target container. '
+                             f'Add it first using `add_slot("{target_slot}")`.')
+
+        target_index = target_index if target_index >= 0 else len(self.parent_slot.children)
+        self.parent_slot.children.insert(target_index, self)
+
         target_container.update()
         target_container.update()
 
 
     def remove(self, element: Union[Element, int]) -> None:
     def remove(self, element: Union[Element, int]) -> None:

+ 33 - 0
tests/test_element.py

@@ -134,17 +134,50 @@ def test_move(screen: Screen):
 
 
     screen.open('/')
     screen.open('/')
     assert screen.find('A').location['y'] < screen.find('X').location['y'] < screen.find('B').location['y']
     assert screen.find('A').location['y'] < screen.find('X').location['y'] < screen.find('B').location['y']
+
     screen.click('Move X to B')
     screen.click('Move X to B')
     screen.wait(0.5)
     screen.wait(0.5)
     assert screen.find('A').location['y'] < screen.find('B').location['y'] < screen.find('X').location['y']
     assert screen.find('A').location['y'] < screen.find('B').location['y'] < screen.find('X').location['y']
+
     screen.click('Move X to A')
     screen.click('Move X to A')
     screen.wait(0.5)
     screen.wait(0.5)
     assert screen.find('A').location['y'] < screen.find('X').location['y'] < screen.find('B').location['y']
     assert screen.find('A').location['y'] < screen.find('X').location['y'] < screen.find('B').location['y']
+
     screen.click('Move X to top')
     screen.click('Move X to top')
     screen.wait(0.5)
     screen.wait(0.5)
     assert screen.find('X').location['y'] < screen.find('A').location['y'] < screen.find('B').location['y']
     assert screen.find('X').location['y'] < screen.find('A').location['y'] < screen.find('B').location['y']
 
 
 
 
+def test_move_slots(screen: Screen):
+    with ui.expansion(value=True) as a:
+        with a.add_slot('header'):
+            ui.label('A')
+        x = ui.label('X')
+
+    with ui.expansion(value=True) as b:
+        with b.add_slot('header'):
+            ui.label('B')
+
+    ui.button('Move X to header', on_click=lambda: x.move(target_slot='header'))
+    ui.button('Move X to B', on_click=lambda: x.move(b))
+    ui.button('Move X to top', on_click=lambda: x.move(target_index=0))
+
+    screen.open('/')
+    assert screen.find('A').location['y'] < screen.find('X').location['y'], 'X is in A.default'
+
+    screen.click('Move X to header')
+    screen.wait(0.5)
+    assert screen.find('A').location['y'] == screen.find('X').location['y'], 'X is in A.header'
+
+    screen.click('Move X to top')
+    screen.wait(0.5)
+    assert screen.find('A').location['y'] < screen.find('X').location['y'], 'X is in A.default'
+
+    screen.click('Move X to B')
+    screen.wait(0.5)
+    assert screen.find('B').location['y'] < screen.find('X').location['y'], 'X is in B.default'
+
+
 def test_xss(screen: Screen):
 def test_xss(screen: Screen):
     ui.label('</script><script>alert(1)</script>')
     ui.label('</script><script>alert(1)</script>')
     ui.label('<b>Bold 1</b>, `code`, copy&paste, multi\nline')
     ui.label('<b>Bold 1</b>, `code`, copy&paste, multi\nline')

+ 13 - 0
website/documentation/content/element_documentation.py

@@ -25,6 +25,19 @@ def move_elements() -> None:
     ui.button('Move X to top', on_click=lambda: x.move(target_index=0))
     ui.button('Move X to top', on_click=lambda: x.move(target_index=0))
 
 
 
 
+@doc.demo('Move elements to slots', '''
+    This demo shows how to move elements between slots within an element.
+''')
+def move_elements_to_slots() -> None:
+    with ui.card() as card:
+        name = ui.input('Name', value='Paul')
+        name.add_slot('append')
+        icon = ui.icon('face')
+
+    ui.button('Move into input', on_click=lambda: icon.move(name, target_slot='append'))
+    ui.button('Move out of input', on_click=lambda: icon.move(card))
+
+
 @doc.demo('Default props', '''
 @doc.demo('Default props', '''
     You can set default props for all elements of a certain class.
     You can set default props for all elements of a certain class.
     This way you can avoid repeating the same props over and over again.
     This way you can avoid repeating the same props over and over again.