From 30bcf66d7e507f2c540987a0900493fbd48f865b Mon Sep 17 00:00:00 2001 From: Niklas Neugebauer <68709968+NiklasNeugebauer@users.noreply.github.com> Date: Tue, 9 Jul 2024 07:26:21 +0200 Subject: [PATCH] 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 --- nicegui/element.py | 21 +++++++++--- tests/test_element.py | 33 +++++++++++++++++++ .../content/element_documentation.py | 13 ++++++++ 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/nicegui/element.py b/nicegui/element.py index 783a0e6f0..1ba1edd87 100644 --- a/nicegui/element.py +++ b/nicegui/element.py @@ -498,19 +498,32 @@ def clear(self) -> None: slot.children.clear() 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. :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_slot: slot within the target container (default: default slot) """ assert self.parent_slot is not None self.parent_slot.children.remove(self) self.parent_slot.parent.update() 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() def remove(self, element: Union[Element, int]) -> None: diff --git a/tests/test_element.py b/tests/test_element.py index 0c41d9861..35117b81b 100644 --- a/tests/test_element.py +++ b/tests/test_element.py @@ -134,17 +134,50 @@ def test_move(screen: Screen): screen.open('/') assert screen.find('A').location['y'] < screen.find('X').location['y'] < screen.find('B').location['y'] + screen.click('Move X to B') screen.wait(0.5) assert screen.find('A').location['y'] < screen.find('B').location['y'] < screen.find('X').location['y'] + screen.click('Move X to A') screen.wait(0.5) assert screen.find('A').location['y'] < screen.find('X').location['y'] < screen.find('B').location['y'] + screen.click('Move X to top') screen.wait(0.5) 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): ui.label('') ui.label('Bold 1, `code`, copy&paste, multi\nline') diff --git a/website/documentation/content/element_documentation.py b/website/documentation/content/element_documentation.py index 331626d77..933456cb5 100644 --- a/website/documentation/content/element_documentation.py +++ b/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)) +@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', ''' 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.