Skip to content
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
Updates for RC.6
  • Loading branch information
gazpachoking committed Oct 21, 2025
commit 7b6055fcf363f4e9c133f4cbbab7fb14ff3e5dc5
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ requires = [ "hatchling" ]

[project]
name = "datastar-py"
version = "0.6.5"
version = "0.7.0"
description = "Helper functions and classes for the Datastar library (https://data-star.dev/)"
readme = "README.md"
keywords = [
Expand Down
78 changes: 21 additions & 57 deletions src/datastar_py/attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import re
from collections.abc import Iterable, Iterator, Mapping
from itertools import chain
from typing import TYPE_CHECKING, Literal, TypeAlias, Union, overload
from typing import TYPE_CHECKING, Literal, TypeAlias, Union

if TYPE_CHECKING:
from typing import Self
Expand Down Expand Up @@ -170,54 +170,18 @@ def class_(self, class_dict: Mapping | None = None, /, **classes: str) -> BaseAt
classes = {**(class_dict if class_dict else {}), **classes}
return BaseAttr("class", value=_js_object(classes), alias=self._alias)

@overload
def on(self, event: Literal["interval"], expression: str) -> OnIntervalAttr: ...
@overload
def on(self, event: Literal["load"], expression: str) -> OnLoadAttr: ...
@overload
def on(self, event: Literal["intersect"], expression: str) -> OnIntersectAttr: ...
@overload
def on(self, event: Literal["raf"], expression: str) -> OnRafAttr: ...
@overload
def on(self, event: Literal["resize"], expression: str) -> OnResizeAttr: ...
@overload
def on(self, event: Literal["signal-patch"], expression: str) -> OnSignalPatchAttr: ...
@overload
def on(self, event: JSEvent | str, expression: str) -> OnAttr: ...
def on(
self, event: str, expression: str
) -> (
OnAttr
| OnIntervalAttr
| OnLoadAttr
| OnIntersectAttr
| OnRafAttr
| OnResizeAttr
| OnSignalPatchAttr
):
def init(self, expression: str) -> InitAttr:
"""Execute an expression when the element is loaded into the DOM."""
return InitAttr(value=expression, alias=self._alias)

def on(self, event: JSEvent | str, expression: str) -> OnAttr:
"""Execute an expression when an event occurs."""
if event == "interval":
return OnIntervalAttr(value=expression, alias=self._alias)
if event == "load":
return OnLoadAttr(value=expression, alias=self._alias)
if event == "raf":
return OnRafAttr(value=expression, alias=self._alias)
if event == "resize":
return OnResizeAttr(value=expression, alias=self._alias)
if event == "intersect":
return OnIntersectAttr(value=expression, alias=self._alias)
if event == "signal-patch":
return OnSignalPatchAttr(value=expression, alias=self._alias)
return OnAttr(key=event, value=expression, alias=self._alias)

def on_interval(self, expression: str) -> OnIntervalAttr:
"""Execute an expression at a regular interval."""
return OnIntervalAttr(value=expression, alias=self._alias)

def on_load(self, expression: str) -> OnLoadAttr:
"""Execute an expression when the element is loaded into the DOM."""
return OnLoadAttr(value=expression, alias=self._alias)

def on_intersect(self, expression: str) -> OnIntersectAttr:
"""Execute an expression when the element intersects with the viewport."""
return OnIntersectAttr(value=expression, alias=self._alias)
Expand Down Expand Up @@ -333,17 +297,16 @@ def __call__(self) -> Self:
def _full_key(self) -> str:
key = f"{self._alias}{self._attr}"
if self._key:
key += f"-{self._key}"
key += f":{self._key}"
for mod, values in self._mods.items():
key += f"__{mod}"
if values:
key += f".{'.'.join(values)}"
return key

def _to_kebab_key(self, key_name: str) -> None:
if "-" in key_name:
kebab_name, from_case = key_name.lower(), "kebab"
elif "_" in key_name:
if "__" in key_name:
# _ are allowed in attributes, the only time we need to convert is if there are multiple underscores
kebab_name, from_case = key_name.lower().replace("_", "-"), "snake"
elif key_name[0].isupper():
kebab_name, from_case = (
Expand All @@ -356,7 +319,8 @@ def _to_kebab_key(self, key_name: str) -> None:
"camel",
)
else:
kebab_name, from_case = key_name, None
# kebab case means the raw name from the attribute will be passed through
kebab_name, from_case = key_name, "kebab"
self._key = kebab_name
if from_case:
self._mods["case"] = [from_case]
Expand Down Expand Up @@ -393,43 +357,43 @@ def debounce(
wait: int | str,
*,
leading: bool = False,
notrail: bool = False,
notrailing: bool = False,
) -> Self:
"""Debounce the event listener.

:param wait: The minimum interval between events.
:param leading: If True, the event listener will be called on the leading edge of the
wait time.
:param notrail: If True, the event listener will not be called on the trailing edge of the
:param notrailing: If True, the event listener will not be called on the trailing edge of the
wait time.
"""
self._mods["debounce"] = [str(wait)]
if leading:
self._mods["debounce"].append("leading")
if notrail:
self._mods["debounce"].append("notrail")
if notrailing:
self._mods["debounce"].append("notrailing")
return self

def throttle(
self: Self,
wait: int | str,
*,
noleading: bool = False,
trail: bool = False,
trailing: bool = False,
) -> Self:
"""Throttle the event listener.

:param wait: The minimum interval between events.
:param noleading: If true, the event listener will not be called on the leading edge of the
wait time.
:param trail: If true, the event listener will be called on the trailing edge of the
:param trailing: If true, the event listener will be called on the trailing edge of the
wait time.
"""
self._mods["throttle"] = [str(wait)]
if noleading:
self._mods["throttle"].append("noleading")
if trail:
self._mods["throttle"].append("trail")
if trailing:
self._mods["throttle"].append("trailing")
return self


Expand Down Expand Up @@ -672,8 +636,8 @@ def duration(self, duration: int | float | str, *, leading: bool = False) -> Sel
return self


class OnLoadAttr(BaseAttr, ViewtransitionMod, DelayMod):
_attr = "on-load"
class InitAttr(BaseAttr, ViewtransitionMod, DelayMod):
_attr = "init"

@property
def once(self) -> Self:
Expand Down
Loading