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

345 lines
9.5 KiB
Markdown

# Keyboard Support - Test Instructions
## ⚠️ Breaking Change
**Version 2.0** uses HTMX configuration objects instead of simple URL strings. The old format is **not supported**.
**Old format (no longer supported)**:
```javascript
{"A": "/url"}
```
**New format (required)**:
```javascript
{"A": {"hx-post": "/url"}}
```
## Files
- `keyboard_support.js` - Main keyboard support library with smart timeout logic
- `test_keyboard_support.html` - Test page to verify functionality
## Key Features
### Multiple Simultaneous Triggers
**IMPORTANT**: If multiple elements listen to the same combination, **ALL** of them will be triggered:
```javascript
add_keyboard_support('modal', '{"esc": "/close-modal"}');
add_keyboard_support('editor', '{"esc": "/cancel-edit"}');
add_keyboard_support('sidebar', '{"esc": "/hide-sidebar"}');
// Pressing ESC will trigger all 3 URLs simultaneously
```
This is crucial for use cases like the ESC key, which often needs to cancel multiple actions at once (close modal, cancel edit, hide panels, etc.).
### Smart Timeout Logic (Longest Match)
The library uses **a single global timeout** based on the sequence state, not on individual elements:
**Key principle**: If **any element** has a longer sequence possible, **all matching elements wait**.
Examples:
**Example 1**: Three elements, same combination
```javascript
add_keyboard_support('elem1', '{"esc": "/url1"}');
add_keyboard_support('elem2', '{"esc": "/url2"}');
add_keyboard_support('elem3', '{"esc": "/url3"}');
// Press ESC → ALL 3 trigger immediately (no longer sequences exist)
```
**Example 2**: Mixed - one has longer sequence
```javascript
add_keyboard_support('elem1', '{"A": "/url1"}');
add_keyboard_support('elem2', '{"A": "/url2"}');
add_keyboard_support('elem3', '{"A": "/url3", "A B": "/url3b"}');
// Press A:
// - elem3 has "A B" possible → EVERYONE WAITS 500ms
// - If B arrives: only elem3 triggers with "A B"
// - If timeout expires: elem1, elem2, elem3 ALL trigger with "A"
```
**Example 3**: Different combinations
```javascript
add_keyboard_support('elem1', '{"A B": "/url1"}');
add_keyboard_support('elem2', '{"C D": "/url2"}');
// Press A: elem1 waits for B, elem2 not affected
// Press C: elem2 waits for D, elem1 not affected
```
The timeout is tied to the **sequence being typed**, not to individual elements.
### Smart Timeout Logic (Longest Match)
Keyboard shortcuts are **disabled** when typing in input fields:
- `<input>` elements
- `<textarea>` elements
- Any `contenteditable` element
This ensures normal typing (Ctrl+C, Ctrl+A, etc.) works as expected in forms.
## How to Test
1. **Download both files** to the same directory
2. **Open `test_keyboard_support.html`** in a web browser
3. **Try the configured shortcuts**:
- `a` - Simple key (waits if "A B" might follow)
- `Ctrl+S` - Save combination (immediate)
- `Ctrl+C` - Copy combination (waits because "Ctrl+C C" exists)
- `A B` - Sequence (waits because "A B C" exists)
- `A B C` - Triple sequence (triggers immediately)
- `Ctrl+C C` - Press Ctrl+C together, release, then press C alone
- `Ctrl+C Ctrl+C` - Press Ctrl+C, keep Ctrl, release C, press C again
- `shift shift` - Press Shift twice in sequence
- `esc` - Escape key (immediate)
4. **Test focus behavior**:
- Click on the test element to focus it (turns blue)
- Try shortcuts with focus
- Click outside to remove focus
- Try shortcuts without focus
- The log shows whether the element had focus when triggered
5. **Test input protection**:
- Try typing in the input field
- Use Ctrl+C, Ctrl+A, etc. - should work normally
- Shortcuts should NOT trigger while typing in input
## Expected Behavior
### Smart Timeout Examples
**Scenario 1**: Only "A" is configured (no element has "A B")
- Press A → Triggers **immediately**
**Scenario 2**: At least one element has "A B"
- Press A → **ALL elements with "A" wait 500ms**
- If B pressed within 500ms → Only elements with "A B" trigger
- If timeout expires → ALL elements with "A" trigger
**Scenario 3**: "A", "A B", and "A B C" all configured (same or different elements)
- Press A → Waits (because "A B" exists)
- Press B → Waits (because "A B C" exists)
- Press C → Triggers "A B C" **immediately**
**Scenario 4**: Multiple elements, ESC on all
```javascript
add_keyboard_support('modal', '{"esc": "/close"}');
add_keyboard_support('editor', '{"esc": "/cancel"}');
```
- Press ESC → **Both trigger simultaneously** (no longer sequences)
## Integration in Your Project
## Integration in Your Project
### Configuration Format
The library now uses **HTMX configuration objects** instead of simple URL strings:
```python
# New format with HTMX configuration
combinations = {
"Ctrl+S": {
"hx-post": "/save-url",
"hx-target": "#result",
"hx-swap": "innerHTML"
},
"A B": {
"hx-post": "/sequence-url",
"hx-vals": {"extra": "data"}
},
"esc": {
"hx-get": "/cancel-url"
}
}
# This will generate the JavaScript call
f"add_keyboard_support('{element_id}', '{json.dumps(combinations)}')"
```
### Supported HTMX Attributes
You can use any HTMX attribute in the configuration object:
**HTTP Methods** (one required):
- `hx-post` - POST request
- `hx-get` - GET request
- `hx-put` - PUT request
- `hx-delete` - DELETE request
- `hx-patch` - PATCH request
**Common Options**:
- `hx-target` - Target element selector
- `hx-swap` - Swap strategy (innerHTML, outerHTML, etc.)
- `hx-vals` - Additional values to send (object)
- `hx-headers` - Custom headers (object)
- `hx-select` - Select specific content from response
- `hx-confirm` - Confirmation message
All other `hx-*` attributes are supported and will be converted to the appropriate htmx.ajax() parameters.
### Automatic Parameters
The library automatically adds these parameters to every request:
- `combination` - The combination that triggered the action (e.g., "Ctrl+S")
- `has_focus` - Boolean indicating if the element had focus
- `is_inside` - Boolean indicating if the focus is inside the element (element itself or any child)
Example final request:
```javascript
htmx.ajax('POST', '/save-url', {
target: '#result',
swap: 'innerHTML',
values: {
extra: "data", // from hx-vals
combination: "Ctrl+S", // automatic
has_focus: true, // automatic
is_inside: true // automatic
}
})
```
## Integration Examples
### Basic Usage
```python
combinations = {
"Ctrl+S": {
"hx-post": "/save"
}
}
```
### With Target and Swap
```python
combinations = {
"Ctrl+D": {
"hx-delete": "/item",
"hx-target": "#item-list",
"hx-swap": "outerHTML"
}
}
```
### With Extra Values
```python
combinations = {
"Ctrl+N": {
"hx-post": "/create",
"hx-vals": json.dumps({"type": "quick", "mode": "keyboard"})
}
}
```
### Multiple Elements Example
```python
# Modal close
modal_combinations = {
"esc": {
"hx-post": "/modal/close",
"hx-target": "#modal",
"hx-swap": "outerHTML"
}
}
# Editor cancel
editor_combinations = {
"esc": {
"hx-post": "/editor/cancel",
"hx-target": "#editor",
"hx-swap": "innerHTML"
}
}
# Both will trigger when ESC is pressed
f"add_keyboard_support('modal', '{json.dumps(modal_combinations)}')"
f"add_keyboard_support('editor', '{json.dumps(editor_combinations)}')"
```
### Removing Keyboard Support
When you no longer need keyboard support for an element:
```python
# Remove keyboard support
f"remove_keyboard_support('{element_id}')"
```
**Behavior**:
- Removes the element from the keyboard registry
- If this was the last element, automatically detaches global event listeners
- Cleans up all associated state (timeouts, snapshots, etc.)
- Other elements continue to work normally
**Example**:
```javascript
// Add support
add_keyboard_support('modal', '{"esc": {"hx-post": "/close"}}');
// Later, remove support
remove_keyboard_support('modal');
// If no other elements remain, keyboard listeners are completely removed
```
## API Reference
### add_keyboard_support(elementId, combinationsJson)
Adds keyboard support to an element.
**Parameters**:
- `elementId` (string): ID of the HTML element
- `combinationsJson` (string): JSON string of combinations with HTMX configs
**Returns**: void
### remove_keyboard_support(elementId)
Removes keyboard 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
## Technical Details
### Trie-based Matching
The library uses a prefix tree (trie) data structure:
- Each node represents a keyboard snapshot (set of pressed keys)
- Leaf nodes contain the HTMX configuration object
- Intermediate nodes indicate longer sequences exist
- Enables efficient O(n) matching where n is sequence length
### HTMX Integration
Configuration objects are mapped to htmx.ajax() calls:
- `hx-*` attributes are converted to camelCase parameters
- HTTP method is extracted from `hx-post`, `hx-get`, etc.
- `combination`, `has_focus`, and `is_inside` are automatically added to values
- All standard HTMX options are supported
### Key Normalization
- Case-insensitive: "ctrl" = "Ctrl" = "CTRL"
- Mapped keys: "Control" → "ctrl", "Escape" → "esc", "Delete" → "del"
- Simultaneous keys represented as sorted sets
## Notes
- The test page mocks `htmx.ajax` to display results in the console
- In production, real AJAX calls will be made to your backend
- Sequence timeout is 500ms between keys
- Maximum 10 snapshots kept in history to prevent memory issues