Metadata-Version: 2.4
Name: cjm-fasthtml-keyboard-navigation
Version: 0.0.18
Summary: A declarative keyboard navigation framework for FastHTML applications with multi-zone focus management, mode switching, and HTMX integration.
Author-email: "Christian J. Mills" <9126128+cj-mills@users.noreply.github.com>
License: Apache-2.0
Project-URL: Repository, https://github.com/cj-mills/cjm-fasthtml-keyboard-navigation
Project-URL: Documentation, https://cj-mills.github.io/cjm-fasthtml-keyboard-navigation
Keywords: nbdev,jupyter,notebook,python
Classifier: Natural Language :: English
Classifier: Intended Audience :: Developers
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: python-fasthtml
Requires-Dist: cjm_fasthtml_tailwind
Requires-Dist: cjm_fasthtml_daisyui
Requires-Dist: cjm_fasthtml_lucide_icons
Dynamic: license-file

# cjm-fasthtml-keyboard-navigation


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

## Install

``` bash
pip install cjm_fasthtml_keyboard_navigation
```

## Project Structure

    nbs/
    ├── components/ (3)
    │   ├── hints.ipynb        # Components for displaying keyboard shortcut hints to users.
    │   ├── hints_modal.ipynb  # Modal-based keyboard shortcut reference with scannable grouped layout and `?` key trigger.
    │   └── system.ipynb       # High-level API for rendering complete keyboard navigation systems.
    ├── core/ (6)
    │   ├── actions.ipynb      # Declarative keyboard action bindings supporting HTMX triggers and JS callbacks.
    │   ├── focus_zone.ipynb   # Configuration for focusable containers with navigable items.
    │   ├── key_mapping.ipynb  # Configurable key-to-direction mappings for customizable navigation keys.
    │   ├── manager.ipynb      # Coordinates keyboard navigation across multiple zones, modes, and actions.
    │   ├── modes.ipynb        # Configuration for keyboard modes that change navigation and action behavior.
    │   └── navigation.ipynb   # Protocols and implementations for keyboard navigation within focus zones.
    ├── htmx/ (2)
    │   ├── buttons.ipynb  # Generate hidden HTMX action buttons triggered by keyboard events.
    │   └── inputs.ipynb   # Generate hidden inputs for HTMX integration with keyboard navigation.
    └── js/ (3)
        ├── coordinator.ipynb  # Global coordinator for hierarchical keyboard system management with single-listener event dispatch.
        ├── generators.ipynb   # Generate complete keyboard navigation JavaScript from configuration.
        └── utils.ipynb        # Core JavaScript utility generators for keyboard navigation.

Total: 14 notebooks across 4 directories

## Module Dependencies

``` mermaid
graph LR
    components_hints[components.hints<br/>Keyboard Hints]
    components_hints_modal[components.hints_modal<br/>Keyboard Hints Modal]
    components_system[components.system<br/>Keyboard System]
    core_actions[core.actions<br/>Key Actions]
    core_focus_zone[core.focus_zone<br/>Focus Zone]
    core_key_mapping[core.key_mapping<br/>Key Mapping]
    core_manager[core.manager<br/>Zone Manager]
    core_modes[core.modes<br/>Keyboard Modes]
    core_navigation[core.navigation<br/>Navigation Patterns]
    htmx_buttons[htmx.buttons<br/>Action Buttons]
    htmx_inputs[htmx.inputs<br/>Hidden Inputs]
    js_coordinator[js.coordinator<br/>Keyboard Coordinator]
    js_generators[js.generators<br/>Script Generators]
    js_utils[js.utils<br/>JavaScript Utilities]

    components_hints --> core_focus_zone
    components_hints --> core_manager
    components_hints --> core_actions
    components_hints_modal --> core_focus_zone
    components_hints_modal --> core_manager
    components_hints_modal --> components_hints
    components_hints_modal --> core_actions
    components_system --> components_hints
    components_system --> htmx_inputs
    components_system --> core_actions
    components_system --> js_generators
    components_system --> core_focus_zone
    components_system --> core_manager
    components_system --> htmx_buttons
    core_actions --> core_key_mapping
    core_focus_zone --> core_navigation
    core_manager --> core_navigation
    core_manager --> core_key_mapping
    core_manager --> core_modes
    core_manager --> core_actions
    core_manager --> core_focus_zone
    core_modes --> core_navigation
    htmx_buttons --> core_actions
    htmx_buttons --> core_focus_zone
    htmx_buttons --> core_manager
    htmx_inputs --> core_focus_zone
    htmx_inputs --> core_manager
    js_generators --> js_coordinator
    js_generators --> core_actions
    js_generators --> core_focus_zone
    js_generators --> core_manager
    js_generators --> js_utils
```

*32 cross-module dependencies detected*

## CLI Reference

No CLI commands found in this project.

## Module Overview

Detailed documentation for each module in the project:

