Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add better support for htmx 2.0 #83

Merged
merged 1 commit into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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!')"}