Honor UntilChanged in sync; let SearchSelect own its disabled look
Follow-ups on the add-form fixes: - syncSelectInputUntilChanged now actually stops mirroring once the user edits the target (the "UntilChanged" contract). The old focus-based stop was a no-op (wrong removeEventListener reference), so live sync kept clobbering a manually-edited Sort name. Track dirty targets in a Set keyed by syncData index; programmatic writes don't fire "input", so only real user edits mark a target dirty. Drops the dead focus listener. - SearchSelect now greys itself when disabled, via has-[:disabled]: utilities on its container class — the visible "box" is the wrapper <div>, so disabling the transparent inner input alone left it looking active. The component owns its disabled appearance; callers only toggle the inner control's `disabled`. - Document the composite-widget disabling approach in CLAUDE.md and the SearchSelect docstring. Extends the e2e tests: sync drops after a manual Sort name edit; disabled related-game wrapper computes opacity 0.5 (and 1 when re-enabled). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,13 @@ This module imports only from ``common.components`` — it has no Django-forms o
|
||||
``data-*`` attributes wired up by ``ts/search_select.ts`` (compiled to
|
||||
``games/static/js/dist/search_select.js``).
|
||||
|
||||
**Disabling**: this is a composite widget — its ``id`` sits on the wrapper
|
||||
``<div>``, which has no ``disabled`` state of its own. To disable it, set
|
||||
``disabled`` on the inner search ``<input>`` (``[data-search-select-search]``);
|
||||
the wrapper then greys itself via the ``has-[:disabled]:`` utilities in
|
||||
``_CONTAINER_CLASS``. Callers toggle only the control's ``disabled`` — never
|
||||
styles. (See ``ts/add_purchase.ts`` gating ``related_game`` on the type field.)
|
||||
|
||||
Option sourcing follows two axes. *Population*: options are either rendered
|
||||
inline up front (``options=``, no ``search_url``) or fetched from ``search_url``.
|
||||
*Completeness*: without a ``search_url`` the inline set is the whole dataset and
|
||||
@@ -47,9 +54,13 @@ LabeledOption = tuple[str, str]
|
||||
# so its pills/hidden inputs flow as direct participants of that row, inline
|
||||
# with the search input. The options panel is absolute, so it sits outside the
|
||||
# flex flow. (border omitted intentionally — see if it's needed later.)
|
||||
# The widget owns its disabled appearance: when any control inside it is
|
||||
# :disabled (e.g. add_purchase.ts disabling the search input), the wrapper greys
|
||||
# itself via :has() — callers only toggle the control's `disabled`, never styles.
|
||||
_CONTAINER_CLASS = (
|
||||
"relative flex flex-wrap items-center gap-1 p-2 "
|
||||
"rounded-base bg-neutral-secondary-medium"
|
||||
"rounded-base bg-neutral-secondary-medium "
|
||||
"has-[:disabled]:opacity-50 has-[:disabled]:cursor-not-allowed"
|
||||
)
|
||||
_PILLS_CLASS = "contents"
|
||||
_SEARCH_CLASS = (
|
||||
|
||||
Reference in New Issue
Block a user