### Key Actions (`actions.ipynb`)

> Declarative keyboard action bindings supporting HTMX triggers and JS
> callbacks.

#### Import

``` python
from cjm_fasthtml_keyboard_navigation.core.actions import (
    KeyAction
)
```

#### Classes

``` python
@dataclass
class KeyAction:
    "A keyboard shortcut binding."
    
    key: str  # JavaScript key name (e.g., "Enter", " ", "ArrowUp")
    modifiers: frozenset[str] = field(...)
    htmx_trigger: Optional[str]  # ID of hidden button to click
    js_callback: Optional[str]  # JS function name to call
    mode_enter: Optional[str]  # mode name to enter
    mode_exit: bool = False  # exit current mode (return to default)
    prevent_default: bool = True  # call e.preventDefault()
    stop_propagation: bool = False  # call e.stopPropagation()
    zone_ids: Optional[tuple[str, ...]]  # only in these zones (None = all)
    mode_names: Optional[tuple[str, ...]]  # only in these modes (None = all)
    not_modes: Optional[tuple[str, ...]]  # not in these modes
    custom_condition: Optional[str]  # raw JS expression for additional conditions
    description: str = ''  # human-readable description for hints
    hint_group: str = 'General'  # grouping for keyboard hints display
    show_in_hints: bool = True  # whether to show in keyboard hints
    
    def matches_context(
            self,
            zone_id: str,  # current active zone
            mode_name: str  # current mode
        ) -> bool:         # True if action is valid in this context
        "Check if action is valid for given zone and mode."
    
    def get_display_key(self) -> str: # formatted key combo for display
            """Get formatted key combination for display."""
            return format_key_combo(self.key, self.modifiers)
    
        def to_js_config(self) -> dict: # JavaScript-compatible configuration
        "Get formatted key combination for display."
    
    def to_js_config(self) -> dict: # JavaScript-compatible configuration
            """Convert to JavaScript configuration object."""
            return {
                "key": self.key,
        "Convert to JavaScript configuration object."
```

### Action Buttons (`buttons.ipynb`)

> Generate hidden HTMX action buttons triggered by keyboard events.

#### Import

``` python
from cjm_fasthtml_keyboard_navigation.htmx.buttons import (
    build_htmx_trigger,
    render_action_button,
    render_action_buttons
)
```

#### Functions

``` python
def build_htmx_trigger(
    key: str,                           # JavaScript key name
    modifiers: frozenset[str] = frozenset(),  # modifier keys
    input_selector: str = "input, textarea, select, [contenteditable]"  # input elements to exclude
) -> str:                               # HTMX trigger expression
    "Build HTMX trigger expression for keyboard event."
```

``` python
def render_action_button(
    action: KeyAction,           # the action configuration
    url: str,                    # POST URL for the action
    target: str,                 # HTMX target selector
    include: str = "",           # hx-include selector
    swap: str = "outerHTML",     # hx-swap value
    vals: dict | None = None,    # hx-vals dictionary (JSON values to include in request)
    use_htmx_trigger: bool = False,  # use hx-trigger (False = JS triggerClick only)
    input_selector: str = "input, textarea, select, [contenteditable]"  # inputs to exclude from trigger
) -> Button | None:              # hidden button or None if not HTMX action
    "Render a hidden HTMX button for a keyboard action."
```

``` python
def render_action_buttons(
    manager: ZoneManager,                         # the zone manager configuration
    url_map: dict[str, str],                      # action button ID -> URL
    target_map: dict[str, str],                   # action button ID -> target selector
    include_map: dict[str, str] | None = None,    # action button ID -> include selector
    swap_map: dict[str, str] | None = None,       # action button ID -> swap value
    vals_map: dict[str, dict] | None = None,      # action button ID -> hx-vals dict
    use_htmx_triggers: bool = False,              # use hx-trigger (False = JS triggerClick only)
    container_id: str = "kb-action-buttons"       # container element ID
) -> Div:                                         # container with all action buttons
    "Render all hidden HTMX action buttons for keyboard navigation."
```

### Keyboard Coordinator (`coordinator.ipynb`)

> Global coordinator for hierarchical keyboard system management with
> single-listener event dispatch.

#### Import

``` python
from cjm_fasthtml_keyboard_navigation.js.coordinator import (
    js_coordinator_setup
)
```

#### Functions

``` python
def js_coordinator_setup() -> str: # JavaScript coordinator singleton code
    "Generate the global keyboard coordinator singleton."
```

### Focus Zone (`focus_zone.ipynb`)

> Configuration for focusable containers with navigable items.

#### Import

``` python
from cjm_fasthtml_keyboard_navigation.core.focus_zone import (
    FocusZone
)
```

#### Classes

