Source code for animaid.html_checkbox

"""HTMLCheckbox - A checkbox widget with two-way binding."""

from __future__ import annotations

import html
import threading
from collections.abc import Callable
from typing import Self


[docs] class HTMLCheckbox: """A checkbox widget with two-way boolean binding. The checked property is automatically synced from the browser when the user toggles the checkbox. Examples: >>> checkbox = HTMLCheckbox("Accept terms", checked=False) >>> checkbox = HTMLCheckbox("Enable feature").on_change(handle_change) # With App - two-way binding >>> with App() as app: ... terms = HTMLCheckbox("I accept the terms") ... app.add(terms) ... # Later, read the current value: ... print(terms.checked) # True/False synced from browser """ def __init__( self, label: str, checked: bool = False, ) -> None: """Create a checkbox widget. Args: label: The text label displayed next to the checkbox. checked: The initial checked state. """ self._label = label self._value = checked # _value holds the checked state self._on_change: Callable[[bool], None] | None = None self._styles: dict[str, str] = {} self._css_classes: list[str] = ["anim-checkbox-container"] self._anim_id: str | None = None self._lock = threading.Lock() @property def checked(self) -> bool: """Get the current checked state (thread-safe). The value is automatically synced from the browser when the user toggles the checkbox. Returns: True if checked, False otherwise. """ with self._lock: return self._value @checked.setter def checked(self, value: bool) -> None: """Set the checked state (thread-safe). Note: Setting the value programmatically does not automatically update the browser display. Use anim.refresh() to update. Args: value: The new checked state. """ with self._lock: self._value = value @property def value(self) -> bool: """Alias for checked property (for HTMLInput compatibility).""" return self.checked @value.setter def value(self, new_value: bool) -> None: """Alias for checked setter (for HTMLInput compatibility).""" self.checked = new_value @property def label(self) -> str: """Get the checkbox label.""" return self._label
[docs] def on_change(self, callback: Callable[[bool], None]) -> "HTMLCheckbox": """Register a callback for state changes. The callback is called when the user toggles the checkbox. Args: callback: A function that takes the new checked state as argument. Returns: Self for method chaining. Examples: >>> def handle_change(checked): ... print(f"Checkbox is now: {'checked' if checked else 'unchecked'}") >>> checkbox = HTMLCheckbox("Enable").on_change(handle_change) """ self._on_change = callback return self
[docs] def render(self) -> str: """Return HTML representation of this checkbox. Returns: A string containing valid HTML for the checkbox. """ attrs = self._build_attributes() escaped_label = html.escape(self._label) checked_attr = " checked" if self._value else "" # Add data-anim-id for event handling anim_id_attr = "" if self._anim_id: anim_id_attr = f' data-anim-id="{html.escape(self._anim_id)}"' checkbox_html = ( f'<input type="checkbox" class="anim-checkbox"{anim_id_attr}{checked_attr}>' ) label_html = f"<span>{escaped_label}</span>" if attrs: return f"<label {attrs}>{checkbox_html}{label_html}</label>" return f"<label>{checkbox_html}{label_html}</label>"
def __html__(self) -> str: """Jinja2 auto-escaping protocol.""" return self.render()
[docs] def styled(self, **styles: str) -> "HTMLCheckbox": """Return a copy with additional inline styles. Args: **styles: CSS property-value pairs. Returns: A new instance with the combined styles. """ new_checkbox = HTMLCheckbox(self._label, self._value) new_checkbox._on_change = self._on_change new_checkbox._anim_id = self._anim_id new_checkbox._styles = dict(self._styles) new_checkbox._css_classes = list(self._css_classes) # Add new styles, converting underscores to hyphens for key, value in styles.items(): css_key = key.replace("_", "-") new_checkbox._styles[css_key] = value return new_checkbox
[docs] def add_class(self, *class_names: str) -> "HTMLCheckbox": """Return a copy with additional CSS classes. Args: *class_names: CSS class names to add. Returns: A new instance with the additional classes. """ new_checkbox = HTMLCheckbox(self._label, self._value) new_checkbox._on_change = self._on_change new_checkbox._anim_id = self._anim_id new_checkbox._styles = dict(self._styles) new_checkbox._css_classes = list(self._css_classes) + list(class_names) return new_checkbox
def _build_style_string(self) -> str: """Convert internal styles dict to CSS style attribute value.""" if not self._styles: return "" return "; ".join(f"{k}: {v}" for k, v in self._styles.items()) def _build_class_string(self) -> str: """Convert internal classes list to CSS class attribute value.""" if not self._css_classes: return "" return " ".join(self._css_classes) def _build_attributes(self) -> str: """Build the complete HTML attributes string.""" parts = [] class_str = self._build_class_string() if class_str: parts.append(f'class="{class_str}"') style_str = self._build_style_string() if style_str: parts.append(f'style="{style_str}"') return " ".join(parts) # Styled presets
[docs] def large(self) -> "HTMLCheckbox": """Return a copy with larger text and checkbox. Returns: A new instance with large styling. """ return self.styled(font_size="18px")
[docs] def small(self) -> "HTMLCheckbox": """Return a copy with smaller text and checkbox. Returns: A new instance with small styling. """ return self.styled(font_size="12px")