Bläddra i källkod

Introduce `ui.teleport` element (#3159)

* fix:add finished event

* fix tailwind classes problem

* code review

* fix pytest

* can run

* testing

* code format

* docs

* documentation

* code review

* fix disappear on auto-index page

* support UI element

* move teleport documentation to page layout section

* use existing update() method to force-update the teleport element

---------

Co-authored-by: Falko Schindler <falko@zauberzeug.com>
CrystalWindSnake 11 månader sedan
förälder
incheckning
c30752af79

+ 20 - 0
nicegui/elements/teleport.js

@@ -0,0 +1,20 @@
+export default {
+  template: `<Teleport v-if="isLoaded" :to="to" :key="key"><slot></slot></Teleport>`,
+  props: {
+    to: String,
+  },
+  mounted() {
+    this.isLoaded = true;
+  },
+  data() {
+    return {
+      key: 0,
+      isLoaded: false,
+    };
+  },
+  methods: {
+    update() {
+      this.key++;
+    },
+  },
+};

+ 26 - 0
nicegui/elements/teleport.py

@@ -0,0 +1,26 @@
+from typing import Union
+
+from nicegui.element import Element
+
+
+class Teleport(Element, component='teleport.js'):
+
+    def __init__(self, to: Union[str, Element]) -> None:
+        """Teleport
+
+        An element that allows us to transmit the content from within a component to any location on the page.
+
+        :param to: NiceGUI element or CSS selector of the target element for the teleported content
+        """
+        super().__init__()
+        if isinstance(to, Element):
+            to = f'#c{to.id}'
+        self._props['to'] = to
+
+    def update(self) -> None:
+        """Force the internal content to be retransmitted to the specified location.
+
+        This method is usually called after the target container is rebuilt.
+        """
+        super().update()
+        self.run_method('update')

+ 4 - 0
nicegui/testing/screen.py

@@ -219,6 +219,10 @@ class Screen:
         """Find all elements with the given HTML tag."""
         """Find all elements with the given HTML tag."""
         return self.selenium.find_elements(By.TAG_NAME, name)
         return self.selenium.find_elements(By.TAG_NAME, name)
 
 
+    def find_by_css(self, selector: str) -> WebElement:
+        """Find the element with the given CSS selector."""
+        return self.selenium.find_element(By.CSS_SELECTOR, selector)
+
     def render_js_logs(self) -> str:
     def render_js_logs(self) -> str:
         """Render the browser console logs as a string."""
         """Render the browser console logs as a string."""
         console = '\n'.join(log['message'] for log in self.selenium.get_log('browser'))
         console = '\n'.join(log['message'] for log in self.selenium.get_log('browser'))

+ 2 - 0
nicegui/ui.py

@@ -88,6 +88,7 @@ __all__ = [
     'tab_panel',
     'tab_panel',
     'tab_panels',
     'tab_panels',
     'tabs',
     'tabs',
+    'teleport',
     'textarea',
     'textarea',
     'time',
     'time',
     'timer',
     'timer',
@@ -212,6 +213,7 @@ from .elements.tabs import Tab as tab
 from .elements.tabs import TabPanel as tab_panel
 from .elements.tabs import TabPanel as tab_panel
 from .elements.tabs import TabPanels as tab_panels
 from .elements.tabs import TabPanels as tab_panels
 from .elements.tabs import Tabs as tabs
 from .elements.tabs import Tabs as tabs
+from .elements.teleport import Teleport as teleport
 from .elements.textarea import Textarea as textarea
 from .elements.textarea import Textarea as textarea
 from .elements.time import Time as time
 from .elements.time import Time as time
 from .elements.timeline import Timeline as timeline
 from .elements.timeline import Timeline as timeline

+ 58 - 0
tests/test_teleport.py

@@ -0,0 +1,58 @@
+from typing import Optional
+
+from nicegui import ui
+from nicegui.testing import Screen
+
+
+def test_teleport(screen: Screen):
+    ui.card().classes('card')
+
+    def create_teleport():
+        with ui.teleport('.card'):
+            ui.label('Hello')
+
+    ui.button('create', on_click=create_teleport)
+
+    screen.open('/')
+    screen.click('create')
+    assert screen.find_by_css('.card > div').text == 'Hello'
+
+
+def test_teleport_with_element(screen: Screen):
+    card = ui.card().classes('card')
+
+    def create_teleport():
+        with ui.teleport(card):
+            ui.label('Hello')
+
+    ui.button('create', on_click=create_teleport)
+
+    screen.open('/')
+    screen.click('create')
+    assert screen.find_by_css('.card > div').text == 'Hello'
+
+
+def test_update(screen: Screen):
+    teleport: Optional[ui.teleport] = None
+
+    card = ui.card().classes('card')
+
+    def create_teleport():
+        nonlocal teleport
+        with ui.teleport('.card') as teleport:
+            ui.label('Hello')
+
+    ui.button('create', on_click=create_teleport)
+
+    def rebuild_card():
+        card.delete()
+        ui.card().classes('card')
+        teleport.update()  # type: ignore
+
+    ui.button('rebuild card', on_click=rebuild_card)
+
+    screen.open('/')
+    screen.click('create')
+    screen.should_contain('Hello')
+    screen.click('rebuild card')
+    assert screen.find_by_css('.card > div').text == 'Hello'

+ 2 - 0
website/documentation/content/section_page_layout.py

@@ -21,6 +21,7 @@ from . import (
     splitter_documentation,
     splitter_documentation,
     stepper_documentation,
     stepper_documentation,
     tabs_documentation,
     tabs_documentation,
+    teleport_documentation,
     timeline_documentation,
     timeline_documentation,
     tooltip_documentation,
     tooltip_documentation,
 )
 )
@@ -80,6 +81,7 @@ def clear_containers_demo():
     ui.button('Clear', on_click=container.clear)
     ui.button('Clear', on_click=container.clear)
 
 
 
 
+doc.intro(teleport_documentation)
 doc.intro(expansion_documentation)
 doc.intro(expansion_documentation)
 doc.intro(scroll_area_documentation)
 doc.intro(scroll_area_documentation)
 doc.intro(separator_documentation)
 doc.intro(separator_documentation)

+ 17 - 0
website/documentation/content/teleport_documentation.py

@@ -0,0 +1,17 @@
+from nicegui import ui
+
+from . import doc
+
+
+@doc.demo(ui.teleport)
+def main_demo() -> None:
+    markdown = ui.markdown('Enter your **name**!')
+
+    def inject_input():
+        with ui.teleport(f'#c{markdown.id} strong'):
+            ui.input('name').classes('inline-flex').props('dense outlined')
+
+    ui.button('inject input', on_click=inject_input)
+
+
+doc.reference(ui.teleport)