The id (e.g. id_related_game) sat on the <search-select> wrapper, a
non-labelable custom element. Consequences:
- <label for="id_X"> focused nothing (a11y gap)
- .disabled / .focus() on #id_X silently no-oped
- add_purchase.ts needed a [data-search-select-search] descendant
workaround to gate related_game on the type field
id is now on the [data-search-select-search] <input>, making it a real
labelable, disableable control. add_purchase.ts drops the workaround
and gates via #id_related_game directly. E2e tests updated; new test
asserts label-click focuses the search box.
Closes#30
Replaces the four onSwap-based widgets with TypeScript custom elements
following the pattern from PR #16. Each widget gets a class extending
HTMLElement with connectedCallback/disconnectedCallback, typed props via
register_element + gen_element_types codegen, and lives in ts/elements/.
- range-slider: RangeSliderElement; Python uses _RangeSlider builder
- date-range-picker: DateRangePickerElement; Python uses _DateRangePicker builder
- search-select: SearchSelectElement; Python uses _SearchSelect builder;
data-* attrs become plain attrs (data-name -> name, data-search-url -> search-url, etc.)
- filter-bar: FilterBarElement; props carry preset URLs; onclick/onsubmit
attrs replaced with data-filter-bar-* sentinel attrs; all window.* globals removed
Deletes ts/range_slider.ts, ts/search_select.ts, ts/date_range_picker.ts,
ts/filter_bar.ts. Updates all tests and e2e pages to use the new element
selectors and script paths (dist/elements/<tag>.js).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Two bugs in the add forms, both root-caused via the e2e harness:
1. add_game Name → Sort name never synced. syncSelectInputUntilChanged was
scoped to "form", but the first <form> on every page is the navbar logout
form — the add-form fields live in a later form, so the delegated listener
never heard their events. Scope to "#add-form" (the add-form wrapper). Also
switch the sync from the "change" event to "input" so Sort name mirrors Name
live as you type, not only on blur.
2. add_purchase Related game not disabled when Type == Game.
disableElementsWhenTrue set `.disabled` on #id_related_game, which is the
SearchSelect wrapper <div> (a <div> ignores `disabled`). Target the inner
[data-search-select-search] input instead, so the widget is actually disabled.
Adds two e2e regression tests (live sync; type-game disables the related-game
search input and re-enables it for other types).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Add ts/search_select.ts: typed port of the SearchSelect/FilterSelect widget.
Exports SearchSelectOption / SearchSelectChangeDetail as the single source of
truth for the "search-select:change" event contract
- add_purchase.ts now imports those types via `import type` (no runtime
coupling), instead of redefining them locally
- Declare window.readSearchSelect in ts/globals.d.ts
- Point the SearchSelect component Media and every view/e2e/test reference at
the compiled dist/search_select.js
- Update doc comments in common/components/search_select.py to name the TS source
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add ts/add_purchase.ts: typed port of add_purchase.js. Replaces getEl with
document.querySelector; types the search-select:change CustomEvent detail
(SearchSelectChangeDetail / SearchSelectOption)
- Point add_purchase / edit_purchase views at compiled dist/add_purchase.js
- Delete add_edition.js: no Edition model/view/url/template references it
(feature was removed; the script was dead)
- Delete the now-superseded add_game.js / add_purchase.js source files
- Tighten test_rendered_pages assertions to the dist/ script paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>