# Actions


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

## create_card_stack_focus_zone

Creates a `FocusZone` configured for the card stack viewport. Uses
`ScrollOnly` navigation since all navigation is handled via HTMX button
triggers, not the keyboard library’s built-in item navigation.

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

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

### create_card_stack_focus_zone

``` python

def create_card_stack_focus_zone(
    ids:CardStackHtmlIds, # HTML IDs for this card stack instance
    on_focus_change:Optional=None, # JS callback name on focus change
    hidden_input_prefix:Optional=None, # Prefix for keyboard nav hidden inputs
    data_attributes:Tuple=(), # Data attributes to track on focused items
)->FocusZone: # Configured focus zone for the card stack

```

*Create a focus zone for a card stack viewport.*

``` python
# Test create_card_stack_focus_zone
ids = CardStackHtmlIds(prefix="cs0")
zone = create_card_stack_focus_zone(ids)

assert zone.id == "cs0-card-stack"
assert zone.item_selector == "[data-card-role='focused']"
assert isinstance(zone.navigation, ScrollOnly)
assert zone.zone_focus_classes == ()
assert zone.item_focus_classes == ()
assert zone.hidden_input_prefix == "cs0-focused"
print("Focus zone default tests passed!")
```

    Focus zone default tests passed!

``` python
# Test with custom parameters
zone = create_card_stack_focus_zone(
    ids,
    on_focus_change="onCardFocusChange",
    hidden_input_prefix="sd-decomp-focused",
    data_attributes=("segment-index",),
)
assert zone.on_focus_change == "onCardFocusChange"
assert zone.hidden_input_prefix == "sd-decomp-focused"
assert zone.data_attributes == ("segment-index",)
print("Focus zone custom parameter tests passed!")
```

    Focus zone custom parameter tests passed!

``` python
# Test multi-instance uniqueness
ids_a = CardStackHtmlIds(prefix="text")
ids_b = CardStackHtmlIds(prefix="vad")
zone_a = create_card_stack_focus_zone(ids_a)
zone_b = create_card_stack_focus_zone(ids_b)
assert zone_a.id != zone_b.id
assert zone_a.hidden_input_prefix != zone_b.hidden_input_prefix
print("Multi-instance focus zone tests passed!")
```

    Multi-instance focus zone tests passed!

## create_card_stack_nav_actions

Creates the standard keyboard navigation actions for a card stack:

- **ArrowUp/Down**: Navigate prev/next item (HTMX button trigger)
- **Ctrl+ArrowUp/Down**: Page jump (JS callback)
- **Ctrl+Shift+ArrowUp/Down**: First/last item (JS callback)
- **`[`/`]`**: Narrow/widen viewport (JS callback)
- **`-`/`=`**: Decrease/increase scale (JS callback)

JS callbacks use prefix-unique global names that map to the namespaced
`window.cardStacks[prefix]` functions.

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

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

### create_card_stack_nav_actions

``` python

def create_card_stack_nav_actions(
    zone_id:str, # Focus zone ID to restrict actions to
    button_ids:CardStackButtonIds, # Button IDs for HTMX triggers
    config:CardStackConfig, # Config (for prefix-unique callback names)
    disable_in_modes:Tuple=(), # Mode names that disable navigation
)->Tuple: # Standard card stack navigation actions

```

*Create standard keyboard navigation actions for a card stack.*

``` python
from cjm_fasthtml_card_stack.core.config import CardStackConfig, _reset_prefix_counter

# Test create_card_stack_nav_actions returns 10 actions
_reset_prefix_counter()
config = CardStackConfig()
btn_ids = CardStackButtonIds(prefix=config.prefix)
ids = CardStackHtmlIds(prefix=config.prefix)
zone = create_card_stack_focus_zone(ids)

actions = create_card_stack_nav_actions(
    zone_id=zone.id,
    button_ids=btn_ids,
    config=config,
)

assert len(actions) == 10
print(f"Got {len(actions)} actions")
```

    Got 10 actions

``` python
# Test ArrowUp/Down actions use HTMX triggers
arrow_up = actions[0]
arrow_down = actions[1]

assert arrow_up.key == "ArrowUp"
assert arrow_up.htmx_trigger == btn_ids.nav_up
assert arrow_up.zone_ids == (zone.id,)

assert arrow_down.key == "ArrowDown"
assert arrow_down.htmx_trigger == btn_ids.nav_down
print("Arrow key action tests passed!")
```

    Arrow key action tests passed!

``` python
# Test Ctrl+Arrow actions use prefix-unique JS callbacks
page_up = actions[2]
page_down = actions[3]

assert page_up.js_callback == "cs0_jumpPageUp"
assert page_down.js_callback == "cs0_jumpPageDown"
assert page_up.modifiers == frozenset({"ctrl"})
print("Page jump callback tests passed!")
```

    Page jump callback tests passed!

