import json from fasthtml.xtend import Script from myfasthtml.core.commands import Command from myfasthtml.core.instances import MultipleInstance class Mouse(MultipleInstance): """ Represents a mechanism to manage mouse event combinations and their associated commands. This class is used to add, manage, and render mouse event sequences with corresponding commands, providing a flexible way to handle mouse interactions programmatically. Combinations can be defined with: - A Command object: mouse.add("click", command) - HTMX parameters: mouse.add("click", hx_post="/url", hx_vals={...}) - Both (named params override command): mouse.add("click", command, hx_target="#other") For dynamic hx_vals, use "js:functionName()" to call a client-side function. """ def __init__(self, parent, _id=None, combinations=None): super().__init__(parent, _id=_id) self.combinations = combinations or {} def add(self, sequence: str, command: Command = None, *, hx_post: str = None, hx_get: str = None, hx_put: str = None, hx_delete: str = None, hx_patch: str = None, hx_target: str = None, hx_swap: str = None, hx_vals=None): """ Add a mouse combination with optional command and HTMX parameters. Args: sequence: Mouse event sequence (e.g., "click", "ctrl+click", "click right_click") command: Optional Command object for server-side action hx_post: HTMX post URL (overrides command) hx_get: HTMX get URL (overrides command) hx_put: HTMX put URL (overrides command) hx_delete: HTMX delete URL (overrides command) hx_patch: HTMX patch URL (overrides command) hx_target: HTMX target selector (overrides command) hx_swap: HTMX swap strategy (overrides command) hx_vals: HTMX values dict or "js:functionName()" for dynamic values Returns: self for method chaining """ self.combinations[sequence] = { "command": command, "hx_post": hx_post, "hx_get": hx_get, "hx_put": hx_put, "hx_delete": hx_delete, "hx_patch": hx_patch, "hx_target": hx_target, "hx_swap": hx_swap, "hx_vals": hx_vals, } return self def _build_htmx_params(self, combination_data: dict) -> dict: """ Build HTMX parameters by merging command params with named overrides. Named parameters take precedence over command parameters. hx_vals is handled separately via hx-vals-extra to preserve command's hx-vals. """ command = combination_data.get("command") # Start with command params if available if command is not None: params = command.get_htmx_params().copy() else: params = {} # Override with named parameters (only if explicitly set) # Note: hx_vals is handled separately below param_mapping = { "hx_post": "hx-post", "hx_get": "hx-get", "hx_put": "hx-put", "hx_delete": "hx-delete", "hx_patch": "hx-patch", "hx_target": "hx-target", "hx_swap": "hx-swap", } for py_name, htmx_name in param_mapping.items(): value = combination_data.get(py_name) if value is not None: params[htmx_name] = value # Handle hx_vals separately - store in hx-vals-extra to not overwrite command's hx-vals hx_vals = combination_data.get("hx_vals") if hx_vals is not None: if isinstance(hx_vals, str) and hx_vals.startswith("js:"): # Dynamic values: extract function name func_name = hx_vals[3:].rstrip("()") params["hx-vals-extra"] = {"js": func_name} elif isinstance(hx_vals, dict): # Static dict values params["hx-vals-extra"] = {"dict": hx_vals} else: # Other string values - try to parse as JSON try: parsed = json.loads(hx_vals) if not isinstance(parsed, dict): raise ValueError(f"hx_vals must be a dict, got {type(parsed).__name__}") params["hx-vals-extra"] = {"dict": parsed} except json.JSONDecodeError as e: raise ValueError(f"hx_vals must be a dict or 'js:functionName()', got invalid JSON: {e}") return params def render(self): str_combinations = { sequence: self._build_htmx_params(data) for sequence, data in self.combinations.items() } return Script(f"add_mouse_support('{self._parent.get_id()}', '{json.dumps(str_combinations)}')") def __ft__(self): return self.render()