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 clickright_click(orrclick) - Right click (contextmenu)
Modified Actions:
ctrl+click(orctrl+rclick) - Ctrl+Click (or Cmd+Click on Mac)shift+click(orshift+rclick) - Shift+Clickalt+click(oralt+rclick) - Alt+Clickctrl+shift+click- Multiple modifiers- Any combination of modifiers
Sequences:
click right_click(orclick rclick) - Click then right-click within 500msclick click- Double click sequencectrl+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:
hx-valscontains static values (e.g.,c_idfrom Command)hx-vals-extra.dictcontains additional static values (merged)hx-vals-extra.jsspecifies 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 elementcombinationsJson(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 clickedis_inside- Boolean indicating if the click was inside the element- For
click:trueif clicked inside element,falseif clicked outside - For
right_click: alwaystrue(only triggers when clicking on element)
- For
has_focus- Boolean indicating if the element had focus when the action triggeredclicked_inside- Boolean indicating if the click was inside the element or outside
Parameter Details
has_focus:
trueif the registered element currently has focusfalseotherwise- Useful for knowing if the element was the active element
clicked_inside:
- For
clickactions:trueif clicked on/inside the element,falseif clicked outside - For
right_clickactions: alwaystrue(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 actionhx_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_valsis merged with command's values (stored inhx-vals-extra), preservingc_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+clickuses Ctrl key - Mac:
ctrl+clickuses 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
contenteditableelement
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_clickaction 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
documentforclickandcontextmenuevents - 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:
rclickcan be used interchangeably withright_clickfor shorter syntax