Files
MyFastHtml/docs/Mouse Support.md

16 KiB

Mouse Support - Documentation

Overview

The mouse support library provides keyboard-like binding capabilities for mouse actions. It supports simple clicks, modified clicks (with Ctrl/Shift/Alt), and sequences of clicks with smart timeout logic.

Features

Supported Mouse Actions

Basic Actions:

  • click - Left click
  • right_click (or rclick) - Right click (contextmenu)

Modified Actions:

  • ctrl+click (or ctrl+rclick) - Ctrl+Click (or Cmd+Click on Mac)
  • shift+click (or shift+rclick) - Shift+Click
  • alt+click (or alt+rclick) - Alt+Click
  • ctrl+shift+click - Multiple modifiers
  • Any combination of modifiers

Sequences:

  • click right_click (or click rclick) - Click then right-click within 500ms
  • click click - Double click sequence
  • ctrl+click click - Ctrl+click then normal click
  • Any sequence of actions

Note: rclick is an alias for right_click and can be used interchangeably.

Smart Timeout Logic

Same as keyboard support:

  • If any element has a longer sequence possible, all matching elements wait
  • Timeout is 500ms between actions
  • Immediate trigger if no longer sequences exist

Multiple Element Support

Multiple elements can listen to the same mouse action and all will trigger simultaneously.

Configuration Format

Uses HTMX configuration objects (same as keyboard support):

const combinations = {
  "click": {
    "hx-post": "/handle-click",
    "hx-target": "#result"
  },
  "ctrl+click": {
    "hx-post": "/handle-ctrl-click",
    "hx-swap": "innerHTML"
  },
  "rclick": {  // Alias for right_click
    "hx-post": "/context-menu"
  },
  "click rclick": {  // Can use rclick in sequences too
    "hx-post": "/sequence-action",
    "hx-vals": {"type": "sequence"}
  }
};

add_mouse_support('my-element', JSON.stringify(combinations));

Dynamic Values with JavaScript Functions

You can add dynamic values computed at click time using hx-vals-extra. This is useful when combined with a Command (which provides hx-vals with c_id).

Configuration format:

const combinations = {
  "click": {
    "hx-post": "/myfasthtml/commands",
    "hx-vals": {"c_id": "command_id"},           // Static values from Command
    "hx-vals-extra": {"js": "getClickData"}      // Dynamic values via JS function
  }
};

How it works:

  1. hx-vals contains static values (e.g., c_id from Command)
  2. hx-vals-extra.dict contains additional static values (merged)
  3. hx-vals-extra.js specifies a function to call for dynamic values (merged)

JavaScript function definition:

// Function receives (event, element, combinationStr)
function getClickData(event, element, combination) {
  return {
    x: event.clientX,
    y: event.clientY,
    target_id: event.target.id,
    timestamp: Date.now()
  };
}

The function parameters are optional - use what you need:

// Full context
function getFullContext(event, element, combination) {
  return { x: event.clientX, elem: element.id, combo: combination };
}

// Just the event
function getPosition(event) {
  return { x: event.clientX, y: event.clientY };
}

// No parameters needed
function getTimestamp() {
  return { ts: Date.now() };
}

Built-in helper function:

// getCellId() - finds parent with .dt2-cell class and returns its id
function getCellId(event) {
  const cell = event.target.closest('.dt2-cell');
  if (cell && cell.id) {
    return { cell_id: cell.id };
  }
  return {};
}

API Reference

add_mouse_support(elementId, combinationsJson)

Adds mouse support to an element.

Parameters:

  • elementId (string): ID of the HTML element
  • combinationsJson (string): JSON string of combinations with HTMX configs

Returns: void

Example:

add_mouse_support('button1', JSON.stringify({
  "click": {"hx-post": "/click"},
  "ctrl+click": {"hx-post": "/ctrl-click"}
}));

remove_mouse_support(elementId)

Removes mouse support from an element.

Parameters:

  • elementId (string): ID of the HTML element

Returns: void

Side effects:

  • If last element: detaches global event listeners and cleans up all state

Example:

remove_mouse_support('button1');

Automatic Parameters

The library automatically adds these parameters to every HTMX request:

  • combination - The mouse combination that triggered the action (e.g., "ctrl+click")
  • has_focus - Boolean indicating if the element had focus when clicked
  • is_inside - Boolean indicating if the click was inside the element
    • For click: true if clicked inside element, false if clicked outside
    • For right_click: always true (only triggers when clicking on element)
  • has_focus - Boolean indicating if the element had focus when the action triggered
  • clicked_inside - Boolean indicating if the click was inside the element or outside

Parameter Details

has_focus:

  • true if the registered element currently has focus
  • false otherwise
  • Useful for knowing if the element was the active element

clicked_inside:

  • For click actions: true if clicked on/inside the element, false if clicked outside
  • For right_click actions: always true (since right-click only triggers on the element)
  • Useful for "click outside to close" logic

