|  | 
| 2 | 2 | 
 | 
| 3 | 3 | from pathlib import Path | 
| 4 | 4 | from typing import Any | 
| 5 |  | -from urllib.parse import urljoin | 
| 6 | 5 | from uuid import uuid4 | 
| 7 | 6 | 
 | 
| 8 |  | -from reactpy import component, event, html, use_connection | 
|  | 7 | +from reactpy import component, html | 
| 9 | 8 | from reactpy.backend.types import Location | 
| 10 |  | -from reactpy.core.types import VdomChild, VdomDict | 
|  | 9 | +from reactpy.core.types import VdomDict | 
| 11 | 10 | from reactpy.web.module import export, module_from_file | 
| 12 | 11 | 
 | 
| 13 | 12 | from reactpy_router.hooks import _use_route_state | 
|  | 
| 17 | 16 |     module_from_file("reactpy-router", file=Path(__file__).parent / "static" / "bundle.js"), | 
| 18 | 17 |     ("History"), | 
| 19 | 18 | ) | 
| 20 |  | -link_js_content = (Path(__file__).parent / "static" / "link.js").read_text(encoding="utf-8") | 
|  | 19 | +Link = export( | 
|  | 20 | +    module_from_file("reactpy-router", file=Path(__file__).parent / "static" / "bundle.js"), | 
|  | 21 | +    ("Link"), | 
|  | 22 | +) | 
| 21 | 23 | 
 | 
| 22 | 24 | 
 | 
| 23 | 25 | @component | 
| 24 |  | -def link(*children: VdomChild, to: str, **attributes: Any) -> VdomDict: | 
|  | 26 | +def link(*attributes_and_children: Any, to: str | None = None, **kwargs: Any) -> VdomDict: | 
| 25 | 27 |     """A component that renders a link to the given path.""" | 
| 26 |  | -    # FIXME: This currently works in a "dumb" way by trusting that ReactPy's script tag \ | 
| 27 |  | -    # properly sets the location. When a client-server communication layer is added to a \ | 
| 28 |  | -    # future ReactPy release, this component will need to be rewritten to use that instead. \ | 
| 29 |  | -    set_location = _use_route_state().set_location | 
| 30 |  | -    current_path = use_connection().location.pathname | 
| 31 |  | - | 
| 32 |  | -    @event(prevent_default=True) | 
| 33 |  | -    def on_click(_event: dict[str, Any]) -> None: | 
| 34 |  | -        pathname, search = to.split("?", 1) if "?" in to else (to, "") | 
| 35 |  | -        if search: | 
| 36 |  | -            search = f"?{search}" | 
| 37 |  | - | 
| 38 |  | -        # Resolve relative paths that match `../foo` | 
| 39 |  | -        if pathname.startswith("../"): | 
| 40 |  | -            pathname = urljoin(current_path, pathname) | 
| 41 |  | - | 
| 42 |  | -        # Resolve relative paths that match `foo` | 
| 43 |  | -        if not pathname.startswith("/"): | 
| 44 |  | -            pathname = urljoin(current_path, pathname) | 
| 45 |  | - | 
| 46 |  | -        # Resolve relative paths that match `/foo/../bar` | 
| 47 |  | -        while "/../" in pathname: | 
| 48 |  | -            part_1, part_2 = pathname.split("/../", 1) | 
| 49 |  | -            pathname = urljoin(f"{part_1}/", f"../{part_2}") | 
| 50 |  | - | 
| 51 |  | -        # Resolve relative paths that match `foo/./bar` | 
| 52 |  | -        pathname = pathname.replace("/./", "/") | 
| 53 |  | - | 
| 54 |  | -        set_location(Location(pathname, search)) | 
|  | 28 | +    if to is None: | 
|  | 29 | +        raise ValueError("The `to` attribute is required for the `Link` component.") | 
| 55 | 30 | 
 | 
| 56 | 31 |     uuid_string = f"link-{uuid4().hex}" | 
| 57 | 32 |     class_name = f"{uuid_string}" | 
|  | 33 | +    set_location = _use_route_state().set_location | 
|  | 34 | +    attributes = {} | 
|  | 35 | +    children: tuple[Any] = attributes_and_children | 
|  | 36 | + | 
|  | 37 | +    if attributes_and_children and isinstance(attributes_and_children[0], dict): | 
|  | 38 | +        attributes = attributes_and_children[0] | 
|  | 39 | +        children = attributes_and_children[1:] | 
| 58 | 40 |     if "className" in attributes: | 
| 59 | 41 |         class_name = " ".join([attributes.pop("className"), class_name]) | 
| 60 |  | -    # TODO: This can be removed when ReactPy stops supporting underscores in attribute names | 
| 61 | 42 |     if "class_name" in attributes:  # pragma: no cover | 
|  | 43 | +        # TODO: This can be removed when ReactPy stops supporting underscores in attribute names | 
| 62 | 44 |         class_name = " ".join([attributes.pop("class_name"), class_name]) | 
| 63 | 45 | 
 | 
| 64 | 46 |     attrs = { | 
| 65 | 47 |         **attributes, | 
| 66 | 48 |         "href": to, | 
| 67 |  | -        "onClick": on_click, | 
| 68 | 49 |         "className": class_name, | 
| 69 | 50 |     } | 
| 70 |  | -    return html._(html.a(attrs, *children), html.script(link_js_content.replace("UUID", uuid_string))) | 
|  | 51 | + | 
|  | 52 | +    def on_click(_event: dict[str, Any]) -> None: | 
|  | 53 | +        set_location(Location(**_event)) | 
|  | 54 | + | 
|  | 55 | +    return html._(html.a(attrs, *children, **kwargs), Link({"onClick": on_click, "linkClass": uuid_string})) | 
| 71 | 56 | 
 | 
| 72 | 57 | 
 | 
| 73 | 58 | def route(path: str, element: Any | None, *routes: Route) -> Route: | 
|  | 
0 commit comments