``` python
@dataclass
class FocusZone:
    "A focusable container with navigable items."
    
    id: str  # HTML element ID of the container
    item_selector: Optional[str]  # CSS selector for items (None = scroll only)
    navigation: Union[NavigationPattern, LinearVertical] = field(...)
    navigation_throttle_ms: int = 0  # minimum ms between navigation events (0 = no throttle)
    item_focus_classes: tuple[str, ...] = (str(ring(2)), str(ring_dui.primary))  # CSS classes for focused item
    item_focus_attribute: str = 'data-focused'  # attribute set to "true" on focused item
    zone_focus_classes: tuple[str, ...] = (str(ring(2)), str(ring_dui.primary), str(inset_ring(2)))
    data_attributes: tuple[str, ...] = ()  # data attributes to extract from focused item
    on_focus_change: Optional[str]  # called when focused item changes
    on_navigate: Optional[str]  # called on any navigation (for side effects like audition)
    on_zone_enter: Optional[str]  # called when zone becomes active
    on_zone_leave: Optional[str]  # called when zone loses focus
    scroll_behavior: str = 'smooth'  # "smooth" or "auto"
    scroll_block: str = 'nearest'  # "start", "center", "end", "nearest"
    hidden_input_prefix: str = ''  # prefix for auto-generated hidden input IDs
    initial_index: int = 0  # initial focused item index
    
    def has_items(self) -> bool: # True if zone has selectable items
            """Check if zone has selectable items."""
            return self.item_selector is not None
    
        def get_hidden_input_id(
            self,
            attr: str  # the data attribute name
        ) -> str:      # the hidden input element ID
        "Check if zone has selectable items."
    
    def get_hidden_input_id(
            self,
            attr: str  # the data attribute name
        ) -> str:      # the hidden input element ID
        "Get the hidden input ID for a data attribute."
    
    def to_js_config(self) -> dict: # JavaScript-compatible configuration
            """Convert to JavaScript configuration object."""
            return {
                "id": self.id,
        "Convert to JavaScript configuration object."
```

### Script Generators (`generators.ipynb`)

> Generate complete keyboard navigation JavaScript from configuration.

#### Import

``` python
from cjm_fasthtml_keyboard_navigation.js.generators import (
    js_zone_state,
    js_focus_management,
    js_zone_switching,
    js_navigation,
    js_mode_management,
    js_action_dispatch,
    js_keyboard_handler,
    js_state_notification,
    js_initialization,
    js_global_api,
    generate_keyboard_script
)
```

#### Functions

``` python
def js_zone_state() -> str: # JavaScript state and getter/setter code
    "Generate JavaScript code for zone state management."
```

``` python
def js_focus_management() -> str: # JavaScript focus management code
    "Generate JavaScript code for focus management."
```

``` python
def js_zone_switching() -> str: # JavaScript zone switching code
    "Generate JavaScript code for zone switching."
```

``` python
def js_navigation() -> str: # JavaScript navigation code
    """Generate JavaScript code for item navigation."""
    return '''
// === Navigation ===
// Track last navigation time per zone for throttling
let lastNavigationTime = {};

function getNavigationPattern(zoneId) {
    // Check if mode overrides navigation
    const modeConfig = getModeConfig(currentMode);
    if (modeConfig && modeConfig.navigationOverride) {
        return modeConfig.navigationOverride;
    }
    // Use zone's pattern
    const zone = getZoneConfig(zoneId);
    return zone ? zone.navigationPattern : 'linear_vertical';
    "Generate JavaScript code for item navigation."
```

``` python
def js_mode_management() -> str: # JavaScript mode management code
    "Generate JavaScript code for mode management."
```

``` python
def js_action_dispatch() -> str: # JavaScript action dispatch code
    "Generate JavaScript code for action dispatch."
```

``` python
def js_keyboard_handler() -> str: # JavaScript keyboard handler code
    "Generate JavaScript code for keyboard event handling."
```

``` python
def js_state_notification() -> str: # JavaScript state notification code
    "Generate JavaScript code for state change notification."
```

``` python
def js_initialization() -> str: # JavaScript initialization code
    "Generate JavaScript code for initialization with focus recovery."
```

``` python
def js_global_api() -> str:  # JavaScript global API exposure code
    "Generate JavaScript code to expose control functions globally."
```

``` python
def generate_keyboard_script(
    manager: ZoneManager  # the zone manager configuration
) -> str:                 # complete JavaScript code wrapped in IIFE
    "Generate complete keyboard navigation JavaScript from ZoneManager."
```

### Keyboard Hints (`hints.ipynb`)

> Components for displaying keyboard shortcut hints to users.

#### Import

``` python
from cjm_fasthtml_keyboard_navigation.components.hints import (
    NAV_ICON_MAP,
    KEY_ICON_MAP,
    get_key_icon,
    render_hint_badge,
    create_nav_icon_hint,
    create_modifier_key_hint,
    render_hint_group,
    group_actions_by_hint_group,
    render_hints_from_actions,
    render_keyboard_hints
)
```

#### Functions