``` python
# Test Ctrl+Shift+Arrow actions use prefix-unique JS callbacks
first_item = actions[4]
last_item = actions[5]

assert first_item.js_callback == "cs0_jumpToFirstItem"
assert last_item.js_callback == "cs0_jumpToLastItem"
assert first_item.modifiers == frozenset({"ctrl", "shift"})
print("First/last callback tests passed!")
```

    First/last callback tests passed!

``` python
# Test width adjustment actions use prefix-unique JS callbacks
narrow = actions[6]
widen = actions[7]

assert narrow.key == "["
assert narrow.js_callback == "cs0_decreaseWidth"
assert widen.key == "]"
assert widen.js_callback == "cs0_increaseWidth"
print("Width adjustment callback tests passed!")
```

    Width adjustment callback tests passed!

``` python
# Test scale adjustment actions use prefix-unique JS callbacks
scale_down = actions[8]
scale_up = actions[9]

assert scale_down.key == "-"
assert scale_down.js_callback == "cs0_decreaseScale"
assert scale_up.key == "="
assert scale_up.js_callback == "cs0_increaseScale"
print("Scale adjustment callback tests passed!")

# Test disable_in_modes filtering
actions_with_modes = create_card_stack_nav_actions(
    zone_id=zone.id,
    button_ids=btn_ids,
    config=config,
    disable_in_modes=("split", "edit"),
)

# Navigation actions should have not_modes set
assert actions_with_modes[0].not_modes == ("split", "edit")
assert actions_with_modes[1].not_modes == ("split", "edit")
assert actions_with_modes[2].not_modes == ("split", "edit")

# Width and scale actions should NOT have not_modes (available in any mode)
assert actions_with_modes[6].not_modes == None
assert actions_with_modes[7].not_modes == None
assert actions_with_modes[8].not_modes == None
assert actions_with_modes[9].not_modes == None
print("Mode filtering tests passed!")
```

    Scale adjustment callback tests passed!
    Mode filtering tests passed!

``` python
# Test multi-instance: different prefixes produce different callback names
config_a = CardStackConfig(prefix="text")
config_b = CardStackConfig(prefix="vad")
btn_a = CardStackButtonIds(prefix="text")
btn_b = CardStackButtonIds(prefix="vad")
ids_a = CardStackHtmlIds(prefix="text")
ids_b = CardStackHtmlIds(prefix="vad")

actions_a = create_card_stack_nav_actions(ids_a.card_stack, btn_a, config_a)
actions_b = create_card_stack_nav_actions(ids_b.card_stack, btn_b, config_b)

# HTMX triggers are different (different button IDs)
assert actions_a[0].htmx_trigger != actions_b[0].htmx_trigger

# JS callbacks are different (different prefixes)
assert actions_a[2].js_callback == "text_jumpPageUp"
assert actions_b[2].js_callback == "vad_jumpPageUp"
print("Multi-instance action tests passed!")
```

    Multi-instance action tests passed!

## build_card_stack_url_map

Returns a `dict` mapping all card stack button IDs to their route URLs,
for use as (or merged into) the `url_map` parameter of
`render_keyboard_system`.

The keyboard system uses this map to create hidden HTMX buttons in the
DOM. The JS callbacks (page jump, first/last) and the HTMX-triggered
actions (nav up/down) both rely on these buttons existing.

Consumer merges with their own action URLs:

``` python
url_map = {**build_card_stack_url_map(btn_ids, urls), **my_workflow_urls}
```

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

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

### build_card_stack_url_map

``` python

def build_card_stack_url_map(
    button_ids:CardStackButtonIds, # Button IDs for this card stack instance
    urls:CardStackUrls, # URL bundle for routing
)->Dict: # Mapping of button ID -> route URL

```

*Build url_map for render_keyboard_system with all card stack navigation
buttons.*

Returns a dict mapping button IDs to URLs for all navigation actions:
nav_up, nav_down, nav_first, nav_last, nav_page_up, nav_page_down.

Merge with consumer’s own action URLs when building the keyboard system:
url_map = {**build_card_stack_url_map(btn_ids, urls),** my_action_urls}

``` python
from cjm_fasthtml_card_stack.core.models import CardStackUrls
from cjm_fasthtml_card_stack.core.config import CardStackConfig

# Test build_card_stack_url_map
_reset_prefix_counter()
config = CardStackConfig()
ids = CardStackHtmlIds(prefix=config.prefix)
btn_ids = CardStackButtonIds(prefix=config.prefix)
urls = CardStackUrls(
    nav_up="/cs/nav_up", nav_down="/cs/nav_down",
    nav_first="/cs/nav_first", nav_last="/cs/nav_last",
    nav_page_up="/cs/nav_page_up", nav_page_down="/cs/nav_page_down",
    nav_to_index="/cs/nav_to_index",
    update_viewport="/cs/update_viewport",
    save_width="/cs/save_width", save_scale="/cs/save_scale",
)

url_map = build_card_stack_url_map(btn_ids, urls)

# Verify all 6 navigation buttons are mapped
assert len(url_map) == 6
assert url_map[btn_ids.nav_up] == urls.nav_up
assert url_map[btn_ids.nav_down] == urls.nav_down
assert url_map[btn_ids.nav_first] == urls.nav_first
assert url_map[btn_ids.nav_last] == urls.nav_last
assert url_map[btn_ids.nav_page_up] == urls.nav_page_up
assert url_map[btn_ids.nav_page_down] == urls.nav_page_down

# Test merging with consumer urls
my_urls = {"my-split-btn": "/split", "my-merge-btn": "/merge"}
merged = {**url_map, **my_urls}
assert len(merged) == 8
print("build_card_stack_url_map tests passed!")
```

    build_card_stack_url_map tests passed!

## render_card_stack_action_buttons

Renders hidden HTMX buttons for the JS-callback-triggered navigation
actions (page jump, first/last). These are NOT created by
`render_keyboard_system` because the corresponding `KeyAction`
definitions use `js_callback` instead of `htmx_trigger`.

The full chain is: 1. Keyboard press → `KeyAction` with `js_callback` 2.
Keyboard library calls `window['cs0_jumpPageUp']()` 3. Global wrapper
calls `window.cardStacks['cs0'].jumpPageUp()` 4. JS function calls
`document.getElementById('cs0-btn-nav-page-up').click()` 5. Hidden
button fires HTMX POST to the route

Note: `nav_up`/`nav_down` buttons are created by
`render_keyboard_system` (since those KeyActions use `htmx_trigger`).
This function creates the remaining 4 navigation buttons that the
keyboard system skips.

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

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

### render_card_stack_action_buttons

``` python

def render_card_stack_action_buttons(
    button_ids:CardStackButtonIds, # Button IDs for this card stack instance
    urls:CardStackUrls, # URL bundle for routing
    ids:CardStackHtmlIds, # HTML IDs (for hx-include of focused_index_input)
)->FT: # Div containing hidden action buttons

```

*Render hidden HTMX buttons for JS-callback-triggered navigation
actions.*

Creates buttons for: page_up, page_down, first, last. These are clicked
programmatically by the card stack’s JS functions. Must be included in
the DOM alongside the keyboard system’s own buttons.

``` python
from fasthtml.common import to_xml

# Test render_card_stack_action_buttons
_reset_prefix_counter()
config = CardStackConfig()
ids = CardStackHtmlIds(prefix=config.prefix)
btn_ids = CardStackButtonIds(prefix=config.prefix)

buttons_div = render_card_stack_action_buttons(btn_ids, urls, ids)
html = to_xml(buttons_div)

# Verify all 4 hidden buttons are present with correct IDs
assert btn_ids.nav_page_up in html
assert btn_ids.nav_page_down in html
assert btn_ids.nav_first in html
assert btn_ids.nav_last in html

# Verify HTMX post URLs
assert urls.nav_page_up in html
assert urls.nav_page_down in html
assert urls.nav_first in html
assert urls.nav_last in html

# Verify hx-include references focused_index_input
assert ids.focused_index_input in html

# Verify hidden class (not inline style)
assert 'class="hidden"' in html

# Verify nav_up/nav_down are NOT included (keyboard system handles those)
assert btn_ids.nav_up not in html
assert btn_ids.nav_down not in html

print("render_card_stack_action_buttons tests passed!")
```

    render_card_stack_action_buttons tests passed!

``` python
from cjm_fasthtml_keyboard_navigation.core.modes import KeyboardMode
from cjm_fasthtml_keyboard_navigation.core.manager import ZoneManager

# Consumer creates zone and gets library nav actions
_reset_prefix_counter()
config = CardStackConfig()
ids = CardStackHtmlIds(prefix=config.prefix)
btn_ids = CardStackButtonIds(prefix=config.prefix)

zone = create_card_stack_focus_zone(ids)
nav_actions = create_card_stack_nav_actions(
    zone.id, btn_ids, config, disable_in_modes=("split",)
)

# Consumer adds their own actions
consumer_actions = (
    KeyAction(
        key="Enter",
        htmx_trigger="my-split-btn",
        not_modes=("split",),
        description="Enter split mode",
    ),
    KeyAction(
        key="Backspace",
        htmx_trigger="my-merge-btn",
        not_modes=("split",),
        description="Merge with previous",
    ),
)

# Consumer defines modes
split_mode = KeyboardMode(
    name="split",
    navigation_override=ScrollOnly(),
    indicator_text="Split Mode",
)

# Consumer assembles full manager
manager = ZoneManager(
    zones=(zone,),
    actions=nav_actions + consumer_actions,
    modes=(split_mode,),
    prev_zone_key="",
    next_zone_key="",
    state_hidden_inputs=True,
)

assert len(manager.zones) == 1
assert len(manager.actions) == 12  # 10 library + 2 consumer
assert len(manager.modes) == 1
print(f"ZoneManager assembled: {len(manager.actions)} actions, {len(manager.modes)} modes")
```

    ZoneManager assembled: 12 actions, 1 modes
