Single-source combobox markup via <template> cloning

Eliminate the Python/JS class-string duplication: the server renders hidden
<template> prototypes (row, pill, include/exclude/modifier pills) using the same
component functions, and search_select.js clones them, filling only the
[data-ss-label] slot, value, and data-* attrs. All Tailwind class strings and DOM
structure now live solely in the Python components — the JS no longer hardcodes
any class. Pill gains an opt-in label_slot; the shell takes a templates list.

Companion issue #8 tracks the further HTMX-idiomatic step of returning rendered
row HTML from the search endpoint.

https://claude.ai/code/session_01XzhXvMvw42CQGc9kmin3GS
This commit is contained in:
Claude
2026-06-08 10:26:46 +00:00
committed by Lukáš Kucharczyk
parent 6bc7da9f2f
commit f210f818a9
4 changed files with 139 additions and 141 deletions
+11 -1
View File
@@ -386,6 +386,7 @@ def Pill(
value: str = "",
removable: bool = False,
extra_class: str = "",
label_slot: bool = False,
attributes: list[HTMLAttribute] | None = None,
) -> SafeText:
"""A small label pill, optionally removable (× button).
@@ -393,6 +394,10 @@ def Pill(
Styling is inline Tailwind utilities; ``data-pill`` / ``data-pill-remove``
are JS hooks only (no CSS attached). ``value`` (when set) becomes
``data-value``; extra ``attributes`` are appended to the outer span.
``label_slot=True`` wraps the label in a ``<span data-ss-label>`` so JS can
fill it when cloning the pill from a server-rendered ``<template>`` (keeps the
markup single-sourced — see ``search_select.py``).
"""
attributes = attributes or []
pill_class = f"{_PILL_CLASS} {extra_class}".strip()
@@ -401,7 +406,12 @@ def Pill(
pill_attrs.append(("data-value", str(value)))
pill_attrs.extend(attributes)
children: list[HTMLTag] = [label]
label_child: HTMLTag = (
Component(tag_name="span", attributes=[("data-ss-label", "")], children=[label])
if label_slot
else label
)
children: list[HTMLTag] = [label_child]
if removable:
children.append(
Component(