Browse Source

Merge commit '4c5745b1f9c46ef8a40dfa98dd278e8fcc1185dc' into on-air

Rodja Trappe 1 year ago
parent
commit
cb1eff86fa

+ 2 - 7
examples/custom_vue_component/counter.py

@@ -1,19 +1,14 @@
-from pathlib import Path
 from typing import Callable, Optional
 from typing import Callable, Optional
 
 
-from nicegui.dependencies import register_vue_component
 from nicegui.element import Element
 from nicegui.element import Element
 
 
-component = register_vue_component(Path('counter.js'), base_path=Path(__file__).parent)
 
 
-
-class Counter(Element):
+class Counter(Element, component='counter.js'):
 
 
     def __init__(self, title: str, *, on_change: Optional[Callable] = None) -> None:
     def __init__(self, title: str, *, on_change: Optional[Callable] = None) -> None:
-        super().__init__(component.tag)
+        super().__init__()
         self._props['title'] = title
         self._props['title'] = title
         self.on('change', on_change)
         self.on('change', on_change)
-        self.use_component(component)
 
 
     def reset(self) -> None:
     def reset(self) -> None:
         self.run_method('reset')
         self.run_method('reset')

+ 2 - 8
examples/map/leaflet.py

@@ -1,18 +1,12 @@
-from pathlib import Path
 from typing import Tuple
 from typing import Tuple
 
 
 from nicegui import ui
 from nicegui import ui
-from nicegui.dependencies import register_vue_component
-from nicegui.element import Element
 
 
-component = register_vue_component(Path('leaflet.js'), base_path=Path(__file__).parent)
 
 
-
-class leaflet(Element):
+class leaflet(ui.element, component='leaflet.js'):
 
 
     def __init__(self) -> None:
     def __init__(self) -> None:
-        super().__init__(component.tag)
-        self.use_component(component)
+        super().__init__()
         ui.add_head_html('<link href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" rel="stylesheet"/>')
         ui.add_head_html('<link href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css" rel="stylesheet"/>')
         ui.add_head_html('<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"></script>')
         ui.add_head_html('<script src="https://unpkg.com/leaflet@1.6.0/dist/leaflet.js"></script>')
 
 

+ 4 - 6
examples/single_page_app/router.py

@@ -1,10 +1,10 @@
-from pathlib import Path
 from typing import Awaitable, Callable, Dict, Union
 from typing import Awaitable, Callable, Dict, Union
 
 
 from nicegui import background_tasks, ui
 from nicegui import background_tasks, ui
-from nicegui.dependencies import register_vue_component
 
 
-component = register_vue_component(Path('router_frame.js'), base_path=Path(__file__).parent)
+
+class RouterFrame(ui.element, component='router_frame.js'):
+    pass
 
 
 
 
 class Router():
 class Router():
@@ -41,7 +41,5 @@ class Router():
         background_tasks.create(build())
         background_tasks.create(build())
 
 
     def frame(self) -> ui.element:
     def frame(self) -> ui.element:
-        self.content = ui.element(component.tag) \
-            .on('open', lambda e: self.open(e.args)) \
-            .use_component(component)
+        self.content = RouterFrame().on('open', lambda e: self.open(e.args))
         return self.content
         return self.content

+ 12 - 6
nicegui/dependencies.py

@@ -1,13 +1,17 @@
+from __future__ import annotations
+
 from dataclasses import dataclass
 from dataclasses import dataclass
 from pathlib import Path
 from pathlib import Path
-from typing import Dict, List, Set, Tuple
+from typing import TYPE_CHECKING, Dict, List, Set, Tuple
 
 
 import vbuild
 import vbuild
 
 
 from . import __version__
 from . import __version__
-from .element import Element
 from .helpers import KWONLY_SLOTS
 from .helpers import KWONLY_SLOTS
 
 
+if TYPE_CHECKING:
+    from .element import Element
+
 
 
 @dataclass(**KWONLY_SLOTS)
 @dataclass(**KWONLY_SLOTS)
 class Component:
 class Component:
