Skip to content

Commit

Permalink
feat: Add better support for htmx 2.0
Browse files Browse the repository at this point in the history
  • Loading branch information
paveldedik committed Jun 26, 2024
1 parent ceaf9be commit 4d80841
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 8 deletions.
7 changes: 7 additions & 0 deletions ludic/attrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ class HtmxAttrs(Attrs, total=False):
hx_ws: Annotated[str, Alias("hx-ws")]
hx_sse: Annotated[str, Alias("hx-sse")]

# Extensions
ws_connect: Annotated[str, Alias("ws-connect")]
ws_send: Annotated[str, Alias("ws-send")]
sse_connect: Annotated[str, Alias("sse-connect")]
sse_send: Annotated[str, Alias("sse-send")]
sse_swap: Annotated[str, Alias("sse-swap")]


class WindowEventAttrs(Attrs, total=False):
"""Event Attributes for HTML elements."""
Expand Down
34 changes: 26 additions & 8 deletions ludic/format.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,34 @@
import html
import inspect
import random
import re
from collections.abc import Mapping
from contextvars import ContextVar
from typing import Any, Final, TypeVar
from functools import lru_cache
from typing import Any, Final, TypeVar, get_type_hints

T = TypeVar("T")

_EXTRACT_NUMBER_RE: Final[re.Pattern[str]] = re.compile(r"\{(\d+:id)\}")
_ALIASES: Final[dict[str, str]] = {
"classes": "class",
}


@lru_cache
def _load_attrs_aliases() -> Mapping[str, str]:
from ludic import attrs

result = {}
for name, cls in inspect.getmembers(attrs, inspect.isclass):
if not name.endswith("Attrs"):
continue

hints = get_type_hints(cls, include_extras=True)
for key, value in hints.items():
if metadata := getattr(value, "__metadata__", None):
for meta in metadata:
if isinstance(meta, attrs.Alias):
result[key] = str(meta)

return result


def format_attr_value(key: str, value: Any, is_html: bool = False) -> str:
Expand Down Expand Up @@ -67,13 +85,13 @@ def format_attrs(attrs: Mapping[str, Any], is_html: bool = False) -> dict[str, A
Returns:
dict[str, Any]: The formatted attributes.
"""
aliases = _load_attrs_aliases()
result: dict[str, str] = {}

for key, value in attrs.items():
if formatted_value := format_attr_value(key, value, is_html=is_html):
if key in _ALIASES:
alias = _ALIASES[key]
elif key.startswith("on_"):
alias = key.replace("on_", "on")
if key in aliases:
alias = aliases[key]
else:
alias = key.strip("_").replace("_", "-")

Expand Down
11 changes: 11 additions & 0 deletions tests/test_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,17 @@ def test_data_attributes() -> None:
assert dom.to_html() == '<div data-foo="1" data-bar="test">content</div>'


def test_htmx_attributes() -> None:
assert html.button(
"Get Info!",
hx_get="/info", hx_on__before_request="alert('Making a request!')",
).to_html() == ( # type: ignore
'<button hx-get="/info" hx-on--before-request="alert(\'Making a request!\')">'
"Get Info!"
"</button>"
) # fmt: skip


def test_all_elements() -> None:
assert html.div("test", id="div").to_html() == '<div id="div">test</div>'
assert html.span("test", id="span").to_html() == '<span id="span">test</span>'
Expand Down
13 changes: 13 additions & 0 deletions tests/test_formatting.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,16 @@ def test_attributes() -> None:
assert format_attrs({"class_": "a b c", "classes": ["more", "classes"]}) == {
"class": "a b c more classes"
}

assert format_attrs(
{
"hx_on_htmx_before_request": "alert('test')",
"hx_on__after_request": "alert('test2')",
}
) == {
"hx-on-htmx-before-request": "alert('test')",
"hx-on--after-request": "alert('test2')",
}
assert format_attrs(
{"hx-on:htmx:before-request": "alert('Making a request!')"}
) == {"hx-on:htmx:before-request": "alert('Making a request!')"}

0 comments on commit 4d80841

Please sign in to comment.