``` python
def get_key_icon(
    key_name: str,    # key name to look up (case-insensitive)
    size: int = 3     # icon size
) -> FT | None:       # icon component or None if no icon mapping
    "Get a lucide icon for a key name, if one exists."
```

``` python
def render_hint_badge(
    key_display: Union[str, FT],  # formatted key string or icon component
    description: str,              # action description
    style: str = "ghost",          # badge style (ghost, outline, soft, dash)
    auto_icon: bool = False        # auto-convert known keys to icons
) -> Div:                          # hint badge component
    "Render a single keyboard hint as a badge."
```

``` python
def create_nav_icon_hint(
    icon_name: str,      # lucide icon name (e.g., "arrow-down-up")
    description: str,    # action description
    style: str = "ghost" # badge style
) -> Div:                # hint badge with icon
    "Create a hint badge with a lucide icon."
```

``` python
def create_modifier_key_hint(
    modifier: str,       # modifier key name (e.g., "shift", "ctrl")
    key_icon_or_text: Union[str, FT],  # the main key icon or text
    description: str,    # action description
    style: str = "ghost" # badge style
) -> Div:                # hint badge with modifier + key
    "Create a hint badge with a modifier key and main key."
```

``` python
def render_hint_group(
    group_name: str,         # group header text
    hints: list[tuple[str, str]],  # list of (key_display, description) tuples
    badge_style: str = "ghost"     # badge style for this group
) -> Div:                    # group container with header and hints
    "Render a group of related keyboard hints."
```

``` python
def group_actions_by_hint_group(
    actions: tuple[KeyAction, ...]  # actions to group
) -> dict[str, list[KeyAction]]:    # grouped actions
    "Group actions by their hint_group attribute."
```

``` python
def render_hints_from_actions(
    actions: tuple[KeyAction, ...],  # actions to display hints for
    badge_style: str = "ghost"       # badge style
) -> Div:                            # container with all hint groups
    "Render keyboard hints from action configurations."
```

``` python
def render_keyboard_hints(
    manager: ZoneManager,                      # the zone manager
    include_navigation: bool = True,           # include navigation hints
    include_zone_switch: bool = True,          # include zone switching hints
    badge_style: str = "ghost",                # badge style
    container_id: str = "kb-hints",            # container element ID
    use_icons: bool = True                     # use lucide icons for nav hints
) -> Div:                                      # complete hints component
    "Render complete keyboard hints for a zone manager."
```

#### Variables

``` python
NAV_ICON_MAP = {2 items}
KEY_ICON_MAP = {9 items}
```

### Keyboard Hints Modal (`hints_modal.ipynb`)

> Modal-based keyboard shortcut reference with scannable grouped layout
> and `?` key trigger.

#### Import

``` python
from cjm_fasthtml_keyboard_navigation.components.hints_modal import (
    render_keyboard_hints_trigger,
    render_keyboard_hints_modal
)
```

#### Functions

``` python
def _render_key_combo(
    display_key: str,  # formatted key combo string (e.g., "Ctrl+Shift+\u2191")
) -> Div:              # container with kbd elements for each key part
    "Render a key combination as a sequence of kbd elements joined by `+`."
```

``` python
def _render_hint_row(
    display_key: str,  # formatted key combo string
    description: str,  # action description
) -> Div:              # single shortcut row with key and description
    "Render a single shortcut row: key combo on left, description on right."
```

``` python
def _render_modal_group(
    group_name: str,                        # group header text
    actions: list[tuple[str, str]],          # list of (display_key, description)
) -> Div:                                    # group container with header and rows
    "Render a group of related shortcuts with a header."
```

``` python
def _render_modal_body(
    manager: ZoneManager,          # keyboard zone manager
    include_navigation: bool = True,  # include \u2191/\u2193 navigation hint
    include_zone_switch: bool = True, # include \u2190/\u2192 zone switch hint
) -> Div:                             # modal body with grouped shortcuts
    "Render the modal body with grouped keyboard shortcuts."
```

``` python
def render_keyboard_hints_trigger(
    modal_id: str = "kb-hints-modal",  # ID of the modal dialog to open
    icon_size: int = 4,                 # lucide icon size
) -> Button:                            # ghost button with keyboard icon
    "Render a keyboard icon button that opens the hints modal."
```

``` python
def _render_question_mark_listener(
    modal_id: str,  # ID of the modal dialog to toggle
) -> Script:        # script element with global `?` key listener
    """
    Render a global `?` key listener that toggles the hints modal.
    
    Uses a named function stored on `window` so that HTMX re-renders
    replace the previous listener instead of accumulating duplicates.
    """
```