@@ -53,7 +57,7 @@ def register_vue_component(location: Path, base_path: Path = Path(__file__).pare
 
 
     :param location: location to the library relative to the base_path (used as the resource identifier, must be URL-safe)
     :param location: location to the library relative to the base_path (used as the resource identifier, must be URL-safe)
     :param base_path: base path where your libraries are located
     :param base_path: base path where your libraries are located
-    :return: resource identifier to be used in element's `use_component`
+    :return: registered component
     """
     """
     path, key, name, suffix = deconstruct_location(location, base_path)
     path, key, name, suffix = deconstruct_location(location, base_path)
     if suffix == '.vue':
     if suffix == '.vue':
@@ -75,7 +79,7 @@ def register_library(location: Path, base_path: Path = Path(__file__).parent / '
     :param location: location to the library relative to the base_path (used as the resource identifier, must be URL-safe)
     :param location: location to the library relative to the base_path (used as the resource identifier, must be URL-safe)
     :param base_path: base path where your libraries are located
     :param base_path: base path where your libraries are located
     :param expose: whether to expose library as an ESM module (exposed modules will NOT be imported)
     :param expose: whether to expose library as an ESM module (exposed modules will NOT be imported)
-    :return: resource identifier to be used in element's `use_library`
+    :return: registered library
     """
     """
     path, key, name, suffix = deconstruct_location(location, base_path)
     path, key, name, suffix = deconstruct_location(location, base_path)
     if suffix in {'.js', '.mjs'}:
     if suffix in {'.js', '.mjs'}:
@@ -87,7 +91,9 @@ def register_library(location: Path, base_path: Path = Path(__file__).parent / '
 
 
 def deconstruct_location(location: Path, base_path: Path) -> Tuple[Path, str, str, str]:
 def deconstruct_location(location: Path, base_path: Path) -> Tuple[Path, str, str, str]:
     """Deconstruct a location into its parts: full path, relative path, name, suffix."""
     """Deconstruct a location into its parts: full path, relative path, name, suffix."""
-    return base_path / location, str(location), location.name.split('.', 1)[0], location.suffix.lower()
+    abs_path = location if location.is_absolute() else base_path / location
+    rel_path = location if not location.is_absolute() else location.relative_to(base_path)
+    return abs_path, str(rel_path), location.name.split('.', 1)[0], location.suffix.lower()
 
 
 
 
 def generate_resources(prefix: str, elements: List[Element]) -> Tuple[List[str],
 def generate_resources(prefix: str, elements: List[Element]) -> Tuple[List[str],
@@ -126,7 +132,7 @@ def generate_resources(prefix: str, elements: List[Element]) -> Tuple[List[str],
                     js_imports.append(f'import "{prefix}/_nicegui/{__version__}/libraries/{library.key}";')
                     js_imports.append(f'import "{prefix}/_nicegui/{__version__}/libraries/{library.key}";')
                 done_libraries.add(library.key)
                 done_libraries.add(library.key)
         for component in element.components:
         for component in element.components:
-            if component.key not in done_components:
+            if component.key not in done_components and component.path.suffix.lower() == '.js':
                 js_imports.extend([
                 js_imports.extend([
                     f'import {{ default as {component.name} }} from "{prefix}/_nicegui/{__version__}/components/{component.key}";',
                     f'import {{ default as {component.name} }} from "{prefix}/_nicegui/{__version__}/components/{component.key}";',
                     f'app.component("{component.tag}", {component.name});',
                     f'app.component("{component.tag}", {component.name});',

+ 22 - 15
nicegui/element.py

@@ -1,7 +1,9 @@
 from __future__ import annotations
 from __future__ import annotations
 
 
+import inspect
 import re
 import re
 from copy import deepcopy
 from copy import deepcopy
+from pathlib import Path
 from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Optional, Union
 from typing import TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Optional, Union
 
 
 from typing_extensions import Self
 from typing_extensions import Self
@@ -9,6 +11,7 @@ from typing_extensions import Self
 from nicegui import json
 from nicegui import json
 
 
 from . import binding, events, globals, outbox, storage
 from . import binding, events, globals, outbox, storage
+from .dependencies import JsComponent, Library, register_library, register_vue_component
 from .elements.mixins.visibility import Visibility
 from .elements.mixins.visibility import Visibility
 from .event_listener import EventListener
 from .event_listener import EventListener
 from .slot import Slot
 from .slot import Slot
@@ -16,14 +19,17 @@ from .tailwind import Tailwind
 
 
 if TYPE_CHECKING:
 if TYPE_CHECKING:
     from .client import Client
     from .client import Client
-    from .dependencies import JsComponent, Library
 
 
 PROPS_PATTERN = re.compile(r'([:\w\-]+)(?:=(?:("[^"\\]*(?:\\.[^"\\]*)*")|([\w\-.%:\/]+)))?(?:$|\s)')
 PROPS_PATTERN = re.compile(r'([:\w\-]+)(?:=(?:("[^"\\]*(?:\\.[^"\\]*)*")|([\w\-.%:\/]+)))?(?:$|\s)')
 
 
 
 
 class Element(Visibility):
 class Element(Visibility):
+    components: List[JsComponent] = []
+    libraries: List[Library] = []
+    extra_libraries: List[Library] = []
+    exposed_libraries: List[Library] = []
 
 
-    def __init__(self, tag: str, *, _client: Optional[Client] = None) -> None:
+    def __init__(self, tag: Optional[str] = None, *, _client: Optional[Client] = None) -> None:
         """Generic Element
         """Generic Element
 
 
         This class is the base class for all other UI elements.
         This class is the base class for all other UI elements.
@@ -36,14 +42,12 @@ class Element(Visibility):
         self.client = _client or globals.get_client()
         self.client = _client or globals.get_client()
         self.id = self.client.next_element_id
         self.id = self.client.next_element_id
         self.client.next_element_id += 1
         self.client.next_element_id += 1
-        self.tag = tag
+        self.tag = tag if tag else self.components[0].tag if self.components else 'div'
         self._classes: List[str] = []
         self._classes: List[str] = []
         self._style: Dict[str, str] = {}
         self._style: Dict[str, str] = {}
         self._props: Dict[str, Any] = {'key': self.id}  # HACK: workaround for #600 and #898
         self._props: Dict[str, Any] = {'key': self.id}  # HACK: workaround for #600 and #898
         self._event_listeners: Dict[str, EventListener] = {}
         self._event_listeners: Dict[str, EventListener] = {}
         self._text: Optional[str] = None
         self._text: Optional[str] = None
-        self.components: List[JsComponent] = []
-        self.libraries: List[Library] = []
         self.slots: Dict[str, Slot] = {}
         self.slots: Dict[str, Slot] = {}
         self.default_slot = self.add_slot('default')
         self.default_slot = self.add_slot('default')
 
 
@@ -60,6 +64,19 @@ class Element(Visibility):
         if self.parent_slot:
         if self.parent_slot:
             outbox.enqueue_update(self.parent_slot.parent)
             outbox.enqueue_update(self.parent_slot.parent)
 
 
+    def __init_subclass__(cls, *,
+                          component: Union[str, Path, None] = None,
+                          libraries: List[Union[str, Path]] = [],
+                          exposed_libraries: List[Union[str, Path]] = [],
+                          extra_libraries: List[Union[str, Path]] = [],
+                          ) -> None:
+        super().__init_subclass__()
+        base = Path(inspect.getfile(cls)).parent
+        cls.components = [register_vue_component(Path(component), base)] if component else []
+        cls.libraries = [register_library(Path(library), base) for library in libraries]
+        cls.extra_libraries = [register_library(Path(library), base) for library in extra_libraries]
+        cls.exposed_libraries = [register_library(Path(library), base, expose=True) for library in exposed_libraries]
+
     def add_slot(self, name: str, template: Optional[str] = None) -> Slot:
     def add_slot(self, name: str, template: Optional[str] = None) -> Slot:
         """Add a slot to the element.
         """Add a slot to the element.
 
 
@@ -307,13 +324,3 @@ class Element(Visibility):
 
 
         Can be overridden to perform cleanup.
         Can be overridden to perform cleanup.
         """
         """
-
-    def use_component(self, component: JsComponent) -> Self:
-        """Register a ``*.js`` Vue component to be used by this element."""
-        self.components.append(component)
-        return self
-
-    def use_library(self, library: Library) -> Self:
-        """Register a JavaScript library to be used by this element."""
-        self.libraries.append(library)
-        return self

+ 2 - 9
nicegui/elements/aggrid.py

@@ -1,17 +1,12 @@
 from __future__ import annotations
 from __future__ import annotations
 
 
-from pathlib import Path
 from typing import Dict, List, Optional, cast
 from typing import Dict, List, Optional, cast
 
 
-from ..dependencies import register_library, register_vue_component
 from ..element import Element
 from ..element import Element
 from ..functions.javascript import run_javascript
 from ..functions.javascript import run_javascript
 
 
-component = register_vue_component(Path('aggrid.js'))
-library = register_library(Path('aggrid', 'ag-grid-community.min.js'))
 
 
-
-class AgGrid(Element):
+class AgGrid(Element, component='aggrid.js', libraries=['lib/aggrid/ag-grid-community.min.js']):
 
 
     def __init__(self, options: Dict, *, html_columns: List[int] = [], theme: str = 'balham') -> None:
     def __init__(self, options: Dict, *, html_columns: List[int] = [], theme: str = 'balham') -> None:
         """AG Grid
         """AG Grid
@@ -24,12 +19,10 @@ class AgGrid(Element):
         :param html_columns: list of columns that should be rendered as HTML (default: `[]`)
         :param html_columns: list of columns that should be rendered as HTML (default: `[]`)
         :param theme: AG Grid theme (default: 'balham')
         :param theme: AG Grid theme (default: 'balham')
         """
         """
-        super().__init__(component.tag)
+        super().__init__()
         self._props['options'] = options
         self._props['options'] = options
         self._props['html_columns'] = html_columns
         self._props['html_columns'] = html_columns
         self._classes = ['nicegui-aggrid', f'ag-theme-{theme}']
         self._classes = ['nicegui-aggrid', f'ag-theme-{theme}']
-        self.use_component(component)
-        self.use_library(library)
 
 
     @staticmethod
     @staticmethod
     def from_pandas(df: 'pandas.DataFrame', *, theme: str = 'balham') -> AgGrid:
     def from_pandas(df: 'pandas.DataFrame', *, theme: str = 'balham') -> AgGrid:

+ 2 - 6
nicegui/elements/audio.py

@@ -3,13 +3,10 @@ from pathlib import Path
 from typing import Union
 from typing import Union
 
 
 from .. import globals
 from .. import globals
-from ..dependencies import register_vue_component
 from ..element import Element
 from ..element import Element
 
 
-component = register_vue_component(Path('audio.js'))
 
 
-
-class Audio(Element):
+class Audio(Element, component='audio.js'):
 
 
     def __init__(self, src: Union[str, Path], *,
     def __init__(self, src: Union[str, Path], *,
                  controls: bool = True,
                  controls: bool = True,
@@ -29,7 +26,7 @@ class Audio(Element):
         See `here <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio#events>`_
         See `here <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/audio#events>`_
         for a list of events you can subscribe to using the generic event subscription `on()`.
         for a list of events you can subscribe to using the generic event subscription `on()`.
         """
         """
-        super().__init__(component.tag)
+        super().__init__()
         if Path(src).is_file():
         if Path(src).is_file():
             src = globals.app.add_media_file(local_file=src)
             src = globals.app.add_media_file(local_file=src)
         self._props['src'] = src
         self._props['src'] = src
@@ -37,7 +34,6 @@ class Audio(Element):
         self._props['autoplay'] = autoplay
         self._props['autoplay'] = autoplay
         self._props['muted'] = muted
         self._props['muted'] = muted
         self._props['loop'] = loop
         self._props['loop'] = loop
-        self.use_component(component)
 
 
         if type:
         if type:
             url = f'https://github.com/zauberzeug/nicegui/pull/624'
             url = f'https://github.com/zauberzeug/nicegui/pull/624'

+ 6 - 16
nicegui/elements/chart.py

@@ -1,20 +1,14 @@
 from pathlib import Path
 from pathlib import Path
 from typing import Dict, List
 from typing import Dict, List
 
 
-from ..dependencies import register_library, register_vue_component
 from ..element import Element
 from ..element import Element
 
 
-component = register_vue_component(Path('chart.js'))
+base = Path(__file__).parent
+libraries = [p.relative_to(base) for p in sorted((base / 'lib' / 'highcharts').glob('*.js'), key=lambda p: p.stem)]
+modules = {p.stem: p.relative_to(base) for p in sorted((base / 'lib' / 'highcharts' / 'modules').glob('*.js'))}
 
 
-core_dependencies: List[Path] = []
-base = Path(__file__).parent / 'lib'
-for path in sorted((base / 'highcharts').glob('*.js'), key=lambda p: p.stem):
-    core_dependencies.append(register_library(path.relative_to(base)))
-for path in sorted((base / 'highcharts' / 'modules').glob('*.js'), key=lambda p: p.stem):
-    register_library(path.relative_to(base))
 
 
-
-class Chart(Element):
+class Chart(Element, component='chart.js', libraries=libraries, extra_libraries=list(modules.values())):
 
 
     def __init__(self, options: Dict, *, type: str = 'chart', extras: List[str] = []) -> None:
     def __init__(self, options: Dict, *, type: str = 'chart', extras: List[str] = []) -> None:
         """Chart
         """Chart
@@ -30,15 +24,11 @@ class Chart(Element):
         :param type: chart type (e.g. "chart", "stockChart", "mapChart", ...; default: "chart")
         :param type: chart type (e.g. "chart", "stockChart", "mapChart", ...; default: "chart")
         :param extras: list of extra dependencies to include (e.g. "annotations", "arc-diagram", "solid-gauge", ...)
         :param extras: list of extra dependencies to include (e.g. "annotations", "arc-diagram", "solid-gauge", ...)
         """
         """
-        super().__init__(component.tag)
+        super().__init__()
         self._props['type'] = type
         self._props['type'] = type
         self._props['options'] = options
         self._props['options'] = options
         self._props['extras'] = extras
         self._props['extras'] = extras
-        self.use_component(component)
-        for dependency in core_dependencies:
-            self.use_library(dependency)
-        for extra in extras:
-            self.use_library(f'highcharts/modules/{extra}.js')
+        self.libraries.extend(library for library in self.extra_libraries if library.path.stem in extras)
 
 
     @property
     @property
     def options(self) -> Dict:
     def options(self) -> Dict:

+ 2 - 7
nicegui/elements/chat_message.py

@@ -1,14 +1,10 @@
 import html
 import html
-from pathlib import Path
 from typing import List, Optional, Union
 from typing import List, Optional, Union
 
 
-from ..dependencies import register_vue_component
 from ..element import Element
 from ..element import Element
 
 
-component = register_vue_component(Path('chat_message.js'))
 
 
-
-class ChatMessage(Element):
+class ChatMessage(Element, component='chat_message.js'):
 
 
     def __init__(self,
     def __init__(self,
                  text: Union[str, List[str]], *,
                  text: Union[str, List[str]], *,
@@ -31,8 +27,7 @@ class ChatMessage(Element):
         :param sent: render as a sent message (so from current user) (default: False)
         :param sent: render as a sent message (so from current user) (default: False)
         :param text_html: render text as HTML (default: False)
         :param text_html: render text as HTML (default: False)
         """
         """
-        super().__init__(component.tag)
-        self.use_component(component)
+        super().__init__()
 
 
         if isinstance(text, str):
         if isinstance(text, str):
             text = [text]
             text = [text]

+ 1 - 1
nicegui/elements/choice_element.py

@@ -6,7 +6,7 @@ from .mixins.value_element import ValueElement
 class ChoiceElement(ValueElement):
 class ChoiceElement(ValueElement):
 
 
     def __init__(self, *,
     def __init__(self, *,
-                 tag: str,
+                 tag: Optional[str] = None,
                  options: Union[List, Dict],
                  options: Union[List, Dict],
                  value: Any,
                  value: Any,
                  on_change: Optional[Callable[..., Any]] = None,
                  on_change: Optional[Callable[..., Any]] = None,

+ 2 - 8
nicegui/elements/colors.py

@@ -1,12 +1,7 @@
-from pathlib import Path
-
-from ..dependencies import register_vue_component
 from ..element import Element
 from ..element import Element
 
 
-component = register_vue_component(Path('colors.js'))
-
 
 
-class Colors(Element):
+class Colors(Element, component='colors.js'):
 
 
     def __init__(self, *,
     def __init__(self, *,
                  primary='#5898d4',
                  primary='#5898d4',
@@ -21,8 +16,7 @@ class Colors(Element):
 
 
         Sets the main colors (primary, secondary, accent, ...) used by `Quasar <https://quasar.dev/>`_.
         Sets the main colors (primary, secondary, accent, ...) used by `Quasar <https://quasar.dev/>`_.
         """
         """
-        super().__init__(component.tag)
-        self.use_component(component)
+        super().__init__()
         self._props['primary'] = primary
         self._props['primary'] = primary
         self._props['secondary'] = secondary
         self._props['secondary'] = secondary
         self._props['accent'] = accent
         self._props['accent'] = accent

+ 2 - 7
nicegui/elements/dark_mode.py

@@ -1,13 +1,9 @@
-from pathlib import Path
 from typing import Optional
 from typing import Optional
 
 
-from ..dependencies import register_vue_component
 from .mixins.value_element import ValueElement
 from .mixins.value_element import ValueElement
 
 
-component = register_vue_component(Path('dark_mode.js'))
 
 
-
-class DarkMode(ValueElement):
+class DarkMode(ValueElement, component='dark_mode.js'):
     VALUE_PROP = 'value'
     VALUE_PROP = 'value'
 
 
     def __init__(self, value: Optional[bool] = False) -> None:
     def __init__(self, value: Optional[bool] = False) -> None:
@@ -20,8 +16,7 @@ class DarkMode(ValueElement):
 
 
         :param value: Whether dark mode is enabled. If None, dark mode is set to auto.
         :param value: Whether dark mode is enabled. If None, dark mode is set to auto.
         """
         """
-        super().__init__(tag=component.tag, value=value, on_value_change=None)
-        self.use_component(component)
+        super().__init__(value=value, on_value_change=None)
 
 
     def enable(self) -> None:
     def enable(self) -> None:
         """Enable dark mode."""
         """Enable dark mode."""

+ 2 - 7
nicegui/elements/image.py

@@ -1,14 +1,10 @@
 from pathlib import Path
 from pathlib import Path
 from typing import Union
 from typing import Union
 
 
-from nicegui.dependencies import register_vue_component
-
 from .mixins.source_element import SourceElement
 from .mixins.source_element import SourceElement
 
 
-component = register_vue_component(Path('image.js'))
-
 
 
-class Image(SourceElement):
+class Image(SourceElement, component='image.js'):
 
 
     def __init__(self, source: Union[str, Path] = '') -> None:
     def __init__(self, source: Union[str, Path] = '') -> None:
         """Image
         """Image
@@ -17,5 +13,4 @@ class Image(SourceElement):
 
 
         :param source: the source of the image; can be a URL, local file path or a base64 string
         :param source: the source of the image; can be a URL, local file path or a base64 string
         """
         """
-        super().__init__(tag=component.tag, source=source)
-        self.use_component(component)
+        super().__init__(source=source)

+ 2 - 8
nicegui/elements/input.py

@@ -1,15 +1,11 @@
-from pathlib import Path
 from typing import Any, Callable, Dict, List, Optional
 from typing import Any, Callable, Dict, List, Optional
 
 
-from ..dependencies import register_vue_component
 from .icon import Icon
 from .icon import Icon
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.validation_element import ValidationElement
 from .mixins.validation_element import ValidationElement
 
 
-component = register_vue_component(Path('input.js'))
 
 
-
-class Input(ValidationElement, DisableableElement):
+class Input(ValidationElement, DisableableElement, component='input.js'):
     VALUE_PROP: str = 'value'
     VALUE_PROP: str = 'value'
     LOOPBACK = False
     LOOPBACK = False
 
 
@@ -42,7 +38,7 @@ class Input(ValidationElement, DisableableElement):
         :param autocomplete: optional list of strings for autocompletion
         :param autocomplete: optional list of strings for autocompletion
         :param validation: dictionary of validation rules, e.g. ``{'Too long!': lambda value: len(value) < 3}``
         :param validation: dictionary of validation rules, e.g. ``{'Too long!': lambda value: len(value) < 3}``
         """
         """
-        super().__init__(tag=component.tag, value=value, on_value_change=on_change, validation=validation)
+        super().__init__(value=value, on_value_change=on_change, validation=validation)
         if label is not None:
         if label is not None:
             self._props['label'] = label
             self._props['label'] = label
         if placeholder is not None:
         if placeholder is not None:
@@ -59,8 +55,6 @@ class Input(ValidationElement, DisableableElement):
 
 
         self._props['autocomplete'] = autocomplete or []
         self._props['autocomplete'] = autocomplete or []
 
 
-        self.use_component(component)
-
     def set_autocomplete(self, autocomplete: Optional[List[str]]) -> None:
     def set_autocomplete(self, autocomplete: Optional[List[str]]) -> None:
         """Set the autocomplete list."""
         """Set the autocomplete list."""
         self._props['autocomplete'] = autocomplete
         self._props['autocomplete'] = autocomplete

+ 2 - 6
nicegui/elements/interactive_image.py

@@ -3,15 +3,12 @@ from __future__ import annotations
 from pathlib import Path
 from pathlib import Path
 from typing import Any, Callable, List, Optional, Union
 from typing import Any, Callable, List, Optional, Union
 
 
-from ..dependencies import register_vue_component
 from ..events import GenericEventArguments, MouseEventArguments, handle_event
 from ..events import GenericEventArguments, MouseEventArguments, handle_event
 from .mixins.content_element import ContentElement
 from .mixins.content_element import ContentElement
 from .mixins.source_element import SourceElement
 from .mixins.source_element import SourceElement
 
 
-component = register_vue_component(Path('interactive_image.js'))
 
 
-
-class InteractiveImage(SourceElement, ContentElement):
+class InteractiveImage(SourceElement, ContentElement, component='interactive_image.js'):
     CONTENT_PROP = 'content'
     CONTENT_PROP = 'content'
 
 
     def __init__(self,
     def __init__(self,
@@ -35,10 +32,9 @@ class InteractiveImage(SourceElement, ContentElement):
         :param events: list of JavaScript events to subscribe to (default: `['click']`)
         :param events: list of JavaScript events to subscribe to (default: `['click']`)
         :param cross: whether to show crosshairs (default: `False`)
         :param cross: whether to show crosshairs (default: `False`)
         """
         """
-        super().__init__(tag=component.tag, source=source, content=content)
+        super().__init__(source=source, content=content)
         self._props['events'] = events
         self._props['events'] = events
         self._props['cross'] = cross
         self._props['cross'] = cross
-        self.use_component(component)
 
 
         def handle_mouse(e: GenericEventArguments) -> None:
         def handle_mouse(e: GenericEventArguments) -> None:
             if on_mouse is None:
             if on_mouse is None:

+ 2 - 8
nicegui/elements/joystick.py

@@ -1,15 +1,10 @@
-from pathlib import Path
 from typing import Any, Callable, Optional
 from typing import Any, Callable, Optional
 
 
-from ..dependencies import register_library, register_vue_component
 from ..element import Element
 from ..element import Element
 from ..events import GenericEventArguments, JoystickEventArguments, handle_event
 from ..events import GenericEventArguments, JoystickEventArguments, handle_event
 
 
-component = register_vue_component(Path('joystick.vue'))
-library = register_library(Path('nipplejs', 'nipplejs.js'))
 
 
-
-class Joystick(Element):
+class Joystick(Element, component='joystick.vue', libraries=['lib/nipplejs/nipplejs.js']):
 
 
     def __init__(self, *,
     def __init__(self, *,
                  on_start: Optional[Callable[..., Any]] = None,
                  on_start: Optional[Callable[..., Any]] = None,
@@ -27,8 +22,7 @@ class Joystick(Element):
         :param throttle: throttle interval in seconds for the move event (default: 0.05)
         :param throttle: throttle interval in seconds for the move event (default: 0.05)
         :param options: arguments like `color` which should be passed to the `underlying nipple.js library <https://github.com/yoannmoinet/nipplejs#options>`_
         :param options: arguments like `color` which should be passed to the `underlying nipple.js library <https://github.com/yoannmoinet/nipplejs#options>`_
         """
         """
-        super().__init__('nicegui-joystick')
-        self.use_library(library)
+        super().__init__()
         self._props['options'] = options
         self._props['options'] = options
         self.active = False
         self.active = False
 
 

+ 2 - 7
nicegui/elements/keyboard.py

@@ -1,18 +1,14 @@
-from pathlib import Path
 from typing import Any, Callable, List
 from typing import Any, Callable, List
 
 
 from typing_extensions import Literal
 from typing_extensions import Literal
 
 
 from ..binding import BindableProperty
 from ..binding import BindableProperty
-from ..dependencies import register_vue_component
 from ..element import Element
 from ..element import Element
 from ..events import (GenericEventArguments, KeyboardAction, KeyboardKey, KeyboardModifiers, KeyEventArguments,
 from ..events import (GenericEventArguments, KeyboardAction, KeyboardKey, KeyboardModifiers, KeyEventArguments,
                       handle_event)
                       handle_event)
 
 
-component = register_vue_component(Path('keyboard.js'))
 
 
-
-class Keyboard(Element):
+class Keyboard(Element, component='keyboard.js'):
     active = BindableProperty()
     active = BindableProperty()
 
 
     def __init__(self,
     def __init__(self,
@@ -30,14 +26,13 @@ class Keyboard(Element):
         :param repeating: boolean flag indicating whether held keys should be sent repeatedly (default: `True`)
         :param repeating: boolean flag indicating whether held keys should be sent repeatedly (default: `True`)
         :param ignore: ignore keys when one of these element types is focussed (default: `['input', 'select', 'button', 'textarea']`)
         :param ignore: ignore keys when one of these element types is focussed (default: `['input', 'select', 'button', 'textarea']`)
         """
         """
-        super().__init__(component.tag)
+        super().__init__()
         self.key_handler = on_key
         self.key_handler = on_key
         self.active = active
         self.active = active
         self._props['events'] = ['keydown', 'keyup']
         self._props['events'] = ['keydown', 'keyup']
         self._props['repeating'] = repeating
         self._props['repeating'] = repeating
         self._props['ignore'] = ignore
         self._props['ignore'] = ignore
         self.on('key', self.handle_key)
         self.on('key', self.handle_key)
-        self.use_component(component)
 
 
     def handle_key(self, e: GenericEventArguments) -> None:
     def handle_key(self, e: GenericEventArguments) -> None:
         if not self.active:
         if not self.active:

+ 2 - 7
nicegui/elements/link.py

@@ -1,15 +1,11 @@
-from pathlib import Path
 from typing import Any, Callable, Union
 from typing import Any, Callable, Union
 
 
 from .. import globals
 from .. import globals
-from ..dependencies import register_vue_component
 from ..element import Element
 from ..element import Element
 from .mixins.text_element import TextElement
 from .mixins.text_element import TextElement
 
 
-component = register_vue_component(Path('link.js'))
 
 
-
-class Link(TextElement):
+class Link(TextElement, component='link.js'):
 
 
     def __init__(self,
     def __init__(self,
                  text: str = '',
                  text: str = '',
@@ -27,7 +23,7 @@ class Link(TextElement):
         :param target: page function, NiceGUI element on the same page or string that is a an absolute URL or relative path from base URL
         :param target: page function, NiceGUI element on the same page or string that is a an absolute URL or relative path from base URL
         :param new_tab: open link in new tab (default: False)
         :param new_tab: open link in new tab (default: False)
         """
         """
-        super().__init__(tag=component.tag, text=text)
+        super().__init__(text=text)
         if isinstance(target, str):
         if isinstance(target, str):
             self._props['href'] = target
             self._props['href'] = target
         elif isinstance(target, Element):
         elif isinstance(target, Element):
@@ -36,7 +32,6 @@ class Link(TextElement):
             self._props['href'] = globals.page_routes[target]
             self._props['href'] = globals.page_routes[target]
         self._props['target'] = '_blank' if new_tab else '_self'
         self._props['target'] = '_blank' if new_tab else '_self'
         self._classes = ['nicegui-link']
         self._classes = ['nicegui-link']
-        self.use_component(component)
 
 
 
 
 class LinkTarget(Element):
 class LinkTarget(Element):

+ 2 - 7
nicegui/elements/log.py

@@ -1,15 +1,11 @@
 import urllib.parse
 import urllib.parse
 from collections import deque
 from collections import deque
-from pathlib import Path
 from typing import Any, Optional
 from typing import Any, Optional
 
 
-from ..dependencies import register_vue_component
 from ..element import Element
 from ..element import Element
 
 
-component = register_vue_component(Path('log.js'))
 
 
-
-class Log(Element):
+class Log(Element, component='log.js'):
 
 
     def __init__(self, max_lines: Optional[int] = None) -> None:
     def __init__(self, max_lines: Optional[int] = None) -> None:
         """Log view
         """Log view
@@ -18,12 +14,11 @@ class Log(Element):
 
 
         :param max_lines: maximum number of lines before dropping oldest ones (default: `None`)
         :param max_lines: maximum number of lines before dropping oldest ones (default: `None`)
         """
         """
-        super().__init__(component.tag)
+        super().__init__()
         self._props['max_lines'] = max_lines
         self._props['max_lines'] = max_lines
         self._props['lines'] = ''
         self._props['lines'] = ''
         self._classes = ['nicegui-log']
         self._classes = ['nicegui-log']
         self.lines: deque[str] = deque(maxlen=max_lines)
         self.lines: deque[str] = deque(maxlen=max_lines)
-        self.use_component(component)
         self.total_count: int = 0
         self.total_count: int = 0
 
 
     def push(self, line: Any) -> None:
     def push(self, line: Any) -> None:

+ 4 - 9
nicegui/elements/markdown.py

@@ -1,20 +1,16 @@
 import os
 import os
 import re
 import re
 from functools import lru_cache
 from functools import lru_cache
-from pathlib import Path
 from typing import List
 from typing import List
 
 
 import markdown2
 import markdown2
 from pygments.formatters import HtmlFormatter
 from pygments.formatters import HtmlFormatter
 
 
-from ..dependencies import register_vue_component
-from .mermaid import library as mermaid_library
+from .mermaid import Mermaid
 from .mixins.content_element import ContentElement
 from .mixins.content_element import ContentElement
 
 
-component = register_vue_component(Path('markdown.js'))
 
 
-
-class Markdown(ContentElement):
+class Markdown(ContentElement, component='markdown.js'):
 
 
     def __init__(self, content: str = '', *, extras: List[str] = ['fenced-code-blocks', 'tables']) -> None:
     def __init__(self, content: str = '', *, extras: List[str] = ['fenced-code-blocks', 'tables']) -> None:
         """Markdown Element
         """Markdown Element
@@ -25,13 +21,12 @@ class Markdown(ContentElement):
         :param extras: list of `markdown2 extensions <https://github.com/trentm/python-markdown2/wiki/Extras#implemented-extras>`_ (default: `['fenced-code-blocks', 'tables']`)
         :param extras: list of `markdown2 extensions <https://github.com/trentm/python-markdown2/wiki/Extras#implemented-extras>`_ (default: `['fenced-code-blocks', 'tables']`)
         """
         """
         self.extras = extras
         self.extras = extras
-        super().__init__(tag=component.tag, content=content)
+        super().__init__(content=content)
         self._classes = ['nicegui-markdown']
         self._classes = ['nicegui-markdown']
         self._props['codehilite_css'] = HtmlFormatter(nobackground=True).get_style_defs('.codehilite')
         self._props['codehilite_css'] = HtmlFormatter(nobackground=True).get_style_defs('.codehilite')
-        self.use_component(component)
         if 'mermaid' in extras:
         if 'mermaid' in extras:
             self._props['use_mermaid'] = True
             self._props['use_mermaid'] = True
-            self.use_library(mermaid_library)
+            self.libraries.append(Mermaid.exposed_libraries[0])
 
 
     def on_content_change(self, content: str) -> None:
     def on_content_change(self, content: str) -> None:
         html = prepare_content(content, extras=' '.join(self.extras))
         html = prepare_content(content, extras=' '.join(self.extras))

+ 6 - 10
nicegui/elements/mermaid.py

@@ -1,16 +1,14 @@
 from pathlib import Path
 from pathlib import Path
 
 
-from ..dependencies import register_library, register_vue_component
 from .mixins.content_element import ContentElement
 from .mixins.content_element import ContentElement
 
 
-component = register_vue_component(Path('mermaid.js'))
-library = register_library(Path('mermaid', 'mermaid.esm.min.mjs'), expose=True)
-extras_path = Path(__file__).parent / 'lib' / 'mermaid'
-for path in extras_path.glob('*.js'):
-    register_library(path.relative_to(extras_path.parent))
+base = Path(__file__).parent
 
 
 
 
-class Mermaid(ContentElement):
+class Mermaid(ContentElement,
+              component='mermaid.js',
+              exposed_libraries=['lib/mermaid/mermaid.esm.min.mjs'],
+              extra_libraries=[p.relative_to(base) for p in (base / 'lib' / 'mermaid').glob('*.js')]):
     CONTENT_PROP = 'content'
     CONTENT_PROP = 'content'
 
 
     def __init__(self, content: str) -> None:
     def __init__(self, content: str) -> None:
@@ -21,9 +19,7 @@ class Mermaid(ContentElement):
 
 
         :param content: the Mermaid content to be displayed
         :param content: the Mermaid content to be displayed
         '''
         '''
-        super().__init__(tag=component.tag, content=content)
-        self.use_component(component)
-        self.use_library(library)
+        super().__init__(content=content)
 
 
     def on_content_change(self, content: str) -> None:
     def on_content_change(self, content: str) -> None:
         self._props[self.CONTENT_PROP] = content.strip()
         self._props[self.CONTENT_PROP] = content.strip()

+ 2 - 8
nicegui/elements/plotly.py

@@ -1,16 +1,11 @@
-from pathlib import Path
 from typing import Dict, Union
 from typing import Dict, Union
 
 
 import plotly.graph_objects as go
 import plotly.graph_objects as go
 
 
-from ..dependencies import register_library, register_vue_component
 from ..element import Element
 from ..element import Element
 
 
-component = register_vue_component(Path('plotly.vue'))
-library = register_library(Path('plotly', 'plotly.min.js'))
 
 
-
-class Plotly(Element):
+class Plotly(Element, component='plotly.vue', libraries=['lib/plotly/plotly.min.js']):
 
 
     def __init__(self, figure: Union[Dict, go.Figure]) -> None:
     def __init__(self, figure: Union[Dict, go.Figure]) -> None:
         """Plotly Element
         """Plotly Element
@@ -27,8 +22,7 @@ class Plotly(Element):
         :param figure: Plotly figure to be rendered. Can be either a `go.Figure` instance, or
         :param figure: Plotly figure to be rendered. Can be either a `go.Figure` instance, or
                        a `dict` object with keys `data`, `layout`, `config` (optional).
                        a `dict` object with keys `data`, `layout`, `config` (optional).
         """
         """
-        super().__init__(component.tag)
-        self.use_library(library)
+        super().__init__()
 
 
         self.figure = figure
         self.figure = figure
         self.update()
         self.update()

+ 2 - 7
nicegui/elements/query.py

@@ -1,24 +1,19 @@
-from pathlib import Path
 from typing import Optional
 from typing import Optional
 
 
 from typing_extensions import Self
 from typing_extensions import Self
 
 
-from ..dependencies import register_vue_component
 from ..element import Element
 from ..element import Element
 from ..globals import get_client
 from ..globals import get_client
 
 
-component = register_vue_component(Path('query.js'))
 
 
-
-class Query(Element):
+class Query(Element, component='query.js'):
 
 
     def __init__(self, selector: str) -> None:
     def __init__(self, selector: str) -> None:
-        super().__init__(component.tag)
+        super().__init__()
         self._props['selector'] = selector
         self._props['selector'] = selector
         self._props['classes'] = []
         self._props['classes'] = []
         self._props['style'] = {}
         self._props['style'] = {}
         self._props['props'] = {}
         self._props['props'] = {}
-        self.use_component(component)
 
 
     def classes(self, add: Optional[str] = None, *, remove: Optional[str] = None, replace: Optional[str] = None) \
     def classes(self, add: Optional[str] = None, *, remove: Optional[str] = None, replace: Optional[str] = None) \
             -> Self:
             -> Self:

+ 11 - 17
nicegui/elements/scene.py

@@ -1,24 +1,12 @@
 from dataclasses import dataclass
 from dataclasses import dataclass
-from pathlib import Path
 from typing import Any, Callable, Dict, List, Optional, Union
 from typing import Any, Callable, Dict, List, Optional, Union
 
 
 from .. import binding, globals
 from .. import binding, globals
-from ..dependencies import register_library, register_vue_component
 from ..element import Element
 from ..element import Element
 from ..events import GenericEventArguments, SceneClickEventArguments, SceneClickHit, handle_event
 from ..events import GenericEventArguments, SceneClickEventArguments, SceneClickHit, handle_event
 from ..helpers import KWONLY_SLOTS
 from ..helpers import KWONLY_SLOTS
 from .scene_object3d import Object3D
 from .scene_object3d import Object3D
 
 
-component = register_vue_component(Path('scene.js'))
-libraries = [
-    register_library(Path('three', 'three.module.js'), expose=True),
-    register_library(Path('three', 'modules', 'CSS2DRenderer.js'), expose=True),
-    register_library(Path('three', 'modules', 'CSS3DRenderer.js'), expose=True),
-    register_library(Path('three', 'modules', 'OrbitControls.js'), expose=True),
-    register_library(Path('three', 'modules', 'STLLoader.js'), expose=True),
-    register_library(Path('tween', 'tween.umd.js')),
-]
-
 
 
 @dataclass(**KWONLY_SLOTS)
 @dataclass(**KWONLY_SLOTS)
 class SceneCamera:
 class SceneCamera:
@@ -38,7 +26,16 @@ class SceneObject:
     id: str = 'scene'
     id: str = 'scene'
 
 
 
 
-class Scene(Element):
+class Scene(Element,
+            component='scene.js',
+            libraries=['lib/tween/tween.umd.js'],
+            exposed_libraries=[
+                'lib/three/three.module.js',
+                'lib/three/modules/CSS2DRenderer.js',
+                'lib/three/modules/CSS3DRenderer.js',
+                'lib/three/modules/OrbitControls.js',
+                'lib/three/modules/STLLoader.js',
+            ]):
     from .scene_objects import Box as box
     from .scene_objects import Box as box
     from .scene_objects import Curve as curve
     from .scene_objects import Curve as curve
     from .scene_objects import Cylinder as cylinder
     from .scene_objects import Cylinder as cylinder
@@ -73,7 +70,7 @@ class Scene(Element):
         :param grid: whether to display a grid
         :param grid: whether to display a grid
         :param on_click: callback to execute when a 3d object is clicked
         :param on_click: callback to execute when a 3d object is clicked
         """
         """
-        super().__init__(component.tag)
+        super().__init__()
         self._props['width'] = width
         self._props['width'] = width
         self._props['height'] = height
         self._props['height'] = height
         self._props['grid'] = grid
         self._props['grid'] = grid
@@ -84,9 +81,6 @@ class Scene(Element):
         self.is_initialized = False
         self.is_initialized = False
         self.on('init', self.handle_init)
         self.on('init', self.handle_init)
         self.on('click3d', self.handle_click)
         self.on('click3d', self.handle_click)
-        self.use_component(component)
-        for library in libraries:
-            self.use_library(library)
 
 
     def handle_init(self, e: GenericEventArguments) -> None:
     def handle_init(self, e: GenericEventArguments) -> None:
         self.is_initialized = True
         self.is_initialized = True

+ 2 - 7
nicegui/elements/select.py

@@ -1,17 +1,13 @@
 import re
 import re
 from copy import deepcopy
 from copy import deepcopy
-from pathlib import Path
 from typing import Any, Callable, Dict, List, Optional, Union
 from typing import Any, Callable, Dict, List, Optional, Union
 
 
-from ..dependencies import register_vue_component
 from ..events import GenericEventArguments
 from ..events import GenericEventArguments
 from .choice_element import ChoiceElement
 from .choice_element import ChoiceElement
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 
 
-component = register_vue_component(Path('select.js'))
 
 
-
-class Select(ChoiceElement, DisableableElement):
+class Select(ChoiceElement, DisableableElement, component='select.js'):
 
 
     def __init__(self,
     def __init__(self,
                  options: Union[List, Dict], *,
                  options: Union[List, Dict], *,
@@ -40,8 +36,7 @@ class Select(ChoiceElement, DisableableElement):
                 value = []
                 value = []
             elif not isinstance(value, list):
             elif not isinstance(value, list):
                 value = [value]
                 value = [value]
-        super().__init__(tag=component.tag, options=options, value=value, on_change=on_change)
-        self.use_component(component)
+        super().__init__(options=options, value=value, on_change=on_change)
         if label is not None:
         if label is not None:
             self._props['label'] = label
             self._props['label'] = label
         if with_input:
         if with_input:

+ 2 - 8
nicegui/elements/table.py

@@ -1,17 +1,13 @@
-from pathlib import Path
 from typing import Any, Callable, Dict, List, Optional
 from typing import Any, Callable, Dict, List, Optional
 
 
 from typing_extensions import Literal
 from typing_extensions import Literal
 
 
-from ..dependencies import register_vue_component
 from ..element import Element
 from ..element import Element
 from ..events import GenericEventArguments, TableSelectionEventArguments, handle_event
 from ..events import GenericEventArguments, TableSelectionEventArguments, handle_event
 from .mixins.filter_element import FilterElement
 from .mixins.filter_element import FilterElement
 
 
-component = register_vue_component(Path('table.js'))
 
 
-
-class Table(FilterElement):
+class Table(FilterElement, component='table.js'):
 
 
     def __init__(self,
     def __init__(self,
                  columns: List[Dict],
                  columns: List[Dict],
@@ -36,7 +32,7 @@ class Table(FilterElement):
 
 
         If selection is 'single' or 'multiple', then a `selected` property is accessible containing the selected rows.
         If selection is 'single' or 'multiple', then a `selected` property is accessible containing the selected rows.
         """
         """
-        super().__init__(tag=component.tag)
+        super().__init__()
 
 
         self.rows = rows
         self.rows = rows
         self.row_key = row_key
         self.row_key = row_key
@@ -63,8 +59,6 @@ class Table(FilterElement):
             handle_event(on_select, arguments)
             handle_event(on_select, arguments)
         self.on('selection', handle_selection, ['added', 'rows', 'keys'])
         self.on('selection', handle_selection, ['added', 'rows', 'keys'])
 
 
-        self.use_component(component)
-
     def add_rows(self, *rows: Dict) -> None:
     def add_rows(self, *rows: Dict) -> None:
         """Add rows to the table."""
         """Add rows to the table."""
         self.rows.extend(rows)
         self.rows.extend(rows)

+ 2 - 7
nicegui/elements/upload.py

@@ -1,18 +1,14 @@
-from pathlib import Path
 from typing import Any, Callable, Dict, Optional
 from typing import Any, Callable, Dict, Optional
 
 
 from fastapi import Request
 from fastapi import Request
 from starlette.datastructures import UploadFile
 from starlette.datastructures import UploadFile
 
 
-from ..dependencies import register_vue_component
 from ..events import EventArguments, UploadEventArguments, handle_event
 from ..events import EventArguments, UploadEventArguments, handle_event
 from ..nicegui import app
 from ..nicegui import app
 from .mixins.disableable_element import DisableableElement
 from .mixins.disableable_element import DisableableElement
 
 
-component = register_vue_component(Path('upload.js'))
 
 
-
-class Upload(DisableableElement):
+class Upload(DisableableElement, component='upload.js'):
 
 
     def __init__(self, *,
     def __init__(self, *,
                  multiple: bool = False,
                  multiple: bool = False,
@@ -37,8 +33,7 @@ class Upload(DisableableElement):
         :param label: label for the uploader (default: `''`)
         :param label: label for the uploader (default: `''`)
         :param auto_upload: automatically upload files when they are selected (default: `False`)
         :param auto_upload: automatically upload files when they are selected (default: `False`)
         """
         """
-        super().__init__(tag=component.tag)
-        self.use_component(component)
+        super().__init__()
         self._props['multiple'] = multiple
         self._props['multiple'] = multiple
         self._props['label'] = label
         self._props['label'] = label
         self._props['auto-upload'] = auto_upload
         self._props['auto-upload'] = auto_upload

+ 2 - 6
nicegui/elements/video.py

@@ -3,13 +3,10 @@ from pathlib import Path
 from typing import Union
 from typing import Union
 
 
 from .. import globals
 from .. import globals
-from ..dependencies import register_vue_component
 from ..element import Element
 from ..element import Element
 
 
-component = register_vue_component(Path('video.js'))
 
 
-
-class Video(Element):
+class Video(Element, component='video.js'):
 
 
     def __init__(self, src: Union[str, Path], *,
     def __init__(self, src: Union[str, Path], *,
                  controls: bool = True,
                  controls: bool = True,
@@ -29,7 +26,7 @@ class Video(Element):
         See `here <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#events>`_
         See `here <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#events>`_
         for a list of events you can subscribe to using the generic event subscription `on()`.
         for a list of events you can subscribe to using the generic event subscription `on()`.
         """
         """
-        super().__init__(component.tag)
+        super().__init__()
         if Path(src).is_file():
         if Path(src).is_file():
             src = globals.app.add_media_file(local_file=src)
             src = globals.app.add_media_file(local_file=src)
         self._props['src'] = src
         self._props['src'] = src
@@ -37,7 +34,6 @@ class Video(Element):
         self._props['autoplay'] = autoplay
         self._props['autoplay'] = autoplay
         self._props['muted'] = muted
         self._props['muted'] = muted
         self._props['loop'] = loop
         self._props['loop'] = loop
-        self.use_component(component)
 
 
         if type:
         if type:
             url = f'https://github.com/zauberzeug/nicegui/pull/624'
             url = f'https://github.com/zauberzeug/nicegui/pull/624'

+ 5 - 6
nicegui/functions/refreshable.py

@@ -1,16 +1,12 @@
 from dataclasses import dataclass
 from dataclasses import dataclass
-from pathlib import Path
 from typing import Any, Awaitable, Callable, Dict, List, Tuple, Union
 from typing import Any, Awaitable, Callable, Dict, List, Tuple, Union
 
 
 from typing_extensions import Self
 from typing_extensions import Self
 
 
 from .. import background_tasks, globals
 from .. import background_tasks, globals
-from ..dependencies import register_vue_component
 from ..element import Element
 from ..element import Element
 from ..helpers import KWONLY_SLOTS, is_coroutine_function
 from ..helpers import KWONLY_SLOTS, is_coroutine_function
 
 
-component = register_vue_component(Path('refreshable.js'), base_path=Path(__file__).parent.parent / 'functions')
-
 
 
 @dataclass(**KWONLY_SLOTS)
 @dataclass(**KWONLY_SLOTS)
 class RefreshableTarget:
 class RefreshableTarget:
@@ -37,6 +33,10 @@ class RefreshableTarget:
             return None  # required by mypy
             return None  # required by mypy
 
 
 
 
+class RefreshableContainer(Element, component='refreshable.js'):
+    pass
+
+
 class refreshable:
 class refreshable:
 
 
     def __init__(self, func: Callable[..., Any]) -> None:
     def __init__(self, func: Callable[..., Any]) -> None:
@@ -55,8 +55,7 @@ class refreshable:
 
 
     def __call__(self, *args: Any, **kwargs: Any) -> Union[None, Awaitable]:
     def __call__(self, *args: Any, **kwargs: Any) -> Union[None, Awaitable]:
         self.prune()
         self.prune()
-        container = Element(component.tag).use_component(component)
-        target = RefreshableTarget(container=container, instance=self.instance, args=args, kwargs=kwargs)
+        target = RefreshableTarget(container=RefreshableContainer(), instance=self.instance, args=args, kwargs=kwargs)
         self.targets.append(target)
         self.targets.append(target)
         return target.run(self.func)
         return target.run(self.func)
 
 

+ 2 - 2
nicegui/templates/index.html

@@ -105,8 +105,8 @@
         }
         }
 
 
         // @todo: Try avoid this with better handling of initial page load.
         // @todo: Try avoid this with better handling of initial page load.
-        element.components.forEach((component) => loaded_components.add(component));
-        element.libraries.forEach((library) => loaded_libraries.add(library));
+        element.components.forEach((component) => loaded_components.add(component.name));
+        element.libraries.forEach((library) => loaded_libraries.add(library.name));
 
 
         const props = {
         const props = {
           id: 'c' + element.id,
           id: 'c' + element.id,

+ 2 - 7
website/intersection_observer.py

@@ -1,21 +1,16 @@
-from pathlib import Path
 from typing import Callable
 from typing import Callable
 
 
-from nicegui.dependencies import register_vue_component
 from nicegui.element import Element
 from nicegui.element import Element
 from nicegui.events import EventArguments, handle_event
 from nicegui.events import EventArguments, handle_event
 
 
-component = register_vue_component(Path('intersection_observer.js'), base_path=Path(__file__).parent)
 
 
-
-class IntersectionObserver(Element):
+class IntersectionObserver(Element, component='intersection_observer.js'):
 
 
     def __init__(self, *, on_intersection: Callable) -> None:
     def __init__(self, *, on_intersection: Callable) -> None:
-        super().__init__(component.tag)
+        super().__init__()
         self.on_intersection = on_intersection
         self.on_intersection = on_intersection
         self.active = True
         self.active = True
         self.on('intersection', self.handle_intersection, [])
         self.on('intersection', self.handle_intersection, [])
-        self.use_component(component)
 
 
     def handle_intersection(self, _) -> None:
     def handle_intersection(self, _) -> None:
         self.run_method('stop')
         self.run_method('stop')