9.5 KiB
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):
{"A": "/url"}
New format (required):
{"A": {"hx-post": "/url"}}
Files
keyboard_support.js- Main keyboard support library with smart timeout logictest_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:
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
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
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
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
contenteditableelement
This ensures normal typing (Ctrl+C, Ctrl+A, etc.) works as expected in forms.
How to Test
-
Download both files to the same directory
-
Open
test_keyboard_support.htmlin a web browser -
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 aloneCtrl+C Ctrl+C- Press Ctrl+C, keep Ctrl, release C, press C againshift shift- Press Shift twice in sequenceesc- Escape key (immediate)
-
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
-
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
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:
# 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 requesthx-get- GET requesthx-put- PUT requesthx-delete- DELETE requesthx-patch- PATCH request
Common Options:
hx-target- Target element selectorhx-swap- Swap strategy (innerHTML, outerHTML, etc.)hx-vals- Additional values to send (object)hx-headers- Custom headers (object)hx-select- Select specific content from responsehx-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 focusis_inside- Boolean indicating if the focus is inside the element (element itself or any child)
Example final request:
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
combinations = {
"Ctrl+S": {
"hx-post": "/save"
}
}
With Target and Swap
combinations = {
"Ctrl+D": {
"hx-delete": "/item",
"hx-target": "#item-list",
"hx-swap": "outerHTML"
}
}
With Extra Values
combinations = {
"Ctrl+N": {
"hx-post": "/create",
"hx-vals": json.dumps({"type": "quick", "mode": "keyboard"})
}
}
Multiple Elements Example
# 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:
# 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:
// 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 elementcombinationsJson(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, andis_insideare 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.ajaxto 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