``` python
def render_keyboard_hints_modal(
    manager: ZoneManager,               # keyboard zone manager with actions configured
    modal_id: str = "kb-hints-modal",    # HTML ID for the modal dialog
    include_navigation: bool = True,     # include \u2191/\u2193 navigation hint
    include_zone_switch: bool = True,    # include zone switch hint (auto-hidden for single zone)
    enable_question_mark_key: bool = True,  # add global `?` key listener
    title: str = "Keyboard Shortcuts",   # modal title text
) -> tuple[FT, FT, FT]:                 # (modal_dialog, trigger_button, question_mark_script)
    """
    Render a modal-based keyboard shortcut reference.
    
    Returns three components:
    - `modal_dialog`: The Dialog element (place anywhere in page)
    - `trigger_button`: Small keyboard icon button (place in step header)
    - `question_mark_script`: Global `?` key listener Script (place in page)
    
    If `enable_question_mark_key` is False, `question_mark_script` is an empty Div.
    """
```

### Hidden Inputs (`inputs.ipynb`)

> Generate hidden inputs for HTMX integration with keyboard navigation.

#### Import

``` python
from cjm_fasthtml_keyboard_navigation.htmx.inputs import (
    render_zone_hidden_inputs,
    render_hidden_inputs,
    build_include_selector,
    build_all_zones_include_selector
)
```

#### Functions

``` python
def render_zone_hidden_inputs(
    zone: FocusZone  # the focus zone configuration
) -> list:           # list of Hidden input components
    "Render hidden inputs for a single zone's data attributes."
```

``` python
def render_hidden_inputs(
    manager: ZoneManager,            # the zone manager configuration
    include_state: bool = False,     # include state tracking inputs
    container_id: str = "kb-hidden-inputs"  # container element ID
) -> Div:                            # container with all hidden inputs
    """
    Render all hidden inputs for keyboard navigation.
    
    Deduplicates inputs by ID - zones with the same hidden_input_prefix
    will share inputs rather than creating duplicates.
    """
```

``` python
def build_include_selector(
    zone: FocusZone,              # the zone to include inputs from
    include_state: bool = False   # include state inputs
) -> str:                         # CSS selector for hx-include
    "Build hx-include selector for zone's hidden inputs."
```

``` python
def build_all_zones_include_selector(
    manager: ZoneManager,         # the zone manager
    include_state: bool = False   # include state inputs
) -> str:                         # CSS selector for all zones
    """
    Build hx-include selector for all zones' hidden inputs.
    
    Deduplicates selectors - zones with the same hidden_input_prefix
    will only include each input once.
    """
```

### Key Mapping (`key_mapping.ipynb`)

> Configurable key-to-direction mappings for customizable navigation
> keys.

#### Import

``` python
from cjm_fasthtml_keyboard_navigation.core.key_mapping import (
    ARROW_KEYS,
    WASD_KEYS,
    VIM_KEYS,
    NUMPAD_KEYS,
    ARROWS_AND_WASD,
    ARROWS_AND_VIM,
    KEY_DISPLAY_MAP,
    KeyMapping,
    format_key_for_display,
    format_key_combo
)
```

#### Functions

``` python
def format_key_for_display(
    key: str  # the JavaScript key name
) -> str:     # human-readable display string
    "Format a key name for user display."
```

``` python
def format_key_combo(
    key: str,                        # the main key
    modifiers: frozenset[str] = frozenset() # modifier keys (shift, ctrl, alt, meta)
) -> str:                            # formatted string like "Ctrl+Shift+A"
    "Format a key combination for display."
```

#### Classes

``` python
class KeyMapping:
    "Maps physical keys to navigation directions."
    
    def get_direction(
            self,
            key: str  # the pressed key (e.g., "ArrowUp", "w")
        ) -> str | None: # the direction ("up", "down", "left", "right") or None
        "Get direction for a given key press."
    
    def all_keys(self) -> tuple[str, ...]: # all mapped keys
            """Return all mapped keys."""
            return self.up + self.down + self.left + self.right
    
        def to_js_map(self) -> dict[str, str]: # {key: direction} mapping
        "Return all mapped keys."
    
    def to_js_map(self) -> dict[str, str]: # {key: direction} mapping
            """Convert to JavaScript-compatible key-to-direction map."""
            result = {}
            for key in self.up
        "Convert to JavaScript-compatible key-to-direction map."
```

#### Variables

``` python
ARROW_KEYS
WASD_KEYS
VIM_KEYS
NUMPAD_KEYS
ARROWS_AND_WASD
ARROWS_AND_VIM
KEY_DISPLAY_MAP: dict[str, str]
```

### Zone Manager (`manager.ipynb`)

> Coordinates keyboard navigation across multiple zones, modes, and
> actions.

#### Import

``` python
from cjm_fasthtml_keyboard_navigation.core.manager import (
    ZoneManager
)
```

#### Classes

``` python
@dataclass
class ZoneManager:
    "Coordinates keyboard navigation across zones."
    
    zones: tuple[FocusZone, ...]  # all focus zones
    system_id: Optional[str]  # unique ID for coordinator registration (defaults to initial zone ID)
    prev_zone_key: str = 'ArrowLeft'  # key to switch to previous zone
    next_zone_key: str = 'ArrowRight'  # key to switch to next zone
    zone_switch_modifiers: frozenset[str] = field(...)
    wrap_zones: bool = True  # wrap from last zone to first
    key_mapping: KeyMapping = field(...)
    initial_zone_id: Optional[str]  # defaults to first zone
    modes: tuple[KeyboardMode, ...] = ()  # custom modes (navigation mode is implicit)
    default_mode: str = 'navigation'  # mode to return to after exiting others
    actions: tuple[KeyAction, ...] = ()  # keyboard action bindings
    on_zone_change: Optional[str]  # called when active zone changes
    on_mode_change: Optional[str]  # called when mode changes
    on_state_change: Optional[str]  # called on any state change (for persistence)
    skip_when_input_focused: bool = True  # ignore keys in input/textarea
    input_selector: str = 'input, textarea, select, [contenteditable]'  # elements to skip
    htmx_settle_event: str = 'htmx:afterSettle'  # event to reinitialize on
    expose_state_globally: bool = False  # expose state on window object
    global_state_name: str = 'keyboardNavState'  # name for global state
    state_hidden_inputs: bool = False  # write state to hidden inputs
    
    def get_zone(
            self,
            zone_id: str  # zone ID to find
        ) -> Optional[FocusZone]: # the zone or None
        "Get zone by ID."
    
    def get_initial_zone_id(self) -> str: # the initial zone ID
            """Get initial zone ID."""
            return self.initial_zone_id or self.zones[0].id
    
        def get_all_modes(self) -> tuple[KeyboardMode, ...]: # all modes including default
        "Get initial zone ID."
    
    def get_all_modes(self) -> tuple[KeyboardMode, ...]: # all modes including default
            """Get all modes including the default navigation mode."""
            return (NAVIGATION_MODE,) + self.modes
    
        def get_mode(
            self,
            mode_name: str  # mode name to find
        ) -> Optional[KeyboardMode]: # the mode or None
        "Get all modes including the default navigation mode."
    
    def get_mode(
            self,
            mode_name: str  # mode name to find
        ) -> Optional[KeyboardMode]: # the mode or None
        "Get mode by name."
    
    def get_actions_for_context(
            self,
            zone_id: str,  # current zone
            mode_name: str  # current mode
        ) -> list[KeyAction]: # actions valid in this context
        "Get actions valid for given zone and mode."
    
    def get_all_data_attributes(self) -> set[str]: # unique data attributes across all zones
            """Get all unique data attributes from all zones."""
            attrs = set()
            for zone in self.zones
        "Get all unique data attributes from all zones."
    
    def to_js_config(self) -> dict: # JavaScript-compatible configuration
            """Convert to JavaScript configuration object."""
            return {
                "systemId": self.system_id,
        "Convert to JavaScript configuration object."
```

### Keyboard Modes (`modes.ipynb`)

> Configuration for keyboard modes that change navigation and action
> behavior.

#### Import

``` python
from cjm_fasthtml_keyboard_navigation.core.modes import (
    NAVIGATION_MODE,
    KeyboardMode
)
```

#### Classes

``` python
@dataclass
class KeyboardMode:
    "A named mode that changes keyboard behavior."
    
    name: str  # unique mode name (e.g., "navigation", "split", "audition")
    enter_key: Optional[str]  # key to enter mode (None = programmatic only)
    enter_modifiers: frozenset[str] = field(...)
    exit_key: str = 'Escape'  # key to exit mode
    exit_modifiers: frozenset[str] = field(...)
    zone_ids: Optional[tuple[str, ...]]  # only available in these zones (None = all)
    navigation_override: Optional[NavigationPattern]  # override zone's navigation pattern
    on_enter: Optional[str]  # called when entering mode
    on_exit: Optional[str]  # called when exiting mode
    indicator_text: Optional[str]  # text shown in UI when mode is active
    exit_on_zone_change: bool = True  # exit mode when switching zones
    
    def is_available_in_zone(
            self,
            zone_id: str  # the zone to check
        ) -> bool:        # True if mode is available in zone
        "Check if mode is available in given zone."
    
    def to_js_config(self) -> dict: # JavaScript-compatible configuration
            """Convert to JavaScript configuration object."""
            return {
                "name": self.name,
        "Convert to JavaScript configuration object."
```

#### Variables

``` python
NAVIGATION_MODE
```

### Navigation Patterns (`navigation.ipynb`)

> Protocols and implementations for keyboard navigation within focus
> zones.

#### Import

``` python
from cjm_fasthtml_keyboard_navigation.core.navigation import (
    Direction,
    NavigationPattern,
    LinearVertical,
    LinearHorizontal,
    ScrollOnly,
    Grid
)
```

#### Classes