Example values sent:

// User clicks inside a modal
{
  combination: "click",
  has_focus: true,
  clicked_inside: true
}

// User clicks outside the modal (modal still gets triggered because click is global)
{
  combination: "click",
  has_focus: false,
  clicked_inside: false  // Perfect for closing the modal!
}

// User right-clicks on an item
{
  combination: "right_click",
  has_focus: false,
  clicked_inside: true  // Always true for right_click
}

Python Integration

Mouse Class

The Mouse class provides a convenient way to add mouse support to elements.

from myfasthtml.controls.Mouse import Mouse
from myfasthtml.core.commands import Command

# Create mouse support for an element
mouse = Mouse(parent_element)

# Add combinations
mouse.add("click", select_command)
mouse.add("ctrl+click", toggle_command)
mouse.add("right_click", context_menu_command)

Mouse.add() Method

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)

Parameters:

  • sequence: Mouse event sequence (e.g., "click", "ctrl+click", "click right_click")
  • command: Optional Command object for server-side action
  • hx_post, hx_get, etc.: HTMX URL parameters (override command)
  • hx_target: HTMX target selector (overrides command)
  • hx_swap: HTMX swap strategy (overrides command)
  • hx_vals: Additional HTMX values - dict or "js:functionName()" for dynamic values

Note:

  • Named parameters (except hx_vals) override the command's parameters.
  • hx_vals is merged with command's values (stored in hx-vals-extra), preserving c_id.

Usage Patterns

With Command only:

mouse.add("click", my_command)

With Command and overrides:

# Command provides hx-post, but we override the target
mouse.add("ctrl+click", my_command, hx_target="#other-result")

Without Command (direct HTMX):

mouse.add("right_click", hx_post="/context-menu", hx_target="#menu", hx_swap="innerHTML")

With dynamic values:

mouse.add("shift+click", my_command, hx_vals="js:getClickPosition()")

Sequences

mouse = Mouse(element)
mouse.add("click", single_click_command)
mouse.add("click click", double_click_command)
mouse.add("click right_click", special_action_command)

Multiple Elements

# Each element gets its own Mouse instance
for item in items:
    mouse = Mouse(item)
    mouse.add("click", Command("select", "Select item", lambda i=item: select(i)))
    mouse.add("ctrl+click", Command("toggle", "Toggle item", lambda i=item: toggle(i)))

Dynamic hx-vals with JavaScript

You can use "js:functionName()" to call a client-side JavaScript function that returns additional values to send with the request. The command's c_id is preserved.

Python:

mouse.add("click", my_command, hx_vals="js:getClickContext()")

Generated config (internally):

{
  "hx-post": "/myfasthtml/commands",
  "hx-vals": {"c_id": "command_id"},
  "hx-vals-extra": {"js": "getClickContext"}
}

JavaScript (client-side):

// Function receives (event, element, combinationStr)
function getClickContext(event, element, combination) {
    return {
        x: event.clientX,
        y: event.clientY,
        elementId: element.id,
        combo: combination
    };
}

// Simple function - parameters are optional
function getTimestamp() {
    return { ts: Date.now() };
}

Values sent to server:

{
  "c_id": "command_id",
  "x": 150,
  "y": 200,
  "elementId": "my-element",
  "combo": "click",
  "combination": "click",
  "has_focus": false,
  "is_inside": true
}

You can also pass a static dict:

mouse.add("click", my_command, hx_vals={"extra_key": "extra_value"})

Low-Level Usage (without Mouse class)

For advanced use cases, you can generate the JavaScript directly:

import json

combinations = {
    "click": {
        "hx-post": "/item/select"
    },
    "ctrl+click": {
        "hx-post": "/item/select-multiple",
        "hx-vals": {"mode": "multi"}
    },
    "right_click": {
        "hx-post": "/item/context-menu",
        "hx-target": "#context-menu",
        "hx-swap": "innerHTML"
    }
}

Script(f"add_mouse_support('{element_id}', '{json.dumps(combinations)}')")

Behavior Details

Click vs Right-Click Behavior

IMPORTANT: The library handles click and right_click differently:

click (global detection):

  • Triggers for ALL registered elements, regardless of where you click
  • Useful for "click outside to close" functionality (modals, dropdowns, popups)
  • Example: Modal registered with click → clicking anywhere on the page triggers the modal's click action

right_click (element-specific detection):

  • Triggers ONLY when you right-click on (or inside) the registered element
  • Right-clicking outside the element does nothing and shows browser's context menu
  • This preserves normal browser behavior while adding custom actions on your elements

Example use case:

// Modal that closes when clicking anywhere
add_mouse_support('modal', JSON.stringify({
  "click": {"hx-post": "/close-modal"}  // Triggers even if you click outside modal
}));

