cjm-fasthtml-token-selector
Install
pip install cjm_fasthtml_token_selectorProject Structure
nbs/
├── components/ (2)
│ ├── inputs.ipynb # Hidden input rendering for HTMX state sync.
│ └── tokens.ipynb # Token grid rendering for all three selection modes (gap, word, span).
├── core/ (4)
│ ├── config.ipynb # Configuration dataclass for token selector initialization.
│ ├── constants.ipynb # CSS class constants, selection mode type, timing defaults, and key defaults for the token selector.
│ ├── html_ids.ipynb # Prefix-based HTML ID generator for token selector DOM elements.
│ └── models.ipynb # Data models for tokens, render context, and mutable runtime state.
├── helpers/ (1)
│ └── tokenizer.ipynb # Tokenization utilities for splitting text into tokens and converting between token indices and character positions.
├── js/ (4)
│ ├── core.ipynb # Master IIFE composer for the token selector JS runtime.
│ ├── display.ipynb # Generates JS functions for updating token display state (caret indicators, highlights, dimming, hidden inputs).
│ ├── navigation.ipynb # Generates mode-specific navigation and selection JS functions.
│ └── repeat.ipynb # Custom key repeat engine with configurable initial delay, repeat interval, and throttle floor.
└── keyboard/ (1)
└── actions.ipynb # Keyboard navigation library integration factories for token selector mode, actions, URL maps, and hidden action buttons.
Total: 12 notebooks across 5 directories
Module Dependencies
graph LR
components_inputs[components.inputs<br/>Inputs]
components_tokens[components.tokens<br/>Tokens]
core_config[core.config<br/>Config]
core_constants[core.constants<br/>Constants]
core_html_ids[core.html_ids<br/>HTML IDs]
core_models[core.models<br/>Models]
helpers_tokenizer[helpers.tokenizer<br/>Tokenizer]
js_core[js.core<br/>Core JS]
js_display[js.display<br/>Display JS]
js_navigation[js.navigation<br/>Navigation JS]
js_repeat[js.repeat<br/>Key Repeat JS]
keyboard_actions[keyboard.actions<br/>Keyboard Actions]
components_inputs --> core_models
components_inputs --> core_html_ids
components_tokens --> core_constants
components_tokens --> core_models
components_tokens --> core_config
components_tokens --> core_html_ids
components_tokens --> helpers_tokenizer
core_config --> core_constants
helpers_tokenizer --> core_models
js_core --> js_display
js_core --> core_models
js_core --> core_html_ids
js_core --> core_config
js_core --> js_repeat
js_core --> js_navigation
js_display --> core_constants
js_display --> core_html_ids
js_display --> core_config
js_navigation --> core_config
js_navigation --> core_html_ids
js_repeat --> core_config
keyboard_actions --> core_html_ids
keyboard_actions --> core_config
keyboard_actions --> js_core
24 cross-module dependencies detected
CLI Reference
No CLI commands found in this project.
Module Overview
Detailed documentation for each module in the project:
Keyboard Actions (actions.ipynb)
Keyboard navigation library integration factories for token selector mode, actions, URL maps, and hidden action buttons.
Import
from cjm_fasthtml_token_selector.keyboard.actions import (
create_token_selector_mode,
create_token_nav_actions,
build_token_selector_url_map,
render_token_action_buttons
)Functions
def create_token_selector_mode(
config:TokenSelectorConfig, # config for this instance
mode_name:str = "token-select", # mode name for the keyboard nav system
indicator_text:str = "Token Select", # mode indicator text
exit_key:str = "", # exit key (empty = programmatic only via Escape KeyAction)
exit_on_zone_change:bool = False, # whether to exit on zone change
) -> KeyboardMode: # configured keyboard mode
"Create a keyboard mode that activates/deactivates the token selector."def create_token_nav_actions(
config:TokenSelectorConfig, # config for this instance
zone_id:str, # focus zone ID
mode_name:str = "token-select", # mode name (must match the mode)
confirm_button_id:str = "", # HTMX button ID for confirm action
cancel_button_id:str = "", # HTMX button ID for cancel action
) -> Tuple[KeyAction, ...]: # non-movement keyboard actions
"Create keyboard actions for the token selector."def build_token_selector_url_map(
confirm_button_id:str, # button ID for confirm action
cancel_button_id:str, # button ID for cancel action
confirm_url:str, # URL for confirm action
cancel_url:str, # URL for cancel action
) -> Dict[str, str]: # button ID -> URL mapping
"Build URL map for keyboard system with token selector action buttons."def render_token_action_buttons(
confirm_button_id:str, # button ID for confirm
cancel_button_id:str, # button ID for cancel
confirm_url:str, # URL for confirm action
cancel_url:str, # URL for cancel action
ids:TokenSelectorHtmlIds, # HTML IDs (for hx_include)
extra_include:str = "", # additional hx_include selectors
) -> Any: # Div containing hidden action buttons
"Render hidden HTMX buttons for confirm/cancel actions."Config (config.ipynb)
Configuration dataclass for token selector initialization.
Import
from cjm_fasthtml_token_selector.core.config import (
TokenSelectorConfig
)Functions
def _auto_prefix() -> str: # unique prefix string
"""Generate an auto-incrementing unique prefix."""
global _prefix_counter
p = f"ts{_prefix_counter}"
_prefix_counter += 1
return p
def _reset_prefix_counter() -> None
"Generate an auto-incrementing unique prefix."def _reset_prefix_counter() -> None
"Reset the prefix counter (for testing only)."Classes
@dataclass
class TokenSelectorConfig:
"Initialization-time settings for a token selector instance."
prefix: str = field(...) # unique instance prefix
selection_mode: SelectionMode = 'gap' # selection behavior: "gap", "word", or "span"
initial_delay: int = DEFAULT_INITIAL_DELAY # ms before first repeat
repeat_interval: int = DEFAULT_REPEAT_INTERVAL # ms between repeats
throttle_floor: int = DEFAULT_THROTTLE_FLOOR # minimum ms between movements
left_key: str = DEFAULT_LEFT_KEY # key for leftward navigation
right_key: str = DEFAULT_RIGHT_KEY # key for rightward navigation
end_token_text: str = DEFAULT_END_TOKEN_TEXT # text for the sentinel end token
show_end_token: bool = True # whether to show the end token
read_only: bool = False # disable click/keyboard interaction
wrap_navigation: bool = False # wrap around at boundaries
on_change_callback: str = '' # JS function name called on selection changeVariables
_prefix_counter: int = 0Constants (constants.ipynb)
CSS class constants, selection mode type, timing defaults, and key defaults for the token selector.
Import
from cjm_fasthtml_token_selector.core.constants import (
SelectionMode,
OPACITY_50_CLS,
CARET_INDICATOR_CLS,
HIGHLIGHT_CLS,
DEFAULT_INITIAL_DELAY,
DEFAULT_REPEAT_INTERVAL,
DEFAULT_THROTTLE_FLOOR,
DEFAULT_LEFT_KEY,
DEFAULT_RIGHT_KEY,
DEFAULT_END_TOKEN_TEXT
)Variables
OPACITY_50_CLS
CARET_INDICATOR_CLS
HIGHLIGHT_CLS
DEFAULT_INITIAL_DELAY: int = 400
DEFAULT_REPEAT_INTERVAL: int = 80
DEFAULT_THROTTLE_FLOOR: int = 50
DEFAULT_LEFT_KEY: str = 'ArrowLeft'
DEFAULT_RIGHT_KEY: str = 'ArrowRight'
DEFAULT_END_TOKEN_TEXT: str = '(End)'Core JS (core.ipynb)
Master IIFE composer for the token selector JS runtime.
Import
from cjm_fasthtml_token_selector.js.core import (
global_callback_name,
generate_token_selector_js
)Functions
def global_callback_name(
prefix:str, # token selector instance prefix
callback:str, # base callback name
) -> str: # global function name
"Generate a prefix-unique global callback name."def _generate_state_init_js(
config:TokenSelectorConfig, # config for this instance
state:TokenSelectorState, # initial state
) -> str: # JS code fragment
"Generate state initialization code."def _generate_on_change_js(
config:TokenSelectorConfig, # config for this instance
) -> str: # JS code fragment
"Generate the on-change callback dispatcher."def _generate_activation_js(
config:TokenSelectorConfig, # config for this instance
ids:TokenSelectorHtmlIds, # HTML IDs
) -> str: # JS code fragment
"Generate activate/deactivate functions."def _generate_global_callbacks_js(
config:TokenSelectorConfig, # config for this instance
) -> str: # JS code fragment
"Generate global callback wrappers."def _generate_settle_handler_js(
config:TokenSelectorConfig, # config for this instance
ids:TokenSelectorHtmlIds, # HTML IDs
) -> str: # JS code fragment
"""
Generate htmx:afterSettle handler for swap resilience.
Stores the handler reference globally and replaces it on
re-initialization to avoid stale closure references when
the IIFE re-runs after HTMX page transitions.
"""def generate_token_selector_js(
config:TokenSelectorConfig, # config for this instance
ids:TokenSelectorHtmlIds, # HTML IDs
state:TokenSelectorState = None, # initial state
extra_scripts:Tuple[str, ...] = (), # additional JS to include in the IIFE
) -> Any: # Script element with the complete IIFE
"Compose all token selector JS into a single namespaced IIFE."Variables
_GLOBAL_CALLBACKSDisplay JS (display.ipynb)
Generates JS functions for updating token display state (caret indicators, highlights, dimming, hidden inputs).
Import
from cjm_fasthtml_token_selector.js.display import (
generate_display_js
)Functions
def _generate_update_inputs_js(
ids:TokenSelectorHtmlIds, # HTML IDs
) -> str: # JS code fragment
"Generate the hidden input sync function."def _generate_gap_display_js(
ids:TokenSelectorHtmlIds, # HTML IDs
) -> str: # JS code fragment
"Generate gap mode display update function."def _generate_word_display_js(
ids:TokenSelectorHtmlIds, # HTML IDs
) -> str: # JS code fragment
"Generate word mode display update function."def _generate_span_display_js(
ids:TokenSelectorHtmlIds, # HTML IDs
) -> str: # JS code fragment
"Generate span mode display update function."def generate_display_js(
config:TokenSelectorConfig, # config for this instance
ids:TokenSelectorHtmlIds, # HTML IDs
) -> str: # JS code fragment for the IIFE
"Generate display update and hidden input sync JS functions."HTML IDs (html_ids.ipynb)
Prefix-based HTML ID generator for token selector DOM elements.
Import
from cjm_fasthtml_token_selector.core.html_ids import (
TokenSelectorHtmlIds
)Classes
@dataclass
class TokenSelectorHtmlIds:
"Prefix-based HTML ID generator for token selector DOM elements."
prefix: str # unique instance prefix
def container(self) -> str: # outer wrapper ID
"""Outer token selector wrapper."""
return f"{self.prefix}-token-selector"
@property
def token_grid(self) -> str: # flex-wrap token container ID
"Outer token selector wrapper."
def token_grid(self) -> str: # flex-wrap token container ID
"""Flex-wrap token container."""
return f"{self.prefix}-token-grid"
@property
def anchor_input(self) -> str: # hidden input ID for anchor position
"Flex-wrap token container."
def anchor_input(self) -> str: # hidden input ID for anchor position
"""Hidden input ID for anchor position (hyphenated, for CSS selectors)."""
return f"{self.prefix}-anchor"
@property
def focus_input(self) -> str: # hidden input ID for focus position
"Hidden input ID for anchor position (hyphenated, for CSS selectors)."
def focus_input(self) -> str: # hidden input ID for focus position
"""Hidden input ID for focus position (hyphenated, for CSS selectors)."""
return f"{self.prefix}-focus"
@property
def anchor_name(self) -> str: # form field name for anchor position
"Hidden input ID for focus position (hyphenated, for CSS selectors)."
def anchor_name(self) -> str: # form field name for anchor position
"""Form field name for anchor position (underscored, for Python kwargs)."""
return f"{self.prefix}_anchor"
@property
def focus_name(self) -> str: # form field name for focus position
"Form field name for anchor position (underscored, for Python kwargs)."
def focus_name(self) -> str: # form field name for focus position
"""Form field name for focus position (underscored, for Python kwargs)."""
return f"{self.prefix}_focus"
def token(self,
index:int, # token position index
) -> str: # individual token span ID
"Form field name for focus position (underscored, for Python kwargs)."
def token(self,
index:int, # token position index
) -> str: # individual token span ID
"Individual token span ID."Inputs (inputs.ipynb)
Hidden input rendering for HTMX state sync.
Import
from cjm_fasthtml_token_selector.components.inputs import (
render_hidden_inputs,
build_include_selector
)Functions
def render_hidden_inputs(
ids:TokenSelectorHtmlIds, # HTML IDs for this instance
state:Optional[TokenSelectorState] = None, # current state
oob:bool = False, # render with hx-swap-oob
) -> Any: # tuple of anchor and focus hidden inputs
"Render hidden inputs for HTMX form submission."def build_include_selector(
ids:TokenSelectorHtmlIds, # HTML IDs for this instance
) -> str: # CSS selector string for hx_include
"Build a CSS selector for including anchor and focus inputs in HTMX requests."Models (models.ipynb)
Data models for tokens, render context, and mutable runtime state.
Import
from cjm_fasthtml_token_selector.core.models import (
Token,
TokenRenderContext,
TokenSelectorState
)Classes
@dataclass
class Token:
"A single token in the token grid."
text: str # the token text content
index: int # 0-based position in the token list
metadata: Any # optional consumer metadata per token@dataclass
class TokenRenderContext:
"Context passed to per-token styling callbacks."
token: Token # the token being rendered
is_selected: bool # whether this token is in the current selection
is_anchor: bool # whether this token is at the anchor position
is_focus: bool # whether this token is at the focus position
selection_mode: str # current selection mode@dataclass
class TokenSelectorState:
"Mutable runtime state for a token selector instance."
anchor: int = 0 # anchor position (gap index or token index)
focus: int = 0 # focus position (same as anchor in gap/word mode)
word_count: int = 0 # total number of tokens
active: bool = False # whether the key repeat engine is activeKey Repeat JS (repeat.ipynb)
Custom key repeat engine with configurable initial delay, repeat interval, and throttle floor.
Import
from cjm_fasthtml_token_selector.js.repeat import (
generate_key_repeat_js
)Functions
def _generate_movement_dispatch_js(
config:TokenSelectorConfig, # config for this instance
) -> str: # JS code fragment for the movement dispatcher
"Generate the key-to-movement dispatch logic."def generate_key_repeat_js(
config:TokenSelectorConfig, # config with timing settings
) -> str: # JS code fragment for the IIFE
"Generate the custom key repeat engine JS."Tokenizer (tokenizer.ipynb)
Tokenization utilities for splitting text into tokens and converting between token indices and character positions.
Import
from cjm_fasthtml_token_selector.helpers.tokenizer import (
count_tokens,
get_token_list,
token_index_to_char_position,
tokenize
)Functions
def count_tokens(
text:str, # text to count tokens in
) -> int: # token count
"Count the number of whitespace-delimited tokens in text."def get_token_list(
text:str, # text to split into tokens
) -> List[str]: # list of token strings
"Split text into a list of whitespace-delimited tokens."def token_index_to_char_position(
text:str, # full text string
token_index:int, # 0-based token index
) -> int: # character position for split
"Convert a token index to the character position where a split should occur."def tokenize(
text_or_tokens:Union[str, List[str]], # raw text string or pre-tokenized list
metadata:Optional[List[Any]] = None, # per-token metadata (must match token count)
) -> List[Token]: # list of Token objects
"Convert text or a pre-tokenized list into Token objects."Tokens (tokens.ipynb)
Token grid rendering for all three selection modes (gap, word, span).
Import
from cjm_fasthtml_token_selector.components.tokens import (
render_token,
render_end_token,
render_token_grid
)Functions
def _is_token_selected(
index:int, # token index
mode:str, # selection mode
anchor:int, # anchor position
focus:int, # focus position
) -> bool: # whether the token is in the selection
"Check if a token at the given index is within the current selection."def _build_token_render_context(
token:Token, # token being rendered
mode:str, # selection mode
anchor:int, # anchor position
focus:int, # focus position
) -> TokenRenderContext: # render context for styling callback
"Build a render context for the per-token styling callback."def _render_caret_indicator() -> Any: # caret indicator Div element
"Render the pulsing caret indicator bar."def render_token(
token:Token, # token to render
config:TokenSelectorConfig, # config for this instance
ids:TokenSelectorHtmlIds, # HTML IDs
state:Optional[TokenSelectorState] = None, # current state for highlighting
style_callback:Optional[Callable] = None, # (TokenRenderContext) -> str for extra CSS
read_only:bool = False, # disable interaction
) -> Any: # Span element for this token
"Render a single interactive word token."def render_end_token(
config:TokenSelectorConfig, # config for this instance
ids:TokenSelectorHtmlIds, # HTML IDs
state:Optional[TokenSelectorState] = None, # current state
read_only:bool = False, # disable interaction
) -> Any: # end sentinel Span element
"Render the end-of-text sentinel token."def render_token_grid(
tokens:List[Token], # token list to render
config:TokenSelectorConfig, # config for this instance
ids:TokenSelectorHtmlIds, # HTML IDs
state:Optional[TokenSelectorState] = None, # current state for highlighting
style_callback:Optional[Callable] = None, # (TokenRenderContext) -> str for extra CSS
read_only:bool = False, # disable all interaction
) -> Any: # Div containing the complete token grid
"Render the full interactive token grid."