``` python
@runtime_checkable
class NavigationPattern(Protocol):
    "Protocol for navigation within a focus zone."
    
    def name(self) -> str: # unique identifier for this pattern
            """Return the pattern name."""
            ...
    
        def get_next_index(
            self,
            current: int,      # current focused index
            direction: Direction, # navigation direction
            total: int,        # total number of items
            columns: int = 1   # number of columns (for grid navigation)
        ) -> int:              # the new index after navigation
        "Return the pattern name."
    
    def get_next_index(
            self,
            current: int,      # current focused index
            direction: Direction, # navigation direction
            total: int,        # total number of items
            columns: int = 1   # number of columns (for grid navigation)
        ) -> int:              # the new index after navigation
        "Calculate next index given current position and direction."
    
    def get_supported_directions(self) -> tuple[Direction, ...]: # directions this pattern responds to
        "Return which arrow key directions this pattern handles."
```

``` python
@dataclass
class LinearVertical:
    "Up/Down navigation through a vertical list."
    
    wrap: bool = False  # wrap from last item to first (and vice versa)
    
    def name(self) -> str:
            """Return the pattern name."""
            return "linear_vertical"
    
        def get_supported_directions(self) -> tuple[Direction, ...]: # ("up", "down")
        "Return the pattern name."
    
    def get_supported_directions(self) -> tuple[Direction, ...]: # ("up", "down")
            """Return supported directions."""
            return ("up", "down")
    
        def get_next_index(
            self,
            current: int,      # current focused index
            direction: Direction, # "up" or "down"
            total: int,        # total number of items
            columns: int = 1   # unused for linear navigation
        ) -> int:              # the new index
        "Return supported directions."
    
    def get_next_index(
            self,
            current: int,      # current focused index
            direction: Direction, # "up" or "down"
            total: int,        # total number of items
            columns: int = 1   # unused for linear navigation
        ) -> int:              # the new index
        "Calculate next index for vertical navigation."
```

``` python
@dataclass
class LinearHorizontal:
    "Left/Right navigation through a horizontal list."
    
    wrap: bool = False  # wrap from last item to first (and vice versa)
    
    def name(self) -> str:
            """Return the pattern name."""
            return "linear_horizontal"
    
        def get_supported_directions(self) -> tuple[Direction, ...]: # ("left", "right")
        "Return the pattern name."
    
    def get_supported_directions(self) -> tuple[Direction, ...]: # ("left", "right")
            """Return supported directions."""
            return ("left", "right")
    
        def get_next_index(
            self,
            current: int,      # current focused index
            direction: Direction, # "left" or "right"
            total: int,        # total number of items
            columns: int = 1   # unused for linear navigation
        ) -> int:              # the new index
        "Return supported directions."
    
    def get_next_index(
            self,
            current: int,      # current focused index
            direction: Direction, # "left" or "right"
            total: int,        # total number of items
            columns: int = 1   # unused for linear navigation
        ) -> int:              # the new index
        "Calculate next index for horizontal navigation."
```

``` python
@dataclass
class ScrollOnly:
    "No item navigation, zone is scrollable content only."
    
    def name(self) -> str:
            """Return the pattern name."""
            return "scroll_only"
    
        def get_supported_directions(self) -> tuple[Direction, ...]: # empty tuple
        "Return the pattern name."
    
    def get_supported_directions(self) -> tuple[Direction, ...]: # empty tuple
            """Return no supported directions."""
            return ()
    
        def get_next_index(
            self,
            current: int,      # current index (unused)
            direction: Direction, # direction (unused)
            total: int,        # total items (unused)
            columns: int = 1   # columns (unused)
        ) -> int:              # always returns current
        "Return no supported directions."
    
    def get_next_index(
            self,
            current: int,      # current index (unused)
            direction: Direction, # direction (unused)
            total: int,        # total items (unused)
            columns: int = 1   # columns (unused)
        ) -> int:              # always returns current
        "Return current index unchanged."
```

``` python
@dataclass
class Grid:
    "2D grid navigation (placeholder for future implementation)."
    
    columns: int = 4  # number of columns in the grid
    wrap_horizontal: bool = True  # wrap at row edges
    wrap_vertical: bool = False  # wrap at grid top/bottom
    
    def name(self) -> str:
            """Return the pattern name."""
            return "grid"
    
        def get_supported_directions(self) -> tuple[Direction, ...]: # all four directions
        "Return the pattern name."
    
    def get_supported_directions(self) -> tuple[Direction, ...]: # all four directions
            """Return all four directions."""
            return ("up", "down", "left", "right")
    
        def get_next_index(
            self,
            current: int,      # current focused index
            direction: Direction, # navigation direction
            total: int,        # total number of items
            columns: int = 0   # override columns (0 = use self.columns)
        ) -> int:              # the new index
        "Return all four directions."
    
    def get_next_index(
            self,
            current: int,      # current focused index
            direction: Direction, # navigation direction
            total: int,        # total number of items
            columns: int = 0   # override columns (0 = use self.columns)
        ) -> int:              # the new index
        "Calculate next index for 2D grid navigation."
```

