A declarative keyboard navigation framework for FastHTML applications with multi-zone focus management, mode switching, and HTMX integration.
Project description
cjm-fasthtml-keyboard-navigation
Install
pip install cjm_fasthtml_keyboard_navigation
Project Structure
nbs/
├── components/ (2)
│ ├── hints.ipynb # Components for displaying keyboard shortcut hints to users.
│ └── 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: 13 notebooks across 4 directories
Module Dependencies
graph LR
components_hints[components.hints<br/>Keyboard Hints]
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_actions
components_hints --> core_manager
components_system --> htmx_inputs
components_system --> core_focus_zone
components_system --> core_actions
components_system --> core_manager
components_system --> js_generators
components_system --> htmx_buttons
components_system --> components_hints
core_actions --> core_key_mapping
core_focus_zone --> core_navigation
core_manager --> core_navigation
core_manager --> core_focus_zone
core_manager --> core_actions
core_manager --> core_key_mapping
core_manager --> core_modes
core_modes --> core_navigation
htmx_buttons --> core_focus_zone
htmx_buttons --> core_actions
htmx_buttons --> core_manager
htmx_inputs --> core_focus_zone
htmx_inputs --> core_manager
js_generators --> js_utils
js_generators --> core_focus_zone
js_generators --> core_actions
js_generators --> core_manager
js_generators --> js_coordinator
28 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
from cjm_fasthtml_keyboard_navigation.core.actions import (
KeyAction
)
Classes
@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
from cjm_fasthtml_keyboard_navigation.htmx.buttons import (
build_htmx_trigger,
render_action_button,
render_action_buttons
)
Functions
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."
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."
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
from cjm_fasthtml_keyboard_navigation.js.coordinator import (
js_coordinator_setup
)
Functions
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
from cjm_fasthtml_keyboard_navigation.core.focus_zone import (
FocusZone
)
Classes
@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
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
def js_zone_state() -> str: # JavaScript state and getter/setter code
"Generate JavaScript code for zone state management."
def js_focus_management() -> str: # JavaScript focus management code
"Generate JavaScript code for focus management."
def js_zone_switching() -> str: # JavaScript zone switching code
"Generate JavaScript code for zone switching."
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."
def js_mode_management() -> str: # JavaScript mode management code
"Generate JavaScript code for mode management."
def js_action_dispatch() -> str: # JavaScript action dispatch code
"Generate JavaScript code for action dispatch."
def js_keyboard_handler() -> str: # JavaScript keyboard handler code
"Generate JavaScript code for keyboard event handling."
def js_state_notification() -> str: # JavaScript state notification code
"Generate JavaScript code for state change notification."
def js_initialization() -> str: # JavaScript initialization code
"Generate JavaScript code for initialization with focus recovery."
def js_global_api() -> str: # JavaScript global API exposure code
"Generate JavaScript code to expose control functions globally."
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
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
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."
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."
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."
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."
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."
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."
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."
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
NAV_ICON_MAP = {2 items}
KEY_ICON_MAP = {9 items}
Hidden Inputs (inputs.ipynb)
Generate hidden inputs for HTMX integration with keyboard navigation.
Import
from cjm_fasthtml_keyboard_navigation.htmx.inputs import (
render_zone_hidden_inputs,
render_hidden_inputs,
build_include_selector,
build_all_zones_include_selector
)
Functions
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."
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.
"""
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."
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
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
def format_key_for_display(
key: str # the JavaScript key name
) -> str: # human-readable display string
"Format a key name for user display."
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
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
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
from cjm_fasthtml_keyboard_navigation.core.manager import (
ZoneManager
)
Classes
@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
from cjm_fasthtml_keyboard_navigation.core.modes import (
NAVIGATION_MODE,
KeyboardMode
)
Classes
@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
NAVIGATION_MODE
Navigation Patterns (navigation.ipynb)
Protocols and implementations for keyboard navigation within focus zones.
Import
from cjm_fasthtml_keyboard_navigation.core.navigation import (
Direction,
NavigationPattern,
LinearVertical,
LinearHorizontal,
ScrollOnly,
Grid
)
Classes
@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."
@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."
@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."
@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."
@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
from cjm_fasthtml_keyboard_navigation.components.system import (
KeyboardSystem,
render_keyboard_system,
quick_keyboard_system
)
Functions
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."
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."
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
@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
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
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."
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."
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."
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."
def js_hidden_input_update() -> str: # JavaScript function definition
"Generate JavaScript function to update hidden input values."
def js_trigger_click() -> str: # JavaScript function definition
"Generate JavaScript function to programmatically click a button."
def js_get_data_attributes() -> str: # JavaScript function definition
"Generate JavaScript function to extract data attributes from element."
def js_get_modifiers() -> str: # JavaScript function definition
"Generate JavaScript function to extract modifier keys from event."
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."
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters