# Handlers


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

## Response Builders

Composable functions that build HTMX response parts. Consumers can use
these directly or call the higher-level
[`card_stack_navigate`](https://cj-mills.github.io/cjm-fasthtml-card-stack/routes/handlers.html#card_stack_navigate)
functions.

------------------------------------------------------------------------

<a
href="https://github.com/cj-mills/cjm-fasthtml-card-stack/blob/main/cjm_fasthtml_card_stack/routes/handlers.py#L22"
target="_blank" style="float:right; font-size:smaller">source</a>

### build_slots_response

``` python

def build_slots_response(
    card_items:List, # All data items
    state:CardStackState, # Current card stack state
    config:CardStackConfig, # Card stack configuration
    ids:CardStackHtmlIds, # HTML IDs for this instance
    urls:CardStackUrls, # URL bundle for navigation
    render_card:Callable, # Card renderer callback
)->List: # OOB slot elements (3 viewport sections)

```

*Build OOB slot updates for the viewport sections only.*

------------------------------------------------------------------------

<a
href="https://github.com/cj-mills/cjm-fasthtml-card-stack/blob/main/cjm_fasthtml_card_stack/routes/handlers.py#L41"
target="_blank" style="float:right; font-size:smaller">source</a>

### build_nav_response

``` python

def build_nav_response(
    card_items:List, # All data items
    state:CardStackState, # Current card stack state
    config:CardStackConfig, # Card stack configuration
    ids:CardStackHtmlIds, # HTML IDs for this instance
    urls:CardStackUrls, # URL bundle for navigation
    render_card:Callable, # Card renderer callback
    progress_label:str='Item', # Label for progress indicator
    form_input_name:str='focused_index', # Name for the focused index hidden input
)->Tuple: # OOB elements (slots + progress + focus + scrollbar)

```

*Build full OOB response for navigation: slots + progress + focus
inputs + scrollbar.*

## Navigation

------------------------------------------------------------------------

<a
href="https://github.com/cj-mills/cjm-fasthtml-card-stack/blob/main/cjm_fasthtml_card_stack/routes/handlers.py#L74"
target="_blank" style="float:right; font-size:smaller">source</a>

### card_stack_navigate

``` python

def card_stack_navigate(
    direction:str, # "up", "down", "first", "last", "page_up", "page_down"
    card_items:List, # All data items
    state:CardStackState, # Current card stack state (mutated in place)
    config:CardStackConfig, # Card stack configuration
    ids:CardStackHtmlIds, # HTML IDs for this instance
    urls:CardStackUrls, # URL bundle for navigation
    render_card:Callable, # Card renderer callback
    progress_label:str='Item', # Label for progress indicator
    form_input_name:str='focused_index', # Name for the focused index hidden input
)->Tuple: # OOB elements (slots + progress + focus)

```

*Navigate to a different item. Mutates state.focused_index in place.*

------------------------------------------------------------------------

<a
href="https://github.com/cj-mills/cjm-fasthtml-card-stack/blob/main/cjm_fasthtml_card_stack/routes/handlers.py#L112"
target="_blank" style="float:right; font-size:smaller">source</a>

### card_stack_navigate_to_index

``` python

def card_stack_navigate_to_index(
    target_index:int, # Target item index to navigate to
    card_items:List, # All data items
    state:CardStackState, # Current card stack state (mutated in place)
    config:CardStackConfig, # Card stack configuration
    ids:CardStackHtmlIds, # HTML IDs for this instance
    urls:CardStackUrls, # URL bundle for navigation
    render_card:Callable, # Card renderer callback
    progress_label:str='Item', # Label for progress indicator
    form_input_name:str='focused_index', # Name for the focused index hidden input
)->Tuple: # OOB elements (slots + progress + focus)

```

*Navigate to a specific item index. Mutates state.focused_index in
place.*

## Viewport Update

------------------------------------------------------------------------

<a
href="https://github.com/cj-mills/cjm-fasthtml-card-stack/blob/main/cjm_fasthtml_card_stack/routes/handlers.py#L139"
target="_blank" style="float:right; font-size:smaller">source</a>

### card_stack_update_viewport

``` python

def card_stack_update_viewport(
    visible_count:int, # New number of visible cards
    card_items:List, # All data items
    state:CardStackState, # Current card stack state (mutated in place)
    config:CardStackConfig, # Card stack configuration
    ids:CardStackHtmlIds, # HTML IDs for this instance
    urls:CardStackUrls, # URL bundle for navigation
    render_card:Callable, # Card renderer callback
    is_auto:bool=True, # Whether this update came from auto-adjust mode
)->Tuple: # OOB section elements (3 viewport sections + scrollbar)

```

*Update viewport with new card count via OOB section swaps. Mutates
state in place.*

## Preference Persistence

------------------------------------------------------------------------

<a
href="https://github.com/cj-mills/cjm-fasthtml-card-stack/blob/main/cjm_fasthtml_card_stack/routes/handlers.py#L171"
target="_blank" style="float:right; font-size:smaller">source</a>

### card_stack_save_width

``` python

def card_stack_save_width(
    state:CardStackState, # Current card stack state (mutated in place)
    card_width:int, # Card stack width in rem
    config:CardStackConfig, # Card stack configuration (for clamping bounds)
)->None: # No response (swap=none on client)

```

*Save card stack width. Mutates state.card_width in place.*

------------------------------------------------------------------------

<a
href="https://github.com/cj-mills/cjm-fasthtml-card-stack/blob/main/cjm_fasthtml_card_stack/routes/handlers.py#L180"
target="_blank" style="float:right; font-size:smaller">source</a>

### card_stack_save_scale

``` python

def card_stack_save_scale(
    state:CardStackState, # Current card stack state (mutated in place)
    card_scale:int, # Card stack scale percentage
    config:CardStackConfig, # Card stack configuration (for clamping bounds)
)->None: # No response (swap=none on client)

```

*Save card stack scale. Mutates state.card_scale in place.*

## Tests

``` python
from cjm_fasthtml_card_stack.core.config import _reset_prefix_counter
from cjm_fasthtml_card_stack.core.models import CardStackState, CardRenderContext, CardStackUrls
from cjm_fasthtml_card_stack.core.html_ids import CardStackHtmlIds

# Simple render_card for testing
from fasthtml.common import Div, Span

def _test_render_card(item, context: CardRenderContext):
    return Div(Span(f"Item {context.index}: {item}"), cls=f"card-{context.card_role}")

# Setup test fixtures
_reset_prefix_counter()
_test_config = CardStackConfig(prefix="test")
_test_ids = CardStackHtmlIds(prefix="test")
_test_urls = CardStackUrls(
    nav_up="/nav_up", nav_down="/nav_down",
    nav_first="/nav_first", nav_last="/nav_last",
    nav_page_up="/nav_page_up", nav_page_down="/nav_page_down",
    nav_to_index="/nav_to_index",
    update_viewport="/update_viewport",
    save_width="/save_width", save_scale="/save_scale",
)
_test_items = [f"Item {i}" for i in range(20)]

print("Test fixtures ready.")
```

    Test fixtures ready.

``` python
# Test card_stack_navigate — basic down navigation
state = CardStackState(focused_index=5, visible_count=3)
result = card_stack_navigate(
    "down", _test_items, state, _test_config, _test_ids, _test_urls, _test_render_card
)
assert state.focused_index == 6  # Mutated in place
assert len(result) > 0  # Returns OOB elements
print("Navigate down test passed!")
```

    Navigate down test passed!

``` python
# Test card_stack_navigate — boundary clamping
state = CardStackState(focused_index=0, visible_count=3)
card_stack_navigate("up", _test_items, state, _test_config, _test_ids, _test_urls, _test_render_card)
assert state.focused_index == 0  # Can't go below 0

state = CardStackState(focused_index=19, visible_count=3)
card_stack_navigate("down", _test_items, state, _test_config, _test_ids, _test_urls, _test_render_card)
assert state.focused_index == 19  # Can't go above total-1
print("Navigate boundary clamping tests passed!")
```

    Navigate boundary clamping tests passed!

``` python
# Test card_stack_navigate — first/last
state = CardStackState(focused_index=10, visible_count=3)
card_stack_navigate("first", _test_items, state, _test_config, _test_ids, _test_urls, _test_render_card)
assert state.focused_index == 0

card_stack_navigate("last", _test_items, state, _test_config, _test_ids, _test_urls, _test_render_card)
assert state.focused_index == 19
print("Navigate first/last tests passed!")
```

    Navigate first/last tests passed!

``` python
# Test card_stack_navigate — page jump uses visible_count - 1
state = CardStackState(focused_index=10, visible_count=5)
card_stack_navigate("page_down", _test_items, state, _test_config, _test_ids, _test_urls, _test_render_card)
assert state.focused_index == 14  # 10 + (5-1) = 14

state = CardStackState(focused_index=10, visible_count=3)
card_stack_navigate("page_up", _test_items, state, _test_config, _test_ids, _test_urls, _test_render_card)
assert state.focused_index == 8  # 10 - (3-1) = 8

# Single card: page jump = 1
state = CardStackState(focused_index=10, visible_count=1)
card_stack_navigate("page_down", _test_items, state, _test_config, _test_ids, _test_urls, _test_render_card)
assert state.focused_index == 11  # 10 + max(1, 1-1) = 10 + 1 = 11
print("Page jump tests passed!")
```

    Page jump tests passed!

``` python
# Test card_stack_navigate — empty items
state = CardStackState(focused_index=0, visible_count=3)
result = card_stack_navigate(
    "down", [], state, _test_config, _test_ids, _test_urls, _test_render_card
)
assert state.focused_index == 0  # Unchanged
assert len(result) > 0  # Still returns elements (empty viewport sections)
print("Empty items navigate test passed!")
```

    Empty items navigate test passed!

``` python
# Test card_stack_navigate_to_index
state = CardStackState(focused_index=0, visible_count=3)
result = card_stack_navigate_to_index(
    15, _test_items, state, _test_config, _test_ids, _test_urls, _test_render_card
)
assert state.focused_index == 15

# Clamps out-of-range
card_stack_navigate_to_index(
    100, _test_items, state, _test_config, _test_ids, _test_urls, _test_render_card
)
assert state.focused_index == 19  # Clamped to total-1

card_stack_navigate_to_index(
    -5, _test_items, state, _test_config, _test_ids, _test_urls, _test_render_card
)
assert state.focused_index == 0  # Clamped to 0
print("Navigate to index tests passed!")
```

    Navigate to index tests passed!

``` python
# Test form_input_name is passed through navigation
from fasthtml.common import to_xml

state = CardStackState(focused_index=5, visible_count=3)
result = card_stack_navigate(
    "down", _test_items, state, _test_config, _test_ids, _test_urls, _test_render_card,
    form_input_name="segment_index"
)
# Find the Hidden input in the result tuple
hidden_found = False
for el in result:
    html = to_xml(el)
    if 'name="segment_index"' in html:
        hidden_found = True
        assert 'value="6"' in html  # focused_index after nav down from 5
        break
assert hidden_found, "Hidden input with custom form_input_name not found in response"
print("form_input_name passthrough test passed!")
```

    form_input_name passthrough test passed!

``` python
# Test card_stack_update_viewport returns OOB section tuple + scrollbar
from fasthtml.common import to_xml

state = CardStackState(focused_index=5, visible_count=3)
result = card_stack_update_viewport(
    7, _test_items, state, _test_config, _test_ids, _test_urls, _test_render_card
)
assert state.visible_count == 7  # Mutated in place
assert state.is_auto_mode == True  # Default is_auto=True
assert isinstance(result, tuple)
assert len(result) == 4  # 3 viewport sections + scrollbar OOB

# Verify each viewport section has OOB swap attribute
for section in result[:3]:
    html = to_xml(section)
    assert 'hx-swap-oob="innerHTML"' in html

# Verify section IDs
assert f'id="{_test_ids.viewport_section_before}"' in to_xml(result[0])
assert f'id="{_test_ids.viewport_section_focused}"' in to_xml(result[1])
assert f'id="{_test_ids.viewport_section_after}"' in to_xml(result[2])

# Verify scrollbar OOB
scrollbar_html = to_xml(result[3])
assert 'hx-swap-oob="outerHTML"' in scrollbar_html
assert 'test-scrollbar-track' in scrollbar_html

# Test is_auto=False saves to state
state2 = CardStackState(focused_index=0, visible_count=1)
card_stack_update_viewport(
    5, _test_items, state2, _test_config, _test_ids, _test_urls, _test_render_card,
    is_auto=False
)
assert state2.visible_count == 5
assert state2.is_auto_mode == False
print("Update viewport OOB test passed!")
```

    Update viewport OOB test passed!

``` python
# Test card_stack_save_width
state = CardStackState(card_width=80)
card_stack_save_width(state, 60, _test_config)
assert state.card_width == 60

# Clamping above max
card_stack_save_width(state, 200, _test_config)
assert state.card_width == _test_config.card_width_max  # 120

# Clamping below min
card_stack_save_width(state, 10, _test_config)
assert state.card_width == _test_config.card_width_min  # 30
print("Save width tests passed!")
```

    Save width tests passed!

``` python
# Test card_stack_save_scale
state = CardStackState(card_scale=100)
card_stack_save_scale(state, 150, _test_config)
assert state.card_scale == 150

# Clamping above max
card_stack_save_scale(state, 300, _test_config)
assert state.card_scale == _test_config.card_scale_max  # 200

# Clamping below min
card_stack_save_scale(state, 10, _test_config)
assert state.card_scale == _test_config.card_scale_min  # 50
print("Save scale tests passed!")
```

    Save scale tests passed!