### Keyboard System (`system.ipynb`)

> High-level API for rendering complete keyboard navigation systems.

#### Import

``` python
from cjm_fasthtml_keyboard_navigation.components.system import (
    KeyboardSystem,
    render_keyboard_system,
    quick_keyboard_system
)
```

#### Functions

``` python
def _build_auto_include_map(
    manager: ZoneManager,        # the zone manager configuration
    include_state: bool = False  # include state inputs in selector
) -> dict[str, str]:             # action button ID -> include selector
    "Auto-generate include_map based on actions and their zone constraints."
```

``` python
def render_keyboard_system(
    manager: ZoneManager,                         # the zone manager configuration
    url_map: dict[str, str],                      # action button ID -> URL
    target_map: dict[str, str],                   # action button ID -> target selector
    include_map: dict[str, str] | None = None,    # action button ID -> include selector (auto-generated if None)
    swap_map: dict[str, str] | None = None,       # action button ID -> swap value
    vals_map: dict[str, dict] | None = None,      # action button ID -> hx-vals dict
    show_hints: bool = True,                      # render keyboard hints UI
    hints_badge_style: str = "ghost",             # badge style for hints
    include_state_inputs: bool = False            # include state tracking inputs
) -> KeyboardSystem:                              # complete keyboard system
    "Render complete keyboard navigation system."
```

``` python
def quick_keyboard_system(
    zones: tuple[FocusZone, ...],                # focus zones
    actions: tuple[KeyAction, ...],              # keyboard actions
    url_map: dict[str, str],                     # action URLs
    target_map: dict[str, str],                  # action targets
    **kwargs                                     # additional ZoneManager/render options
) -> KeyboardSystem:                             # complete keyboard system
    "Quick setup for simple keyboard navigation."
```

#### Classes

``` python
@dataclass
class KeyboardSystem:
    "Container for all keyboard navigation components."
    
    script: Script  # the keyboard navigation JavaScript
    hidden_inputs: Div  # hidden inputs for HTMX
    action_buttons: Div  # hidden action buttons for HTMX
    hints: Optional[Div]  # optional keyboard hints UI
    
    def all_components(self) -> tuple:  # all components as tuple
            """Return all components for easy unpacking into render."""
            components = [self.script, self.hidden_inputs, self.action_buttons]
            if self.hints
        "Return all components for easy unpacking into render."
```

### JavaScript Utilities (`utils.ipynb`)

> Core JavaScript utility generators for keyboard navigation.

#### Import

``` python
from cjm_fasthtml_keyboard_navigation.js.utils import (
    js_config_from_dict,
    js_input_detection,
    js_focus_ring_helpers,
    js_scroll_into_view,
    js_hidden_input_update,
    js_trigger_click,
    js_get_data_attributes,
    js_get_modifiers,
    js_all_utils
)
```

#### Functions

``` python
def js_config_from_dict(
    config: dict[str, Any],  # Python dict to convert
    var_name: str = "cfg"    # JavaScript variable name
) -> str:                    # JavaScript const declaration
    "Generate JavaScript const declaration from Python dict."
```

``` python
def js_input_detection(
    selector: str = "input, textarea, select, [contenteditable='true']"  # CSS selector for input elements
) -> str:  # JavaScript function definition
    "Generate JavaScript function to detect if input element is focused."
```

``` python
def js_focus_ring_helpers(
    default_classes: tuple[str, ...] = (str(ring(2)), str(ring_dui.primary))  # default focus ring CSS classes
) -> str:  # JavaScript function definitions
    "Generate JavaScript functions for adding/removing focus ring classes."
```

``` python
def js_scroll_into_view(
    behavior: str = "smooth",  # "smooth" or "auto"
    block: str = "nearest"     # "start", "center", "end", "nearest"
) -> str:  # JavaScript function definition
    "Generate JavaScript function to scroll element into view."
```

``` python
def js_hidden_input_update() -> str: # JavaScript function definition
    "Generate JavaScript function to update hidden input values."
```

``` python
def js_trigger_click() -> str: # JavaScript function definition
    "Generate JavaScript function to programmatically click a button."
```

``` python
def js_get_data_attributes() -> str: # JavaScript function definition
    "Generate JavaScript function to extract data attributes from element."
```

``` python
def js_get_modifiers() -> str: # JavaScript function definition
    "Generate JavaScript function to extract modifier keys from event."
```

``` python
def js_all_utils(
    input_selector: str = "input, textarea, select, [contenteditable='true']",  # input element selector
    default_focus_classes: tuple[str, ...] = (str(ring(2)), str(ring_dui.primary)),  # focus ring classes
    scroll_behavior: str = "smooth",  # scroll behavior
    scroll_block: str = "nearest"     # scroll block alignment
) -> str:  # all utility functions combined
    "Generate all JavaScript utility functions."
```
