# 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-vals-extra` - Extra values to merge (see below)
- `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.

### Dynamic Values with hx-vals-extra

The `hx-vals-extra` attribute allows adding dynamic values computed at event time, without overwriting the static `hx-vals`.

**Format:**
```javascript
{
  "hx-vals": {"c_id": "command_id"},           // Static values (preserved)
  "hx-vals-extra": {
    "dict": {"key": "value"},                  // Additional static values (merged)
    "js": "functionName"                       // JS function to call (merged)
  }
}
```

**How values are merged:**
1. `hx-vals` - static values (e.g., `c_id` from Command)
2. `hx-vals-extra.dict` - additional static values
3. `hx-vals-extra.js` - function called with `(event, element, combinationStr)`, result merged

**JavaScript function example:**
```javascript
function getKeyboardContext(event, element, combination) {
  return {
    key: event.key,
    shift: event.shiftKey,
    timestamp: Date.now()
  };
}
```

**Configuration example:**
```javascript
const combinations = {
  "Ctrl+S": {
    "hx-post": "/save",
    "hx-vals": {"c_id": "save_cmd"},
    "hx-vals-extra": {"js": "getKeyboardContext"}
  }
};
```

### 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