feat: add HTMX trigger to filter inputs for dynamic checkbox selection
When users type a filter, HTMX now fetches the matching animals and displays a checkbox list for subset selection. Changes: - Add hx_get="/api/selection-preview" to filter inputs in all forms - Wrap selection component in #selection-container for HTMX targeting - Add subset_mode hidden field to checkbox list component - Handle single-animal case with simple count display (no checkboxes) Forms updated: outcome, tag-add, tag-end, attrs, move 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -74,11 +74,26 @@ def selection_preview(request: Request):
|
|||||||
animal_repo = AnimalRepository(db)
|
animal_repo = AnimalRepository(db)
|
||||||
animals = animal_repo.get_by_ids(resolution.animal_ids)
|
animals = animal_repo.get_by_ids(resolution.animal_ids)
|
||||||
|
|
||||||
|
from fasthtml.common import Div, P, Span, to_xml
|
||||||
|
|
||||||
|
# For single animal, show simple count display (no checkboxes needed)
|
||||||
|
if len(animals) == 1:
|
||||||
|
return HTMLResponse(
|
||||||
|
content=to_xml(
|
||||||
|
Div(
|
||||||
|
P(
|
||||||
|
Span("1", cls="font-bold text-lg"),
|
||||||
|
" animal selected",
|
||||||
|
cls="text-sm",
|
||||||
|
),
|
||||||
|
cls="p-3 bg-slate-100 dark:bg-slate-800 rounded-md",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# If no pre-selection, default to all selected
|
# If no pre-selection, default to all selected
|
||||||
if selected_ids is None:
|
if selected_ids is None:
|
||||||
selected_ids = [a.animal_id for a in animals]
|
selected_ids = [a.animal_id for a in animals]
|
||||||
|
|
||||||
# Render checkbox list
|
# Render checkbox list for multiple animals
|
||||||
from fasthtml.common import to_xml
|
|
||||||
|
|
||||||
return HTMLResponse(content=to_xml(animal_checkbox_list(animals, selected_ids)))
|
return HTMLResponse(content=to_xml(animal_checkbox_list(animals, selected_ids)))
|
||||||
|
|||||||
@@ -566,31 +566,38 @@ def tag_add_form(
|
|||||||
error_component = Alert(error, cls=AlertT.warning)
|
error_component = Alert(error, cls=AlertT.warning)
|
||||||
|
|
||||||
# Selection component - show checkboxes if animals provided and > 1
|
# Selection component - show checkboxes if animals provided and > 1
|
||||||
selection_component = None
|
# Wrapped in a container with ID for HTMX targeting
|
||||||
|
selection_content = None
|
||||||
subset_mode = False
|
subset_mode = False
|
||||||
if animals and len(animals) > 1:
|
if animals and len(animals) > 1:
|
||||||
# Show checkbox list for subset selection
|
# Show checkbox list for subset selection
|
||||||
selection_component = Div(
|
selection_content = Div(
|
||||||
P("Select animals for this action:", cls="text-sm text-stone-400 mb-2"),
|
P("Select animals for this action:", cls="text-sm text-stone-400 mb-2"),
|
||||||
animal_checkbox_list(animals, resolved_ids),
|
animal_checkbox_list(animals, resolved_ids),
|
||||||
cls="mb-4",
|
|
||||||
)
|
)
|
||||||
subset_mode = True
|
subset_mode = True
|
||||||
elif resolved_count > 0:
|
elif resolved_count > 0:
|
||||||
selection_component = Div(
|
selection_content = Div(
|
||||||
P(
|
P(
|
||||||
Span(f"{resolved_count}", cls="font-bold text-lg"),
|
Span(f"{resolved_count}", cls="font-bold text-lg"),
|
||||||
" animals selected",
|
" animals selected",
|
||||||
cls="text-sm",
|
cls="text-sm",
|
||||||
),
|
),
|
||||||
cls="p-3 bg-slate-100 dark:bg-slate-800 rounded-md mb-4",
|
cls="p-3 bg-slate-100 dark:bg-slate-800 rounded-md",
|
||||||
)
|
)
|
||||||
elif filter_str:
|
elif filter_str:
|
||||||
selection_component = Div(
|
selection_content = Div(
|
||||||
P("No animals match this filter", cls="text-sm text-amber-600"),
|
P("No animals match this filter", cls="text-sm text-amber-600"),
|
||||||
cls="p-3 bg-amber-50 dark:bg-amber-900/20 rounded-md mb-4",
|
cls="p-3 bg-amber-50 dark:bg-amber-900/20 rounded-md",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Container for HTMX updates - always present so it can be targeted
|
||||||
|
selection_container = Div(
|
||||||
|
selection_content,
|
||||||
|
id="selection-container",
|
||||||
|
cls="mb-4" if selection_content else "",
|
||||||
|
)
|
||||||
|
|
||||||
# Hidden fields for resolved_ids (as multiple values)
|
# Hidden fields for resolved_ids (as multiple values)
|
||||||
resolved_id_fields = [
|
resolved_id_fields = [
|
||||||
Hidden(name="resolved_ids", value=animal_id) for animal_id in resolved_ids
|
Hidden(name="resolved_ids", value=animal_id) for animal_id in resolved_ids
|
||||||
@@ -600,16 +607,20 @@ def tag_add_form(
|
|||||||
H2("Add Tag", cls="text-xl font-bold mb-4"),
|
H2("Add Tag", cls="text-xl font-bold mb-4"),
|
||||||
# Error message if present
|
# Error message if present
|
||||||
error_component,
|
error_component,
|
||||||
# Filter input
|
# Filter input with HTMX to fetch selection preview
|
||||||
LabelInput(
|
LabelInput(
|
||||||
"Filter",
|
"Filter",
|
||||||
id="filter",
|
id="filter",
|
||||||
name="filter",
|
name="filter",
|
||||||
value=filter_str,
|
value=filter_str,
|
||||||
placeholder='e.g., location:"Strip 1" species:duck',
|
placeholder='e.g., location:"Strip 1" species:duck',
|
||||||
|
hx_get="/api/selection-preview",
|
||||||
|
hx_trigger="change, keyup delay:500ms changed",
|
||||||
|
hx_target="#selection-container",
|
||||||
|
hx_swap="innerHTML",
|
||||||
),
|
),
|
||||||
# Selection component (checkboxes or simple count)
|
# Selection container - updated via HTMX when filter changes
|
||||||
selection_component,
|
selection_container,
|
||||||
# Tag input
|
# Tag input
|
||||||
LabelInput(
|
LabelInput(
|
||||||
"Tag",
|
"Tag",
|
||||||
@@ -768,31 +779,38 @@ def tag_end_form(
|
|||||||
error_component = Alert(error, cls=AlertT.warning)
|
error_component = Alert(error, cls=AlertT.warning)
|
||||||
|
|
||||||
# Selection component - show checkboxes if animals provided and > 1
|
# Selection component - show checkboxes if animals provided and > 1
|
||||||
selection_component = None
|
# Wrapped in a container with ID for HTMX targeting
|
||||||
|
selection_content = None
|
||||||
subset_mode = False
|
subset_mode = False
|
||||||
if animals and len(animals) > 1:
|
if animals and len(animals) > 1:
|
||||||
# Show checkbox list for subset selection
|
# Show checkbox list for subset selection
|
||||||
selection_component = Div(
|
selection_content = Div(
|
||||||
P("Select animals for this action:", cls="text-sm text-stone-400 mb-2"),
|
P("Select animals for this action:", cls="text-sm text-stone-400 mb-2"),
|
||||||
animal_checkbox_list(animals, resolved_ids),
|
animal_checkbox_list(animals, resolved_ids),
|
||||||
cls="mb-4",
|
|
||||||
)
|
)
|
||||||
subset_mode = True
|
subset_mode = True
|
||||||
elif resolved_count > 0:
|
elif resolved_count > 0:
|
||||||
selection_component = Div(
|
selection_content = Div(
|
||||||
P(
|
P(
|
||||||
Span(f"{resolved_count}", cls="font-bold text-lg"),
|
Span(f"{resolved_count}", cls="font-bold text-lg"),
|
||||||
" animals selected",
|
" animals selected",
|
||||||
cls="text-sm",
|
cls="text-sm",
|
||||||
),
|
),
|
||||||
cls="p-3 bg-slate-100 dark:bg-slate-800 rounded-md mb-4",
|
cls="p-3 bg-slate-100 dark:bg-slate-800 rounded-md",
|
||||||
)
|
)
|
||||||
elif filter_str:
|
elif filter_str:
|
||||||
selection_component = Div(
|
selection_content = Div(
|
||||||
P("No animals match this filter", cls="text-sm text-amber-600"),
|
P("No animals match this filter", cls="text-sm text-amber-600"),
|
||||||
cls="p-3 bg-amber-50 dark:bg-amber-900/20 rounded-md mb-4",
|
cls="p-3 bg-amber-50 dark:bg-amber-900/20 rounded-md",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Container for HTMX updates - always present so it can be targeted
|
||||||
|
selection_container = Div(
|
||||||
|
selection_content,
|
||||||
|
id="selection-container",
|
||||||
|
cls="mb-4" if selection_content else "",
|
||||||
|
)
|
||||||
|
|
||||||
# Build tag options from active_tags
|
# Build tag options from active_tags
|
||||||
tag_options = [Option("Select tag to end...", value="", disabled=True, selected=True)]
|
tag_options = [Option("Select tag to end...", value="", disabled=True, selected=True)]
|
||||||
for tag in active_tags:
|
for tag in active_tags:
|
||||||
@@ -807,16 +825,20 @@ def tag_end_form(
|
|||||||
H2("End Tag", cls="text-xl font-bold mb-4"),
|
H2("End Tag", cls="text-xl font-bold mb-4"),
|
||||||
# Error message if present
|
# Error message if present
|
||||||
error_component,
|
error_component,
|
||||||
# Filter input
|
# Filter input with HTMX to fetch selection preview
|
||||||
LabelInput(
|
LabelInput(
|
||||||
"Filter",
|
"Filter",
|
||||||
id="filter",
|
id="filter",
|
||||||
name="filter",
|
name="filter",
|
||||||
value=filter_str,
|
value=filter_str,
|
||||||
placeholder="e.g., tag:layer-birds species:duck",
|
placeholder="e.g., tag:layer-birds species:duck",
|
||||||
|
hx_get="/api/selection-preview",
|
||||||
|
hx_trigger="change, keyup delay:500ms changed",
|
||||||
|
hx_target="#selection-container",
|
||||||
|
hx_swap="innerHTML",
|
||||||
),
|
),
|
||||||
# Selection component (checkboxes or simple count)
|
# Selection container - updated via HTMX when filter changes
|
||||||
selection_component,
|
selection_container,
|
||||||
# Tag dropdown
|
# Tag dropdown
|
||||||
LabelSelect(
|
LabelSelect(
|
||||||
*tag_options,
|
*tag_options,
|
||||||
@@ -976,31 +998,38 @@ def attrs_form(
|
|||||||
error_component = Alert(error, cls=AlertT.warning)
|
error_component = Alert(error, cls=AlertT.warning)
|
||||||
|
|
||||||
# Selection component - show checkboxes if animals provided and > 1
|
# Selection component - show checkboxes if animals provided and > 1
|
||||||
selection_component = None
|
# Wrapped in a container with ID for HTMX targeting
|
||||||
|
selection_content = None
|
||||||
subset_mode = False
|
subset_mode = False
|
||||||
if animals and len(animals) > 1:
|
if animals and len(animals) > 1:
|
||||||
# Show checkbox list for subset selection
|
# Show checkbox list for subset selection
|
||||||
selection_component = Div(
|
selection_content = Div(
|
||||||
P("Select animals for this action:", cls="text-sm text-stone-400 mb-2"),
|
P("Select animals for this action:", cls="text-sm text-stone-400 mb-2"),
|
||||||
animal_checkbox_list(animals, resolved_ids),
|
animal_checkbox_list(animals, resolved_ids),
|
||||||
cls="mb-4",
|
|
||||||
)
|
)
|
||||||
subset_mode = True
|
subset_mode = True
|
||||||
elif resolved_count > 0:
|
elif resolved_count > 0:
|
||||||
selection_component = Div(
|
selection_content = Div(
|
||||||
P(
|
P(
|
||||||
Span(f"{resolved_count}", cls="font-bold text-lg"),
|
Span(f"{resolved_count}", cls="font-bold text-lg"),
|
||||||
" animals selected",
|
" animals selected",
|
||||||
cls="text-sm",
|
cls="text-sm",
|
||||||
),
|
),
|
||||||
cls="p-3 bg-slate-100 dark:bg-slate-800 rounded-md mb-4",
|
cls="p-3 bg-slate-100 dark:bg-slate-800 rounded-md",
|
||||||
)
|
)
|
||||||
elif filter_str:
|
elif filter_str:
|
||||||
selection_component = Div(
|
selection_content = Div(
|
||||||
P("No animals match this filter", cls="text-sm text-amber-600"),
|
P("No animals match this filter", cls="text-sm text-amber-600"),
|
||||||
cls="p-3 bg-amber-50 dark:bg-amber-900/20 rounded-md mb-4",
|
cls="p-3 bg-amber-50 dark:bg-amber-900/20 rounded-md",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Container for HTMX updates - always present so it can be targeted
|
||||||
|
selection_container = Div(
|
||||||
|
selection_content,
|
||||||
|
id="selection-container",
|
||||||
|
cls="mb-4" if selection_content else "",
|
||||||
|
)
|
||||||
|
|
||||||
# Build sex options
|
# Build sex options
|
||||||
sex_options = [
|
sex_options = [
|
||||||
Option("No change", value="", selected=True),
|
Option("No change", value="", selected=True),
|
||||||
@@ -1036,16 +1065,20 @@ def attrs_form(
|
|||||||
H2("Update Attributes", cls="text-xl font-bold mb-4"),
|
H2("Update Attributes", cls="text-xl font-bold mb-4"),
|
||||||
# Error message if present
|
# Error message if present
|
||||||
error_component,
|
error_component,
|
||||||
# Filter input
|
# Filter input with HTMX to fetch selection preview
|
||||||
LabelInput(
|
LabelInput(
|
||||||
"Filter",
|
"Filter",
|
||||||
id="filter",
|
id="filter",
|
||||||
name="filter",
|
name="filter",
|
||||||
value=filter_str,
|
value=filter_str,
|
||||||
placeholder="e.g., species:duck life_stage:juvenile",
|
placeholder="e.g., species:duck life_stage:juvenile",
|
||||||
|
hx_get="/api/selection-preview",
|
||||||
|
hx_trigger="change, keyup delay:500ms changed",
|
||||||
|
hx_target="#selection-container",
|
||||||
|
hx_swap="innerHTML",
|
||||||
),
|
),
|
||||||
# Selection component (checkboxes or simple count)
|
# Selection container - updated via HTMX when filter changes
|
||||||
selection_component,
|
selection_container,
|
||||||
# Attribute dropdowns
|
# Attribute dropdowns
|
||||||
LabelSelect(
|
LabelSelect(
|
||||||
*sex_options,
|
*sex_options,
|
||||||
@@ -1222,32 +1255,39 @@ def outcome_form(
|
|||||||
error_component = Alert(error, cls=AlertT.warning)
|
error_component = Alert(error, cls=AlertT.warning)
|
||||||
|
|
||||||
# Selection component - show checkboxes if animals provided and > 1
|
# Selection component - show checkboxes if animals provided and > 1
|
||||||
selection_component = None
|
# Wrapped in a container with ID for HTMX targeting
|
||||||
|
selection_content = None
|
||||||
subset_mode = False
|
subset_mode = False
|
||||||
if animals and len(animals) > 1:
|
if animals and len(animals) > 1:
|
||||||
# Show checkbox list for subset selection
|
# Show checkbox list for subset selection
|
||||||
selection_component = Div(
|
selection_content = Div(
|
||||||
P("Select animals for this action:", cls="text-sm text-stone-400 mb-2"),
|
P("Select animals for this action:", cls="text-sm text-stone-400 mb-2"),
|
||||||
animal_checkbox_list(animals, resolved_ids),
|
animal_checkbox_list(animals, resolved_ids),
|
||||||
cls="mb-4",
|
|
||||||
)
|
)
|
||||||
subset_mode = True
|
subset_mode = True
|
||||||
elif resolved_count > 0:
|
elif resolved_count > 0:
|
||||||
# Fallback to simple count display
|
# Fallback to simple count display
|
||||||
selection_component = Div(
|
selection_content = Div(
|
||||||
P(
|
P(
|
||||||
Span(f"{resolved_count}", cls="font-bold text-lg"),
|
Span(f"{resolved_count}", cls="font-bold text-lg"),
|
||||||
" animals selected",
|
" animals selected",
|
||||||
cls="text-sm",
|
cls="text-sm",
|
||||||
),
|
),
|
||||||
cls="p-3 bg-slate-100 dark:bg-slate-800 rounded-md mb-4",
|
cls="p-3 bg-slate-100 dark:bg-slate-800 rounded-md",
|
||||||
)
|
)
|
||||||
elif filter_str:
|
elif filter_str:
|
||||||
selection_component = Div(
|
selection_content = Div(
|
||||||
P("No animals match this filter", cls="text-sm text-amber-600"),
|
P("No animals match this filter", cls="text-sm text-amber-600"),
|
||||||
cls="p-3 bg-amber-50 dark:bg-amber-900/20 rounded-md mb-4",
|
cls="p-3 bg-amber-50 dark:bg-amber-900/20 rounded-md",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Container for HTMX updates - always present so it can be targeted
|
||||||
|
selection_container = Div(
|
||||||
|
selection_content,
|
||||||
|
id="selection-container",
|
||||||
|
cls="mb-4" if selection_content else "",
|
||||||
|
)
|
||||||
|
|
||||||
# Build outcome options
|
# Build outcome options
|
||||||
outcome_options = [
|
outcome_options = [
|
||||||
Option("Select outcome...", value="", selected=True, disabled=True),
|
Option("Select outcome...", value="", selected=True, disabled=True),
|
||||||
@@ -1323,15 +1363,20 @@ def outcome_form(
|
|||||||
return Form(
|
return Form(
|
||||||
H2("Record Outcome", cls="text-xl font-bold mb-4"),
|
H2("Record Outcome", cls="text-xl font-bold mb-4"),
|
||||||
error_component,
|
error_component,
|
||||||
selection_component,
|
# Filter field with HTMX to fetch selection preview
|
||||||
# Filter field
|
|
||||||
LabelInput(
|
LabelInput(
|
||||||
label="Filter (DSL)",
|
label="Filter (DSL)",
|
||||||
id="filter",
|
id="filter",
|
||||||
name="filter",
|
name="filter",
|
||||||
value=filter_str,
|
value=filter_str,
|
||||||
placeholder="e.g., species:duck location:Coop1",
|
placeholder="e.g., species:duck location:Coop1",
|
||||||
|
hx_get="/api/selection-preview",
|
||||||
|
hx_trigger="change, keyup delay:500ms changed",
|
||||||
|
hx_target="#selection-container",
|
||||||
|
hx_swap="innerHTML",
|
||||||
),
|
),
|
||||||
|
# Selection container - updated via HTMX when filter changes
|
||||||
|
selection_container,
|
||||||
# Outcome selection
|
# Outcome selection
|
||||||
LabelSelect(
|
LabelSelect(
|
||||||
*outcome_options,
|
*outcome_options,
|
||||||
|
|||||||
@@ -89,6 +89,8 @@ def animal_checkbox_list(
|
|||||||
*items,
|
*items,
|
||||||
cls="max-h-64 overflow-y-auto",
|
cls="max-h-64 overflow-y-auto",
|
||||||
),
|
),
|
||||||
|
# Hidden field to indicate subset selection mode
|
||||||
|
Input(type="hidden", name="subset_mode", value="true"),
|
||||||
# Hidden field for roster_hash - will be updated via JS
|
# Hidden field for roster_hash - will be updated via JS
|
||||||
Input(type="hidden", name="roster_hash", id="roster-hash-field"),
|
Input(type="hidden", name="roster_hash", id="roster-hash-field"),
|
||||||
# Script for selection management
|
# Script for selection management
|
||||||
|
|||||||
@@ -66,33 +66,40 @@ def move_form(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Selection component - show checkboxes if animals provided and > 1
|
# Selection component - show checkboxes if animals provided and > 1
|
||||||
selection_component = None
|
# Wrapped in a container with ID for HTMX targeting
|
||||||
|
selection_content = None
|
||||||
subset_mode = False
|
subset_mode = False
|
||||||
if animals and len(animals) > 1:
|
if animals and len(animals) > 1:
|
||||||
# Show checkbox list for subset selection
|
# Show checkbox list for subset selection
|
||||||
location_info = f" from {from_location_name}" if from_location_name else ""
|
location_info = f" from {from_location_name}" if from_location_name else ""
|
||||||
selection_component = Div(
|
selection_content = Div(
|
||||||
P(f"Select animals to move{location_info}:", cls="text-sm text-stone-400 mb-2"),
|
P(f"Select animals to move{location_info}:", cls="text-sm text-stone-400 mb-2"),
|
||||||
animal_checkbox_list(animals, resolved_ids),
|
animal_checkbox_list(animals, resolved_ids),
|
||||||
cls="mb-4",
|
|
||||||
)
|
)
|
||||||
subset_mode = True
|
subset_mode = True
|
||||||
elif resolved_count > 0:
|
elif resolved_count > 0:
|
||||||
location_info = f" from {from_location_name}" if from_location_name else ""
|
location_info = f" from {from_location_name}" if from_location_name else ""
|
||||||
selection_component = Div(
|
selection_content = Div(
|
||||||
P(
|
P(
|
||||||
Span(f"{resolved_count}", cls="font-bold text-lg"),
|
Span(f"{resolved_count}", cls="font-bold text-lg"),
|
||||||
f" animals selected{location_info}",
|
f" animals selected{location_info}",
|
||||||
cls="text-sm",
|
cls="text-sm",
|
||||||
),
|
),
|
||||||
cls="p-3 bg-slate-100 dark:bg-slate-800 rounded-md mb-4",
|
cls="p-3 bg-slate-100 dark:bg-slate-800 rounded-md",
|
||||||
)
|
)
|
||||||
elif filter_str:
|
elif filter_str:
|
||||||
selection_component = Div(
|
selection_content = Div(
|
||||||
P("No animals match this filter", cls="text-sm text-amber-600"),
|
P("No animals match this filter", cls="text-sm text-amber-600"),
|
||||||
cls="p-3 bg-amber-50 dark:bg-amber-900/20 rounded-md mb-4",
|
cls="p-3 bg-amber-50 dark:bg-amber-900/20 rounded-md",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Container for HTMX updates - always present so it can be targeted
|
||||||
|
selection_container = Div(
|
||||||
|
selection_content,
|
||||||
|
id="selection-container",
|
||||||
|
cls="mb-4" if selection_content else "",
|
||||||
|
)
|
||||||
|
|
||||||
# Hidden fields for resolved_ids (as multiple values)
|
# Hidden fields for resolved_ids (as multiple values)
|
||||||
resolved_id_fields = [
|
resolved_id_fields = [
|
||||||
Hidden(name="resolved_ids", value=animal_id) for animal_id in resolved_ids
|
Hidden(name="resolved_ids", value=animal_id) for animal_id in resolved_ids
|
||||||
@@ -102,16 +109,20 @@ def move_form(
|
|||||||
H2("Move Animals", cls="text-xl font-bold mb-4"),
|
H2("Move Animals", cls="text-xl font-bold mb-4"),
|
||||||
# Error message if present
|
# Error message if present
|
||||||
error_component,
|
error_component,
|
||||||
# Filter input
|
# Filter input with HTMX to fetch selection preview
|
||||||
LabelInput(
|
LabelInput(
|
||||||
"Filter",
|
"Filter",
|
||||||
id="filter",
|
id="filter",
|
||||||
name="filter",
|
name="filter",
|
||||||
value=filter_str,
|
value=filter_str,
|
||||||
placeholder='e.g., location:"Strip 1" species:duck',
|
placeholder='e.g., location:"Strip 1" species:duck',
|
||||||
|
hx_get="/api/selection-preview",
|
||||||
|
hx_trigger="change, keyup delay:500ms changed",
|
||||||
|
hx_target="#selection-container",
|
||||||
|
hx_swap="innerHTML",
|
||||||
),
|
),
|
||||||
# Selection component (checkboxes or simple count)
|
# Selection container - updated via HTMX when filter changes
|
||||||
selection_component,
|
selection_container,
|
||||||
# Destination dropdown
|
# Destination dropdown
|
||||||
LabelSelect(
|
LabelSelect(
|
||||||
*location_options,
|
*location_options,
|
||||||
|
|||||||
Reference in New Issue
Block a user