cjm-fasthtml-token-selector 0.0.2
A token-level interactive text selector for FastHTML with gap, word, and span selection modes, custom key repeat navigation, and HTMX state sync.
License: Apache-2.0
Requires Python: >=3.12
Keywords: nbdev jupyter notebook python

# cjm-fasthtml-token-selector


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

## Install

``` bash
pip install cjm_fasthtml_token_selector
```

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

``` mermaid
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

``` python
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

``` python
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."
```

``` python
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."
```

``` python
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."
```

``` python
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

``` python
from cjm_fasthtml_token_selector.core.config import (
    TokenSelectorConfig
)
```

#### Functions

``` python
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."
```

``` python
def _reset_prefix_counter() -> None
    "Reset the prefix counter (for testing only)."
```

#### Classes

``` python
@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 change
```

#### Variables

``` python
_prefix_counter: int = 0
```

### Constants (`constants.ipynb`)

> CSS class constants, selection mode type, timing defaults, and key
> defaults for the token selector.

#### Import

``` python
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

``` python
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

``` python
from cjm_fasthtml_token_selector.js.core import (
    global_callback_name,
    generate_token_selector_js
)
```

#### Functions

``` python
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."
```

``` python
def _generate_state_init_js(
    config:TokenSelectorConfig,  # config for this instance
    state:TokenSelectorState,    # initial state
) -> str:  # JS code fragment
    "Generate state initialization code."
```

``` python
def _generate_on_change_js(
    config:TokenSelectorConfig,  # config for this instance
) -> str:  # JS code fragment
    "Generate the on-change callback dispatcher."
```

``` python
def _generate_activation_js(
    config:TokenSelectorConfig,  # config for this instance
    ids:TokenSelectorHtmlIds,    # HTML IDs
) -> str:  # JS code fragment
    "Generate activate/deactivate functions."
```

``` python
def _generate_global_callbacks_js(
    config:TokenSelectorConfig,  # config for this instance
) -> str:  # JS code fragment
    "Generate global callback wrappers."
```

``` python
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.
    """
```

``` python
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

``` python
_GLOBAL_CALLBACKS
```

### Display JS (`display.ipynb`)

> Generates JS functions for updating token display state (caret
> indicators, highlights, dimming, hidden inputs).

#### Import

``` python
from cjm_fasthtml_token_selector.js.display import (
    generate_display_js
)
```

#### Functions

``` python
def _generate_update_inputs_js(
    ids:TokenSelectorHtmlIds,  # HTML IDs
) -> str:  # JS code fragment
    "Generate the hidden input sync function."
```

``` python
def _generate_gap_display_js(
    ids:TokenSelectorHtmlIds,  # HTML IDs
) -> str:  # JS code fragment
    "Generate gap mode display update function."
```

``` python
def _generate_word_display_js(
    ids:TokenSelectorHtmlIds,  # HTML IDs
) -> str:  # JS code fragment
    "Generate word mode display update function."
```

``` python
def _generate_span_display_js(
    ids:TokenSelectorHtmlIds,  # HTML IDs
) -> str:  # JS code fragment
    "Generate span mode display update function."
```

``` python
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

``` python
from cjm_fasthtml_token_selector.core.html_ids import (
    TokenSelectorHtmlIds
)
```

#### Classes

``` python
@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

``` python
from cjm_fasthtml_token_selector.components.inputs import (
    render_hidden_inputs,
    build_include_selector
)
```

#### Functions

``` python
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."
```

``` python
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

``` python
from cjm_fasthtml_token_selector.core.models import (
    Token,
    TokenRenderContext,
    TokenSelectorState
)
```

#### Classes

``` python
@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
```

``` python
@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
```

``` python
@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 active
```

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

> Generates mode-specific navigation and selection JS functions.

#### Import

``` python
from cjm_fasthtml_token_selector.js.navigation import (
    generate_navigation_js
)
```

#### Functions

``` python
def _generate_gap_nav_js(
    config:TokenSelectorConfig,  # config for this instance
    ids:TokenSelectorHtmlIds,    # HTML IDs
) -> str:  # JS code fragment
    "Generate gap mode navigation functions."
```

``` python
def _generate_word_nav_js(
    config:TokenSelectorConfig,  # config for this instance
    ids:TokenSelectorHtmlIds,    # HTML IDs
) -> str:  # JS code fragment
    "Generate word mode navigation functions."
```

``` python
def _generate_span_nav_js(
    config:TokenSelectorConfig,  # config for this instance
    ids:TokenSelectorHtmlIds,    # HTML IDs
) -> str:  # JS code fragment
    "Generate span mode navigation functions."
```

``` python
def generate_navigation_js(
    config:TokenSelectorConfig,  # config for this instance
    ids:TokenSelectorHtmlIds,    # HTML IDs
) -> str:  # JS code fragment for the IIFE
    "Generate mode-specific navigation and selection JS functions."
```

### Key Repeat JS (`repeat.ipynb`)

> Custom key repeat engine with configurable initial delay, repeat
> interval, and throttle floor.

#### Import

``` python
from cjm_fasthtml_token_selector.js.repeat import (
    generate_key_repeat_js
)
```

#### Functions

``` python
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."
```

``` python
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

``` python
from cjm_fasthtml_token_selector.helpers.tokenizer import (
    count_tokens,
    get_token_list,
    token_index_to_char_position,
    tokenize
)
```

#### Functions

``` python
def count_tokens(
    text:str,  # text to count tokens in
) -> int:  # token count
    "Count the number of whitespace-delimited tokens in text."
```

``` python
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."
```

``` python
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."
```

``` python
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

``` python
from cjm_fasthtml_token_selector.components.tokens import (
    render_token,
    render_end_token,
    render_token_grid
)
```

#### Functions

``` python
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."
```

``` python
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."
```

``` python
def _render_caret_indicator() -> Any:  # caret indicator Div element
    "Render the pulsing caret indicator bar."
```

``` python
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."
```

``` python
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."
```

``` python
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."
```