// Context menu that only appears on element
add_mouse_support('item', JSON.stringify({
  "right_click": {"hx-post": "/item-menu"}  // Only triggers when right-clicking the item
}));

Modifier Keys (Cross-Platform)

  • Windows/Linux: ctrl+click uses Ctrl key
  • Mac: ctrl+click uses Cmd (⌘) key OR Ctrl key
  • This follows standard web conventions for cross-platform compatibility

Input Context Protection

Mouse actions are disabled when clicking in input fields:

  • <input> elements
  • <textarea> elements
  • Any contenteditable element

This ensures normal text selection and interaction works in forms.

Right-Click Menu

The contextmenu (right-click menu) is prevented with preventDefault() when:

  • A right_click action is configured for the element
  • The element is NOT an input/textarea/contenteditable

Event Bubbling

The library checks if the click target OR any parent element is registered:

<div id="container">
  <button>Click me</button>
</div>

If container is registered, clicking the button will trigger the container's actions.

Examples

Using Aliases

You can use rclick instead of right_click anywhere:

// These are equivalent
const config1 = {
  "right_click": {"hx-post": "/menu"}
};

const config2 = {
  "rclick": {"hx-post": "/menu"}  // Shorter alias
};

// Works in sequences too
const config3 = {
  "click rclick": {"hx-post": "/sequence"}
};

// Works with modifiers
const config4 = {
  "ctrl+rclick": {"hx-post": "/ctrl-right-click"}
};

Context Menu

const combinations = {
  "right_click": {
    "hx-post": "/show-context-menu",
    "hx-target": "#menu",
    "hx-swap": "innerHTML"
  }
};

Close Modal/Popup on Click Outside

Since click is detected globally (anywhere on the page), it's perfect for "click outside to close":

// Modal element
const modalCombinations = {
  "click": {
    "hx-post": "/close-modal",
    "hx-target": "#modal",
    "hx-swap": "outerHTML"
  }
};
add_mouse_support('modal', JSON.stringify(modalCombinations));

// Now clicking ANYWHERE on the page will trigger the handler
// Your backend receives:
// - clicked_inside: true (if clicked on modal) → maybe keep it open
// - clicked_inside: false (if clicked outside) → close it!

Backend example (Python/Flask):

@app.route('/close-modal', methods=['POST'])
def close_modal():
    clicked_inside = request.form.get('clicked_inside') == 'true'
    
    if clicked_inside:
        # User clicked inside the modal - keep it open
        return render_template('modal_content.html')
    else:
        # User clicked outside - close the modal
        return ''  # Empty response removes the modal

Note: The click handler on the modal element will trigger for all clicks on the page, not just clicks on the modal itself. Use the clicked_inside parameter to determine the appropriate action.

Multi-Select List

const combinations = {
  "click": {
    "hx-post": "/select-item",
    "hx-vals": {"mode": "single"}
  },
  "ctrl+click": {
    "hx-post": "/select-item",
    "hx-vals": {"mode": "toggle"}
  },
  "shift+click": {
    "hx-post": "/select-range",
    "hx-vals": {"mode": "range"}
  }
};

Interactive Canvas/Drawing

const combinations = {
  "click": {
    "hx-post": "/draw-point"
  },
  "ctrl+click": {
    "hx-post": "/draw-special"
  },
  "click click": {
    "hx-post": "/confirm-action"
  }
};

Drag-and-Drop Alternative

const combinations = {
  "click": {
    "hx-post": "/select-source"
  },
  "click click": {
    "hx-post": "/set-destination"
  }
};

Troubleshooting

Clicks not detected

  • Verify the element exists and has the correct ID
  • Check browser console for errors
  • Ensure HTMX is loaded before mouse_support.js

Right-click menu still appears

  • Check if element is in input context (input/textarea)
  • Verify the combination is configured correctly
  • Check browser console for configuration errors

Sequences not working

  • Ensure clicks happen within 500ms timeout
  • Check if longer sequences exist (causes waiting)
  • Verify the combination string format (space-separated)

Technical Details

Architecture

  • Global listeners on document for click and contextmenu events
  • Tree-based matching using prefix trees (same as keyboard support)
  • Single timeout for all elements (sequence-based, not element-based)
  • Independent from keyboard support (separate registry and timeouts)

Performance

  • Single event listener regardless of number of elements
  • O(n) matching where n is sequence length
  • Efficient memory usage with automatic cleanup

Browser Compatibility

  • Modern browsers (ES6+ required)
  • Chrome, Firefox, Safari, Edge
  • Requires HTMX library

Notes

  • Timeout value is the same as keyboard support (500ms) but in separate variable
  • Can be used independently or alongside keyboard support
  • Does not interfere with normal mouse behavior in inputs
  • Element must exist in DOM when add_mouse_support() is called
  • Alias: rclick can be used interchangeably with right_click for shorter syntax