439 lines
11 KiB
Markdown
439 lines
11 KiB
Markdown
# 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):
|
|
|
|
```javascript
|
|
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**:
|
|
```javascript
|
|
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**:
|
|
```javascript
|
|
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**:
|
|
```javascript
|
|
// 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
|
|
|
|
```python
|
|
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
|
|
|
|
```python
|
|
combinations = {
|
|
"click": {
|
|
"hx-post": "/single-click"
|
|
},
|
|
"click click": {
|
|
"hx-post": "/double-click-sequence"
|
|
},
|
|
"click right_click": {
|
|
"hx-post": "/click-then-right-click"
|
|
}
|
|
}
|
|
```
|
|
|
|
### Multiple Elements
|
|
|
|
```python
|
|
# 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**:
|
|
```javascript
|
|
// 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:
|
|
```html
|
|
<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:
|
|
|
|
```javascript
|
|
// 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
|
|
|
|
```javascript
|
|
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":
|
|
|
|
```javascript
|
|
// 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)**:
|
|
```python
|
|
@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
|
|
|
|
```javascript
|
|
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
|
|
|
|
```javascript
|
|
const combinations = {
|
|
"click": {
|
|
"hx-post": "/draw-point"
|
|
},
|
|
"ctrl+click": {
|
|
"hx-post": "/draw-special"
|
|
},
|
|
"click click": {
|
|
"hx-post": "/confirm-action"
|
|
}
|
|
};
|
|
```
|
|
|
|
### Drag-and-Drop Alternative
|
|
|
|
```javascript
|
|
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 |