FastHTML virtualized collection rendering with discrete navigation, custom scrollbar, table layout, and cell-level HTMX updates.
Project description
cjm-fasthtml-virtual-collection
Install
pip install cjm_fasthtml_virtual_collection
Project Structure
nbs/
├── components/ (4)
│ ├── collection.ipynb # Main entry point for rendering a virtual collection.
│ ├── footer.ipynb # Footer component showing item range indicator.
│ ├── scrollbar.ipynb # Custom scrollbar component with proportional thumb for position indication.
│ └── table.ipynb # Table layout rendering: header row, data rows, and cells using CSS table display.
├── core/ (4)
│ ├── button_ids.ipynb # Hidden button ID generators for navigation triggers.
│ ├── html_ids.ipynb # HTML element ID generators for virtual collection components.
│ ├── models.ipynb # Data models for virtual collection state, configuration, column definitions, render contexts, and URL bundles.
│ └── windowing.ipynb # Pure math functions for viewport window and scrollbar calculations.
├── js/ (4)
│ ├── auto_fit.ipynb # JavaScript generator for overflow-based automatic visible row count adjustment.
│ ├── scroll.ipynb # JavaScript generator for scroll wheel to navigation conversion.
│ ├── scrollbar.ipynb # JavaScript generator for custom scrollbar interaction (drag thumb, click track).
│ └── touch.ipynb # JavaScript generator for touch/swipe to navigation conversion.
├── keyboard/ (1)
│ └── actions.ipynb # Keyboard navigation focus zone and action factories for the virtual collection.
└── routes/ (2)
├── handlers.ipynb # Response builder functions for virtual collection navigation (Tier 1 API).
└── router.ipynb # Convenience router factory that wires up standard virtual collection routes (Tier 2 API).
Total: 15 notebooks across 5 directories
Module Dependencies
graph LR
components_collection[components.collection<br/>components.collection]
components_footer[components.footer<br/>components.footer]
components_scrollbar[components.scrollbar<br/>components.scrollbar]
components_table[components.table<br/>components.table]
core_button_ids[core.button_ids<br/>core.button_ids]
core_html_ids[core.html_ids<br/>core.html_ids]
core_models[core.models<br/>core.models]
core_windowing[core.windowing<br/>core.windowing]
js_auto_fit[js.auto_fit<br/>js.auto_fit]
js_scroll[js.scroll<br/>js.scroll]
js_scrollbar[js.scrollbar<br/>js.scrollbar]
js_touch[js.touch<br/>js.touch]
keyboard_actions[keyboard.actions<br/>keyboard.actions]
routes_handlers[routes.handlers<br/>routes.handlers]
routes_router[routes.router<br/>routes.router]
components_collection --> components_scrollbar
components_collection --> core_models
components_collection --> components_footer
components_collection --> components_table
components_collection --> core_html_ids
components_footer --> core_html_ids
components_footer --> core_windowing
components_footer --> core_models
components_scrollbar --> core_windowing
components_scrollbar --> core_models
components_scrollbar --> core_html_ids
components_table --> core_models
components_table --> core_html_ids
js_auto_fit --> core_models
js_auto_fit --> core_html_ids
js_scroll --> core_html_ids
js_scroll --> core_button_ids
js_scrollbar --> core_html_ids
js_scrollbar --> core_button_ids
js_scrollbar --> core_models
js_touch --> core_html_ids
js_touch --> core_button_ids
js_touch --> core_models
keyboard_actions --> core_html_ids
keyboard_actions --> core_button_ids
keyboard_actions --> core_models
routes_handlers --> components_scrollbar
routes_handlers --> core_models
routes_handlers --> components_table
routes_handlers --> core_windowing
routes_handlers --> components_footer
routes_handlers --> core_html_ids
routes_router --> routes_handlers
routes_router --> core_models
routes_router --> core_html_ids
35 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 focus zone and action factories for the virtual collection.
Import
from cjm_fasthtml_virtual_collection.keyboard.actions import (
create_collection_focus_zone,
create_collection_nav_actions,
build_collection_url_map,
apply_nav_sync
)
Functions
def create_collection_focus_zone(
ids: VirtualCollectionHtmlIds, # HTML IDs for this collection instance
hidden_input_prefix: Optional[str] = None, # Prefix for keyboard nav hidden inputs
) -> FocusZone: # Configured focus zone for the collection
"Create a focus zone for a virtual collection viewport."
def create_collection_nav_actions(
zone_id: str, # Focus zone ID to restrict actions to
button_ids: VirtualCollectionButtonIds, # Button IDs for HTMX triggers
disable_in_modes: Tuple[str, ...] = (), # Mode names that disable navigation
) -> Tuple[KeyAction, ...]: # Standard collection navigation actions
"Create standard keyboard navigation actions for a virtual collection."
def build_collection_url_map(
button_ids: VirtualCollectionButtonIds, # Button IDs for this collection
urls: VirtualCollectionUrls, # URL bundle for routing
) -> Dict[str, str]: # Mapping of button ID -> route URL
"Build url_map for render_keyboard_system with all collection buttons."
def apply_nav_sync(
kb_system: KeyboardSystem, # Rendered keyboard system to patch
ids: VirtualCollectionHtmlIds, # HTML IDs for this collection
) -> None: # Modifies kb_system.action_buttons in place
"""
Add hx-sync to nav buttons so rapid input aborts stale in-flight requests.
Prevents race conditions between cursor-only and full-window OOB responses
during rapid keyboard or scroll wheel navigation.
"""
js.auto_fit (auto_fit.ipynb)
JavaScript generator for overflow-based automatic visible row count adjustment.
Import
from cjm_fasthtml_virtual_collection.js.auto_fit import (
generate_auto_fit_js,
auto_fit_callback_name
)
Functions
def generate_auto_fit_js(
ids: VirtualCollectionHtmlIds, # HTML IDs for this collection
config: VirtualCollectionConfig, # Collection config
urls: VirtualCollectionUrls, # URL bundle (for update_viewport)
total_items: int = 0, # Total item count
initial_visible: int = 1, # Initial visible row count
) -> str: # JavaScript code fragment
"""
Generate JS for overflow-based auto-fit of visible row count.
Measures actual table overflow against wrapper height. Grows incrementally
with opacity:0 validation, shrinks via batch estimation. Adapted from the
cjm-fasthtml-card-stack auto_adjust pattern.
"""
def auto_fit_callback_name(
config: VirtualCollectionConfig, # Collection config (for prefix)
) -> str: # Global JS function name
"Get the global callback name for viewport-fit's resize_callback."
core.button_ids (button_ids.ipynb)
Hidden button ID generators for navigation triggers.
Import
from cjm_fasthtml_virtual_collection.core.button_ids import (
VirtualCollectionButtonIds
)
Classes
@dataclass
class VirtualCollectionButtonIds:
"Hidden button IDs for keyboard/scroll navigation triggers."
prefix: str # Instance prefix (e.g., 'vc0', 'fb')
def nav_up(self) -> str: return f"{self.prefix}-btn-nav-up"
@property
def nav_down(self) -> str: return f"{self.prefix}-btn-nav-down"
def nav_down(self) -> str: return f"{self.prefix}-btn-nav-down"
@property
def nav_page_up(self) -> str: return f"{self.prefix}-btn-nav-page-up"
def nav_page_up(self) -> str: return f"{self.prefix}-btn-nav-page-up"
@property
def nav_page_down(self) -> str: return f"{self.prefix}-btn-nav-page-down"
def nav_page_down(self) -> str: return f"{self.prefix}-btn-nav-page-down"
@property
def nav_first(self) -> str: return f"{self.prefix}-btn-nav-first"
def nav_first(self) -> str: return f"{self.prefix}-btn-nav-first"
@property
def nav_last(self) -> str: return f"{self.prefix}-btn-nav-last"
def nav_last(self) -> str: return f"{self.prefix}-btn-nav-last"
# -- Action buttons --
@property
def activate(self) -> str: return f"{self.prefix}-btn-activate"
def activate(self) -> str: return f"{self.prefix}-btn-activate"
components.collection (collection.ipynb)
Main entry point for rendering a virtual collection.
Import
from cjm_fasthtml_virtual_collection.components.collection import (
render_virtual_collection
)
Functions
def render_virtual_collection(
items: list, # Full item list
config: VirtualCollectionConfig, # Collection config
state: VirtualCollectionState, # Collection state
ids: VirtualCollectionHtmlIds, # HTML IDs
urls: VirtualCollectionUrls, # URL bundle
render_cell: Optional[Callable] = None, # Table layout cell render callback
render_item: Optional[Callable] = None, # Grid layout item render callback
render_empty: Optional[Callable] = None, # Empty state callback: () -> FT component
) -> Div: # Complete collection element
"Render a complete virtual collection with wrapper, table, scrollbar, and footer."
components.footer (footer.ipynb)
Footer component showing item range indicator.
Import
from cjm_fasthtml_virtual_collection.components.footer import (
render_footer
)
Functions
def render_footer(state: VirtualCollectionState, # Collection state
ids: VirtualCollectionHtmlIds, # HTML IDs
oob: bool = False, # Whether to include hx-swap-oob
) -> Div: # Footer element
"Render the footer with item range indicator."
routes.handlers (handlers.ipynb)
Response builder functions for virtual collection navigation (Tier 1 API).
Import
from cjm_fasthtml_virtual_collection.routes.handlers import (
build_nav_response,
build_cursor_move_response,
handle_navigate,
handle_navigate_to_index,
handle_update_viewport,
handle_focus_row,
handle_activate,
handle_sort
)
Functions
def _render_window_start_oob(
state: VirtualCollectionState, # Current state
ids: VirtualCollectionHtmlIds, # HTML IDs
) -> Hidden: # Hidden input with OOB swap
"Render OOB hidden input carrying the current window_start for JS thumb positioning."
def build_nav_response(
items: list, # Full item list
state: VirtualCollectionState, # Current state (already mutated)
config: VirtualCollectionConfig, # Collection config
ids: VirtualCollectionHtmlIds, # HTML IDs
render_cell: Callable, # Consumer cell render callback
focus_url: str = "", # URL for click-to-focus
) -> Tuple: # OOB elements (slot OOBs + footer + window_start input)
"Build OOB response for navigation: all visible slots + footer + window_start."
def build_cursor_move_response(
old_cursor: int, # Previous cursor index
items: list, # Full item list
state: VirtualCollectionState, # Current state (cursor already updated)
config: VirtualCollectionConfig, # Collection config
ids: VirtualCollectionHtmlIds, # HTML IDs
render_cell: Callable, # Consumer cell render callback
focus_url: str = "", # URL for click-to-focus
) -> Tuple: # OOB elements (affected slot OOBs + footer + window_start input)
"Build OOB response for cursor-only move: swap just the affected slots."
def _is_cursor_visible(
state: VirtualCollectionState, # Current state
) -> bool: # Whether cursor is within the visible window
"Check if the cursor index is within the current visible window."
def _append_cursor_change(
result: Tuple, # Base response tuple
items: list, # Full item list
state: VirtualCollectionState, # Current state
on_cursor_change: Optional[Callable], # Callback: (item, cursor_index, state) -> Tuple
) -> Tuple: # Response with appended cursor change OOB elements
"Append on_cursor_change callback results to a response tuple."
def _scroll_to_cursor(
state: VirtualCollectionState, # Current state (mutated in place)
) -> None
"Scroll window to bring an off-screen cursor into view."
def handle_navigate(
direction: str, # 'up', 'down', 'page_up', 'page_down', 'first', 'last'
items: list, # Full item list
state: VirtualCollectionState, # Current state (mutated in place)
config: VirtualCollectionConfig, # Collection config
ids: VirtualCollectionHtmlIds, # HTML IDs
render_cell: Callable, # Consumer cell render callback
focus_url: str = "", # URL for click-to-focus
is_skippable: Optional[Callable[[Any], bool]] = None, # Predicate: item -> skip?
on_cursor_change: Optional[Callable] = None, # Callback: (item, cursor_index, state) -> Tuple
) -> Tuple: # OOB elements
"Navigate in a direction. Mutates state in place."
def _call_action_callback(
callback: Callable, # Consumer callback to invoke
item: Any, # Item at the cursor position
row_index: int, # Row index
state: VirtualCollectionState, # Current VC state
request: Any = None, # FastHTML request (passed if callback accepts it)
) -> Any: # Callback result
"Call an action callback, passing request if the callback signature accepts it."
def handle_navigate_to_index(
target_index: int, # Target window_start
items: list, # Full item list
state: VirtualCollectionState, # Current state (mutated in place)
config: VirtualCollectionConfig, # Collection config
ids: VirtualCollectionHtmlIds, # HTML IDs
render_cell: Callable, # Consumer cell render callback
focus_url: str = "", # URL for click-to-focus
) -> Tuple: # OOB elements
"Navigate to a specific index. Mutates state.window_start in place."
def _render_scrollbar_oob(
state: VirtualCollectionState, # Current state
config: VirtualCollectionConfig, # Collection config
ids: VirtualCollectionHtmlIds, # HTML IDs
) -> Any: # Scrollbar element with OOB swap
"Render OOB scrollbar with fresh data-total-items and data-visible-rows attributes."
def _build_container_response(
items: list, # Full item list
state: VirtualCollectionState, # Current state
config: VirtualCollectionConfig, # Collection config
ids: VirtualCollectionHtmlIds, # HTML IDs
render_cell: Callable, # Consumer cell render callback
focus_url: str = "", # URL for click-to-focus
) -> Tuple: # OOB elements (container + scrollbar + footer + window_start input)
"Build OOB response that replaces the entire rows container with new slots."
def handle_update_viewport(
visible_rows: int, # New visible row count
items: list, # Full item list
state: VirtualCollectionState, # Current state (mutated in place)
config: VirtualCollectionConfig, # Collection config
ids: VirtualCollectionHtmlIds, # HTML IDs
render_cell: Callable, # Consumer cell render callback
is_auto: bool = True, # Whether from auto-fit
focus_url: str = "", # URL for click-to-focus
) -> Tuple: # OOB elements
"Update viewport with new row count. Mutates state in place."
def handle_focus_row(
row_index: int, # Row index to focus
items: list, # Full item list
state: VirtualCollectionState, # Current state (mutated in place)
config: VirtualCollectionConfig, # Collection config
ids: VirtualCollectionHtmlIds, # HTML IDs
render_cell: Callable, # Consumer cell render callback
focus_url: str = "", # URL for click-to-focus
on_refocus: Optional[Callable] = None, # Callback when clicking already-focused row: (item, row_index, state) -> Tuple
is_skippable: Optional[Callable[[Any], bool]] = None, # Predicate: item -> skip?
on_cursor_change: Optional[Callable] = None, # Callback: (item, cursor_index, state) -> Tuple
request: Any = None, # FastHTML request (passed to on_refocus if it accepts it)
) -> Tuple: # OOB elements (affected slot OOBs + footer + window_start input)
"""
Move cursor to a specific row via click/tap.
If the clicked row is skippable, returns empty (no-op).
If `on_refocus` is provided and the clicked row is already the cursor,
delegates to `on_refocus` instead of the normal cursor-move logic.
"""
def handle_activate(
items: list, # Full item list
state: VirtualCollectionState, # Current state
config: VirtualCollectionConfig, # Collection config
ids: VirtualCollectionHtmlIds, # HTML IDs
render_cell: Callable, # Consumer cell render callback
on_activate: Callable, # Consumer callback: (item, row_index, state[, request]) -> Tuple of OOB elements
focus_url: str = "", # URL for click-to-focus
request: Any = None, # FastHTML request (passed to on_activate if it accepts it)
) -> Tuple: # OOB elements from consumer callback
"Activate the focused row via Space/Enter. Delegates to consumer callback."
def handle_sort(
column_key: str, # Column key to sort by
items: list, # Full item list
state: VirtualCollectionState, # Current state (mutated in place)
config: VirtualCollectionConfig, # Collection config
ids: VirtualCollectionHtmlIds, # HTML IDs
render_cell: Callable, # Consumer cell render callback
sort_callback: Callable, # Consumer: (items, column_key, ascending) -> sorted items
sort_url: str = "", # Sort URL for header re-render
focus_url: str = "", # URL for click-to-focus
is_skippable: Optional[Callable[[Any], bool]] = None, # Predicate: item -> skip?
on_cursor_change: Optional[Callable] = None, # Callback: (item, cursor_index, state) -> Tuple
) -> Tuple: # OOB elements (header + rows + footer + window_start)
"Sort by column. Toggles direction if same column, resets window to start."
core.html_ids (html_ids.ipynb)
HTML element ID generators for virtual collection components.
Import
from cjm_fasthtml_virtual_collection.core.html_ids import (
VirtualCollectionHtmlIds
)
Classes
@dataclass
class VirtualCollectionHtmlIds:
"HTML element ID generators for a virtual collection instance."
prefix: str # Instance prefix (e.g., 'vc0', 'fb')
def collection(self) -> str: return f"{self.prefix}-collection"
# -- Wrapper (viewport-fit target) --
@property
def wrapper(self) -> str: return f"{self.prefix}-wrapper"
def wrapper(self) -> str: return f"{self.prefix}-wrapper"
# -- Table container --
@property
def table(self) -> str: return f"{self.prefix}-table"
def table(self) -> str: return f"{self.prefix}-table"
# -- Header --
@property
def header(self) -> str: return f"{self.prefix}-header"
def header(self) -> str: return f"{self.prefix}-header"
# -- Viewport (kept for viewport-fit compatibility) --
@property
def viewport(self) -> str: return f"{self.prefix}-viewport"
def viewport(self) -> str: return f"{self.prefix}-viewport"
# -- Rows container (table-row-group) --
@property
def rows(self) -> str: return f"{self.prefix}-rows"
def rows(self) -> str: return f"{self.prefix}-rows"
# -- Slot IDs (position-based, display:contents wrappers) --
def slot_id(self, slot_index: int) -> str: # ID for a viewport slot
def slot_id(self, slot_index: int) -> str: # ID for a viewport slot
return f"{self.prefix}-slot-{slot_index}"
# -- Dynamic row/cell/item IDs (data-based, on inner content) --
def row_id(self, index: int) -> str: # ID for a table row
def row_id(self, index: int) -> str: # ID for a table row
return f"{self.prefix}-row-{index}"
def cell_id(self, row_index: int, col_key: str) -> str: # ID for a table cell
def cell_id(self, row_index: int, col_key: str) -> str: # ID for a table cell
return f"{self.prefix}-row-{row_index}-col-{col_key}"
def item_id(self, index: int) -> str: # ID for a grid item
def item_id(self, index: int) -> str: # ID for a grid item
return f"{self.prefix}-item-{index}"
# -- Custom scrollbar --
@property
def scrollbar_track(self) -> str: return f"{self.prefix}-scrollbar-track"
def scrollbar_track(self) -> str: return f"{self.prefix}-scrollbar-track"
@property
def scrollbar_thumb(self) -> str: return f"{self.prefix}-scrollbar-thumb"
def scrollbar_thumb(self) -> str: return f"{self.prefix}-scrollbar-thumb"
# -- Footer --
@property
def footer(self) -> str: return f"{self.prefix}-footer"
def footer(self) -> str: return f"{self.prefix}-footer"
# -- Progress indicator --
@property
def progress(self) -> str: return f"{self.prefix}-progress"
def progress(self) -> str: return f"{self.prefix}-progress"
# -- Hidden inputs --
@property
def window_start_input(self) -> str: return f"{self.prefix}-window-start-input"
def window_start_input(self) -> str: return f"{self.prefix}-window-start-input"
core.models (models.ipynb)
Data models for virtual collection state, configuration, column definitions, render contexts, and URL bundles.
Import
from cjm_fasthtml_virtual_collection.core.models import (
ColumnDef,
VirtualCollectionConfig,
VirtualCollectionState,
RowRenderContext,
CellRenderContext,
VirtualCollectionUrls
)
Functions
def _auto_prefix() -> str: # Auto-generated prefix like 'vc0', 'vc1', etc.
"Generate a unique prefix for a virtual collection instance."
Classes
@dataclass
class ColumnDef:
"Column definition for table layout."
key: str # Unique column identifier (used in cell IDs)
header: str = '' # Display text for header
sortable: bool = False # Whether column header is clickable for sort
header_cls: str = '' # Additional CSS classes for header cell
cell_cls: str = '' # Additional CSS classes for data cells
@dataclass
class VirtualCollectionConfig:
"Initialization-time configuration for a virtual collection."
prefix: str = '' # HTML ID prefix (auto-generated if empty)
layout: str = 'table' # 'table' or 'grid'
columns: Tuple[ColumnDef, ...] = () # Column definitions
columns_per_row: int = 4 # Items per grid row
grid_gap: str = '1rem' # Gap between grid items
disable_scroll_in_modes: Tuple[str, ...] = () # Mode-based scroll suppression
show_scrollbar: bool = True # Show custom scrollbar
min_thumb_height: int = 24 # Minimum scrollbar thumb height (px)
@dataclass
class VirtualCollectionState:
"Mutable runtime state for a virtual collection."
window_start: int = 0 # First visible row index
visible_rows: int = 0 # Number of visible rows (from auto-fit)
total_items: int = 0 # Total item count (set by consumer)
viewport_height: float = 0.0 # Measured viewport height (px)
is_auto_mode: bool = True # Auto-adjust visible rows from viewport
cursor_index: int = -1 # Keyboard cursor position (-1 = none)
sort_column: str = '' # Current sort column key (empty = unsorted)
sort_ascending: bool = True # Sort direction
@dataclass
class RowRenderContext:
"Context passed to row/item render callback."
index: int # Row index in the full collection
total_items: int # Total item count
is_cursor: bool = False # Whether this row is the keyboard cursor
is_first_visible: bool = False # First row in current window
is_last_visible: bool = False # Last row in current window
@dataclass
class CellRenderContext:
"Context passed to cell render callback."
row_index: int # Row index in the full collection
column: ColumnDef # Column definition
total_items: int # Total item count
is_cursor: bool = False # Whether this row is the keyboard cursor
@dataclass
class VirtualCollectionUrls:
"URL bundle for HTMX endpoints."
nav_up: str = '' # Navigate up one row
nav_down: str = '' # Navigate down one row
nav_page_up: str = '' # Navigate up one page
nav_page_down: str = '' # Navigate down one page
nav_first: str = '' # Navigate to first row
nav_last: str = '' # Navigate to last row
nav_to_index: str = '' # Navigate to specific row index
update_viewport: str = '' # Update visible_rows (auto-fit)
focus_row: str = '' # Move cursor to a specific row (click/tap)
activate: str = '' # Activate focused row (Space/Enter)
sort: str = '' # Sort by column (header click)
Variables
_prefix_counter: int = 0
routes.router (router.ipynb)
Convenience router factory that wires up standard virtual collection routes (Tier 2 API).
Import
from cjm_fasthtml_virtual_collection.routes.router import (
init_virtual_collection_router
)
Functions
def init_virtual_collection_router(
config: VirtualCollectionConfig, # Collection config
state_getter: Callable[[], VirtualCollectionState], # Function to get current state
state_setter: Callable[[VirtualCollectionState], None], # Function to save state
get_items: Callable[[], list], # Function to get current items
render_cell: Callable, # Cell render callback
on_activate: Optional[Callable] = None, # Consumer callback for Space/Enter on focused row
on_refocus: Optional[Callable] = None, # Consumer callback when clicking already-focused row
sort_callback: Optional[Callable] = None, # Consumer callback: (items, column_key, ascending) -> None
is_skippable: Optional[Callable] = None, # Predicate: (item) -> bool, cursor skips these items
on_cursor_change: Optional[Callable] = None, # Callback: (item, cursor_index, state) -> Tuple of extra OOB elements
route_prefix: str = "/collection", # Route prefix
) -> Tuple[APIRouter, VirtualCollectionUrls]: # (router, urls) tuple
"Initialize an APIRouter with all standard virtual collection routes."
js.scroll (scroll.ipynb)
JavaScript generator for scroll wheel to navigation conversion.
Import
from cjm_fasthtml_virtual_collection.js.scroll import (
SCROLL_THRESHOLD,
NAVIGATION_COOLDOWN,
TRACKPAD_COOLDOWN,
generate_scroll_nav_js
)
Functions
def generate_scroll_nav_js(
ids: VirtualCollectionHtmlIds, # HTML IDs for this collection
button_ids: VirtualCollectionButtonIds, # Button IDs for nav triggers
disable_in_modes: Tuple[str, ...] = (), # Mode names where scroll is suppressed
) -> str: # JavaScript IIFE
"Generate JS for scroll wheel to navigation conversion."
Variables
SCROLL_THRESHOLD = 1 # Minimum accumulated delta to trigger navigation (px)
NAVIGATION_COOLDOWN = 100 # Mouse wheel cooldown (ms)
TRACKPAD_COOLDOWN = 250 # Trackpad cooldown (ms) — higher to prevent rapid-fire
components.scrollbar (scrollbar.ipynb)
Custom scrollbar component with proportional thumb for position indication.
Import
from cjm_fasthtml_virtual_collection.components.scrollbar import (
render_scrollbar_thumb,
render_scrollbar
)
Functions
def render_scrollbar_thumb(
state: VirtualCollectionState, # Collection state
config: VirtualCollectionConfig, # Collection config
ids: VirtualCollectionHtmlIds, # HTML IDs
track_height: float = 600.0, # Track height for min thumb calculation
oob: bool = False, # Whether to include hx-swap-oob
) -> Div: # Thumb element
"Render the scrollbar thumb at the correct position."
def render_scrollbar(
state: VirtualCollectionState, # Collection state
config: VirtualCollectionConfig, # Collection config
ids: VirtualCollectionHtmlIds, # HTML IDs
) -> Div: # Complete scrollbar (track + thumb)
"Render the custom scrollbar with track and proportional thumb."
Variables
_TRACK_CLS
js.scrollbar (scrollbar.ipynb)
JavaScript generator for custom scrollbar interaction (drag thumb, click track).
Import
from cjm_fasthtml_virtual_collection.js.scrollbar import (
generate_scrollbar_js
)
Functions
def generate_scrollbar_js(
ids: VirtualCollectionHtmlIds, # HTML IDs for this collection
urls: VirtualCollectionUrls, # URL bundle (for nav_to_index)
) -> str: # JavaScript code fragment
"Generate JS for custom scrollbar: thumb positioning from hidden input + drag/click interaction."
components.table (table.ipynb)
Table layout rendering: header row, data rows, and cells using CSS table display.
Import
from cjm_fasthtml_virtual_collection.components.table import (
SORT_ICON_SIZE,
render_header_cell,
render_header_row,
render_data_cell,
render_data_row,
render_slot,
render_table_rows,
render_cell_oob,
render_row_oob,
render_visible_cells_oob
)
Functions
def _sort_indicator(column: ColumnDef, # Column definition
state: VirtualCollectionState, # Collection state (for current sort)
) -> Any: # Sort icon element or empty string
"Render sort indicator icon for a sortable header cell."
def render_header_cell(column: ColumnDef, # Column definition
state: VirtualCollectionState, # Collection state
sort_url: str = "", # Sort URL (empty = no click-to-sort)
) -> Div: # Header cell element
"Render a single table header cell with optional sort indicator."
def render_header_row(config: VirtualCollectionConfig, # Collection config
ids: VirtualCollectionHtmlIds, # HTML IDs
state: VirtualCollectionState = None, # Collection state (for sort indicators)
sort_url: str = "", # Sort URL (empty = no click-to-sort)
) -> Div: # Header row element
"Render the table header row with optional sort indicators."
def render_data_cell(item: Any, # Data item
column: ColumnDef, # Column definition
row_index: int, # Row index
total_items: int, # Total item count
ids: VirtualCollectionHtmlIds, # HTML IDs
render_cell: Callable, # Consumer cell render callback
is_cursor: bool = False, # Whether row is keyboard cursor
) -> Div: # Cell element with stable ID
"Render a single data cell with a stable ID for OOB updates."
def render_data_row(item: Any, # Data item
row_index: int, # Row index in full collection
config: VirtualCollectionConfig, # Collection config
state: VirtualCollectionState, # Collection state
ids: VirtualCollectionHtmlIds, # HTML IDs
render_cell: Callable, # Consumer cell render callback
focus_url: str = "", # URL for click-to-focus (empty = disabled)
) -> Div: # Row element with stable ID
"Render a single data row with all cells."
def render_slot(
slot_index: int, # Position in viewport (0-based)
item: Any, # Data item
item_index: int, # Row index in full collection
config: VirtualCollectionConfig, # Collection config
state: VirtualCollectionState, # Collection state
ids: VirtualCollectionHtmlIds, # HTML IDs
render_cell: Callable, # Consumer cell render callback
oob: bool = False, # Whether to render as OOB swap
focus_url: str = "", # URL for click-to-focus (empty = disabled)
) -> Div: # Slot wrapper (display:contents) containing the data row
"Render a viewport slot wrapping a data row with display:contents."
def render_table_rows(items: list, # Full item list
config: VirtualCollectionConfig, # Collection config
state: VirtualCollectionState, # Collection state
ids: VirtualCollectionHtmlIds, # HTML IDs
render_cell: Callable, # Consumer cell render callback
focus_url: str = "", # URL for click-to-focus (empty = disabled)
) -> Div: # Table-row-group container with slot wrappers
"Render all visible rows in the current window as a table-row-group."
def render_cell_oob(item: Any, # Data item
column: ColumnDef, # Column to render
row_index: int, # Row index
total_items: int, # Total item count
ids: VirtualCollectionHtmlIds, # HTML IDs
render_cell: Callable, # Consumer cell render callback
is_cursor: bool = False, # Whether row is keyboard cursor
) -> Div: # Cell element with hx-swap-oob
"Render a single cell with OOB swap for targeted update."
def render_row_oob(item: Any, # Data item
row_index: int, # Row index
config: VirtualCollectionConfig, # Collection config
state: VirtualCollectionState, # Collection state
ids: VirtualCollectionHtmlIds, # HTML IDs
render_cell: Callable, # Consumer cell render callback
) -> Div: # Row element with hx-swap-oob
"Render a full row with OOB swap for targeted update."
def render_visible_cells_oob(
column: ColumnDef, # Column to re-render
item_indices: List[int], # Item indices that changed
items: list, # Full items list
state: VirtualCollectionState, # Current VC state (for window bounds)
ids: VirtualCollectionHtmlIds, # HTML IDs
render_cell: Callable, # Consumer cell render callback
) -> Tuple[Div, ...]: # OOB cell elements for visible items only
"Batch-render OOB cell updates for specified items within the visible window."
Variables
SORT_ICON_SIZE = 3 # Tailwind size scale for sort indicator icons
js.touch (touch.ipynb)
JavaScript generator for touch/swipe to navigation conversion.
Import
from cjm_fasthtml_virtual_collection.js.touch import (
TOUCH_SWIPE_THRESHOLD,
TOUCH_MOMENTUM_MIN_VELOCITY,
TOUCH_MOMENTUM_FRICTION,
TOUCH_VELOCITY_SAMPLES,
generate_touch_nav_js
)
Functions
def generate_touch_nav_js(
ids: VirtualCollectionHtmlIds, # HTML IDs for this collection
button_ids: VirtualCollectionButtonIds, # Button IDs for nav triggers (unused, kept for API consistency)
urls: VirtualCollectionUrls, # URL bundle for direct HTMX ajax calls
step_distance: int = TOUCH_SWIPE_THRESHOLD, # Drag distance in px to trigger one nav step
disable_in_modes: Tuple[str, ...] = (), # Mode names where touch is suppressed
) -> str: # JavaScript IIFE
"Generate JS for touch gesture to navigation conversion."
Variables
TOUCH_SWIPE_THRESHOLD: int = 30
TOUCH_MOMENTUM_MIN_VELOCITY: float = 0.5
TOUCH_MOMENTUM_FRICTION: float = 0.95
TOUCH_VELOCITY_SAMPLES: int = 5
core.windowing (windowing.ipynb)
Pure math functions for viewport window and scrollbar calculations.
Import
from cjm_fasthtml_virtual_collection.core.windowing import (
clamp_window_start,
compute_window,
compute_scrollbar,
navigate,
navigate_cursor,
find_nearest_focusable
)
Functions
def clamp_window_start(window_start: int, # Requested first visible row
visible_rows: int, # Number of visible rows
total_items: int, # Total item count
) -> int: # Clamped window_start
"Clamp window_start to valid range."
def compute_window(window_start: int, # First visible row index (already clamped)
visible_rows: int, # Number of visible rows
total_items: int, # Total item count
) -> Tuple[int, int]: # (render_start, render_end) exclusive end
"Compute the range of rows to render."
def compute_scrollbar(window_start: int, # First visible row index
visible_rows: int, # Number of visible rows
total_items: int, # Total item count
track_height: float, # Scrollbar track height in px
min_thumb_height: int = 24, # Minimum thumb height in px
) -> Tuple[float, float]: # (thumb_top_percent, thumb_height_percent)
"Compute scrollbar thumb position and size as percentages."
def navigate(window_start: int, # Current first visible row
direction: str, # 'up', 'down', 'page_up', 'page_down', 'first', 'last'
visible_rows: int, # Number of visible rows
total_items: int, # Total item count
) -> int: # New window_start (clamped)
"Compute new window_start for a navigation action."
def navigate_cursor(
cursor_index: int, # Current cursor position (-1 treated as window_start)
direction: str, # 'up' or 'down'
window_start: int, # Current first visible row
visible_rows: int, # Number of visible rows
total_items: int, # Total item count
is_skippable: Optional[Callable[[int], bool]] = None, # Predicate: index -> skip this item?
) -> Tuple[int, int, bool]: # (new_cursor, new_window_start, window_changed)
"Move cursor up/down within the visible window, scrolling at edges."
def find_nearest_focusable(
index: int, # Starting index to search from
total_items: int, # Total item count
is_skippable: Optional[Callable[[int], bool]] = None, # Predicate: index -> skip?
direction: int = 1, # Search direction: 1 (forward) or -1 (backward)
) -> int: # Nearest focusable index, or -1 if none found
"Find the nearest non-skippable index from a starting position."
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
File details
Details for the file cjm_fasthtml_virtual_collection-0.0.12.tar.gz.
File metadata
- Download URL: cjm_fasthtml_virtual_collection-0.0.12.tar.gz
- Upload date:
- Size: 58.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c486c4dcd85de09078bca627ed384fc08f7697f94ad298ded70a6db19c434e35
|
|
| MD5 |
517122b00dfc3185fab107a4f5f76402
|
|
| BLAKE2b-256 |
776527f6604070d3e5b237fc9cc86bba9c265164db991e720c0a365875672efb
|
File details
Details for the file cjm_fasthtml_virtual_collection-0.0.12-py3-none-any.whl.
File metadata
- Download URL: cjm_fasthtml_virtual_collection-0.0.12-py3-none-any.whl
- Upload date:
- Size: 53.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4dbc0a05769af8d2054efee3ee25894b830c49755f0c43fd8193391c0015cde6
|
|
| MD5 |
4ad8853c7142b7a37f6d31f917684c8c
|
|
| BLAKE2b-256 |
922ebf37dbc6cafb4c81b2021b88c71557454dca9406eadd1ce6ece9cec0edbc
|