Files
MyFastHtml/docs/Mouse Support.md
2025-11-26 20:53:12 +01:00

11 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));

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

Basic Usage

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

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

Sequences

combinations = {
    "click": {
        "hx-post": "/single-click"
    },
    "click click": {
        "hx-post": "/double-click-sequence"
    },
    "click right_click": {
        "hx-post": "/click-then-right-click"
    }
}

Multiple Elements

# Item 1
item1_combinations = {
    "click": {"hx-post": f"/item/1/select"},
    "ctrl+click": {"hx-post": f"/item/1/toggle"}
}
f"add_mouse_support('item-1', '{json.dumps(item1_combinations)}')"

# Item 2
item2_combinations = {
    "click": {"hx-post": f"/item/2/select"},
    "ctrl+click": {"hx-post": f"/item/2/toggle"}
}
f"add_mouse_support('item-2', '{json.dumps(item2_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