htpy-style sugar on Element: kwargs attributes + [] children
This commit is contained in:
@@ -235,6 +235,16 @@ class Element(Node):
|
|||||||
children = [children]
|
children = [children]
|
||||||
self.children = children
|
self.children = children
|
||||||
|
|
||||||
|
def __getitem__(self, children: "Children | Node") -> "Element":
|
||||||
|
"""htpy-style children: ``Div(class_="x")[child1, child2]``.
|
||||||
|
|
||||||
|
Returns an Element with the same tag/attributes/media and these
|
||||||
|
children, so the tree stays walkable (Media still bubbles)."""
|
||||||
|
items = children if isinstance(children, tuple) else (children,)
|
||||||
|
clone = Element(self.tag_name, self.attributes, list(items))
|
||||||
|
clone.media = self.media
|
||||||
|
return clone
|
||||||
|
|
||||||
def collect_media(self) -> Media:
|
def collect_media(self) -> Media:
|
||||||
media = self.media
|
media = self.media
|
||||||
for child in self.children:
|
for child in self.children:
|
||||||
|
|||||||
@@ -52,14 +52,31 @@ _SIZE_CLASSES = {
|
|||||||
# tag name is data, not a separate class/function body. Add a tag = one line.
|
# tag name is data, not a separate class/function body. Add a tag = one line.
|
||||||
|
|
||||||
|
|
||||||
|
def _attrs_from_kwargs(attrs: dict[str, object]) -> list[HTMLAttribute]:
|
||||||
|
"""Translate htpy-style attribute kwargs to (name, value) pairs.
|
||||||
|
|
||||||
|
``class_`` -> ``class`` (trailing underscore stripped); ``hx_get`` ->
|
||||||
|
``hx-get`` (inner underscores to hyphens); ``True`` -> bare attribute;
|
||||||
|
``False`` / ``None`` -> omitted."""
|
||||||
|
result: list[HTMLAttribute] = []
|
||||||
|
for key, value in attrs.items():
|
||||||
|
if value is None or value is False:
|
||||||
|
continue
|
||||||
|
name = key.rstrip("_").replace("_", "-")
|
||||||
|
result.append((name, name if value is True else value)) # type: ignore[arg-type]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def _html_element(tag_name: str):
|
def _html_element(tag_name: str):
|
||||||
"""Build a generic element builder for ``tag_name`` (the whitelist factory)."""
|
"""Build a generic element builder for ``tag_name`` (the whitelist factory)."""
|
||||||
|
|
||||||
def element(
|
def element(
|
||||||
attributes: Attributes | None = None,
|
attributes: Attributes | None = None,
|
||||||
children: Children = None,
|
children: Children = None,
|
||||||
|
**attrs: object,
|
||||||
) -> Element:
|
) -> Element:
|
||||||
return Element(tag_name, attributes, children)
|
merged = as_attributes(attributes) + _attrs_from_kwargs(attrs)
|
||||||
|
return Element(tag_name, merged, children)
|
||||||
|
|
||||||
element.__name__ = element.__qualname__ = tag_name[:1].upper() + tag_name[1:]
|
element.__name__ = element.__qualname__ = tag_name[:1].upper() + tag_name[1:]
|
||||||
element.__doc__ = f"Builder for the <{tag_name}> element."
|
element.__doc__ = f"Builder for the <{tag_name}> element."
|
||||||
|
|||||||
@@ -174,5 +174,48 @@ class RealComponentMediaTest(unittest.TestCase):
|
|||||||
self.assertIn("range_slider.js", media.js)
|
self.assertIn("range_slider.js", media.js)
|
||||||
|
|
||||||
|
|
||||||
|
class HtpyStyleSugarTest(unittest.TestCase):
|
||||||
|
def test_getitem_sets_children(self):
|
||||||
|
from common.components import Div, Span
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
render(Div(class_="card")[Span()["hi"]]),
|
||||||
|
'<div class="card"><span>hi</span></div>',
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_getitem_multiple_children(self):
|
||||||
|
from common.components import Div
|
||||||
|
|
||||||
|
self.assertEqual(render(Div()["a", "b"]), "<div>a\nb</div>")
|
||||||
|
|
||||||
|
def test_kwargs_class_underscore_becomes_class(self):
|
||||||
|
from common.components import Div
|
||||||
|
|
||||||
|
self.assertIn('class="x"', render(Div(class_="x")))
|
||||||
|
|
||||||
|
def test_kwargs_inner_underscore_becomes_hyphen(self):
|
||||||
|
from common.components import Div
|
||||||
|
|
||||||
|
self.assertIn('hx-get="/y"', render(Div(hx_get="/y")))
|
||||||
|
|
||||||
|
def test_kwargs_true_renders_bare_attr(self):
|
||||||
|
from common.components import Div
|
||||||
|
|
||||||
|
self.assertIn('hidden="hidden"', render(Div(hidden=True)))
|
||||||
|
|
||||||
|
def test_kwargs_false_and_none_omitted(self):
|
||||||
|
from common.components import Div
|
||||||
|
|
||||||
|
html = render(Div(hidden=False, title=None))
|
||||||
|
self.assertNotIn("hidden", html)
|
||||||
|
self.assertNotIn("title", html)
|
||||||
|
|
||||||
|
def test_getitem_preserves_media(self):
|
||||||
|
from common.components import Div, Media, collect_media
|
||||||
|
|
||||||
|
node = Div(class_="x").with_media(Media(js=("a.js",)))["child"]
|
||||||
|
self.assertEqual(collect_media(node).js, ("a.js",))
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|||||||
Reference in New Issue
Block a user