Convert onSwap widgets to custom elements (issue #18)
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>
This commit is contained in:
@@ -22,9 +22,9 @@ def _bar_page(filter_json: str = "") -> str:
|
||||
<head>
|
||||
<title>Boolean filter E2E</title>
|
||||
<script src="/static/js/htmx.min.js"></script>
|
||||
<script src="/static/js/dist/range_slider.js" type="module"></script>
|
||||
<script src="/static/js/dist/search_select.js" type="module"></script>
|
||||
<script src="/static/js/dist/filter_bar.js" type="module"></script>
|
||||
<script src="/static/js/dist/elements/range-slider.js" type="module"></script>
|
||||
<script src="/static/js/dist/elements/search-select.js" type="module"></script>
|
||||
<script src="/static/js/dist/elements/filter-bar.js" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
{FilterBar(filter_json=filter_json, preset_list_url="/p/l", preset_save_url="/p/s")}
|
||||
|
||||
@@ -30,9 +30,9 @@ def _bar_page(filter_json: str = "") -> str:
|
||||
<head>
|
||||
<title>Date filter E2E</title>
|
||||
<script src="/static/js/htmx.min.js"></script>
|
||||
<script src="/static/js/dist/range_slider.js" type="module"></script>
|
||||
<script src="/static/js/dist/search_select.js" type="module"></script>
|
||||
<script src="/static/js/dist/filter_bar.js" type="module"></script>
|
||||
<script src="/static/js/dist/elements/range-slider.js" type="module"></script>
|
||||
<script src="/static/js/dist/elements/search-select.js" type="module"></script>
|
||||
<script src="/static/js/dist/elements/filter-bar.js" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
{PurchaseFilterBar(filter_json=filter_json, preset_list_url="/p/l", preset_save_url="/p/s")}
|
||||
|
||||
@@ -29,10 +29,10 @@ def _bar_page(filter_json: str = "") -> str:
|
||||
<head>
|
||||
<title>Date range picker E2E</title>
|
||||
<script src="/static/js/htmx.min.js"></script>
|
||||
<script src="/static/js/dist/range_slider.js" type="module"></script>
|
||||
<script src="/static/js/dist/search_select.js" type="module"></script>
|
||||
<script src="/static/js/dist/date_range_picker.js" type="module"></script>
|
||||
<script src="/static/js/dist/filter_bar.js" type="module"></script>
|
||||
<script src="/static/js/dist/elements/range-slider.js" type="module"></script>
|
||||
<script src="/static/js/dist/elements/search-select.js" type="module"></script>
|
||||
<script src="/static/js/dist/elements/date-range-picker.js" type="module"></script>
|
||||
<script src="/static/js/dist/elements/filter-bar.js" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
{PurchaseFilterBar(filter_json=filter_json, preset_list_url="/p/l", preset_save_url="/p/s")}
|
||||
@@ -63,7 +63,7 @@ urlpatterns = [
|
||||
]
|
||||
|
||||
|
||||
PICKER = '[data-date-range-picker][data-input-name-prefix="filter-date-purchased"]'
|
||||
PICKER = "date-range-picker"
|
||||
POPUP = PICKER + " [data-date-range-calendar]"
|
||||
HIDDEN_MIN = 'input[name="filter-date-purchased-min"]'
|
||||
HIDDEN_MAX = 'input[name="filter-date-purchased-max"]'
|
||||
|
||||
@@ -24,7 +24,7 @@ def selection_fields_view(request):
|
||||
<html>
|
||||
<head>
|
||||
<script src="/static/js/htmx.min.js"></script>
|
||||
<script type="module" src="/static/js/dist/search_select.js"></script>
|
||||
<script type="module" src="/static/js/dist/elements/search-select.js"></script>
|
||||
<script type="module" src="/static/js/dist/elements/selection-fields.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -66,7 +66,7 @@ urlpatterns = [
|
||||
def test_selection_fields_syncs_with_source(live_server, page: Page):
|
||||
page.goto(live_server.url + "/sf-test/")
|
||||
|
||||
games = page.locator('[data-search-select][data-name="games"]')
|
||||
games = page.locator('search-select[name="games"]')
|
||||
rows = page.locator("selection-fields [data-selection-fields-rows] input")
|
||||
|
||||
# Below min_items (2): nothing rendered.
|
||||
@@ -112,7 +112,7 @@ def authenticated_page(live_server, page: Page, django_user_model) -> Page:
|
||||
|
||||
|
||||
def _select_two_games(page: Page) -> None:
|
||||
games = page.locator('[data-search-select][data-name="games"]')
|
||||
games = page.locator('search-select[name="games"]')
|
||||
games.locator("[data-search-select-search]").click()
|
||||
options = games.locator("[data-search-select-option]")
|
||||
expect(options).to_have_count(2) # prefetched on focus
|
||||
|
||||
@@ -14,9 +14,9 @@ def _bar_page(filter_json: str = "") -> str:
|
||||
<head>
|
||||
<title>Range Slider E2E</title>
|
||||
<script src="/static/js/htmx.min.js"></script>
|
||||
<script src="/static/js/dist/range_slider.js" type="module"></script>
|
||||
<script src="/static/js/dist/search_select.js" type="module"></script>
|
||||
<script src="/static/js/dist/filter_bar.js" type="module"></script>
|
||||
<script src="/static/js/dist/elements/range-slider.js" type="module"></script>
|
||||
<script src="/static/js/dist/elements/search-select.js" type="module"></script>
|
||||
<script src="/static/js/dist/elements/filter-bar.js" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
{FilterBar(filter_json=filter_json, preset_list_url="/p/l", preset_save_url="/p/s")}
|
||||
|
||||
@@ -11,10 +11,9 @@ def e2e_test_view(request):
|
||||
<html>
|
||||
<head>
|
||||
<title>SearchSelect E2E Test</title>
|
||||
<!-- search_select.js is an ES module and initializes via onSwap(),
|
||||
which rides on htmx.onLoad — so htmx must be present. -->
|
||||
<!-- search-select is a custom element; htmx must be present for filter_bar. -->
|
||||
<script src="/static/js/htmx.min.js"></script>
|
||||
<script type="module" src="/static/js/dist/search_select.js"></script>
|
||||
<script type="module" src="/static/js/dist/elements/search-select.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div style="padding: 50px;">
|
||||
@@ -52,7 +51,7 @@ def test_search_select_backspace_clears_single_select(live_server, page):
|
||||
# Inject our event logger
|
||||
page.evaluate("""() => {
|
||||
const s = document.querySelector('input[data-search-select-search]');
|
||||
const c = document.querySelector('[data-search-select]');
|
||||
const c = document.querySelector('search-select');
|
||||
s.addEventListener('focus', () => console.log('JS-EVENT: focus, dirty=' + c._searchSelectDirty + ', value="' + s.value + '"'));
|
||||
s.addEventListener('blur', () => console.log('JS-EVENT: blur, dirty=' + c._searchSelectDirty + ', value="' + s.value + '"'));
|
||||
s.addEventListener('input', () => console.log('JS-EVENT: input, dirty=' + c._searchSelectDirty + ', value="' + s.value + '"'));
|
||||
|
||||
@@ -17,9 +17,9 @@ def _bar_page(filter_json: str = "") -> str:
|
||||
<head>
|
||||
<title>String filter E2E</title>
|
||||
<script src="/static/js/htmx.min.js"></script>
|
||||
<script src="/static/js/dist/range_slider.js" type="module"></script>
|
||||
<script src="/static/js/dist/search_select.js" type="module"></script>
|
||||
<script src="/static/js/dist/filter_bar.js" type="module"></script>
|
||||
<script src="/static/js/dist/elements/range-slider.js" type="module"></script>
|
||||
<script src="/static/js/dist/elements/search-select.js" type="module"></script>
|
||||
<script src="/static/js/dist/elements/filter-bar.js" type="module"></script>
|
||||
</head>
|
||||
<body>
|
||||
{PlatformFilterBar(filter_json=filter_json, preset_list_url="/p/l", preset_save_url="/p/s")}
|
||||
|
||||
+11
-13
@@ -31,7 +31,7 @@ def open_filter_bar(page: Page) -> None:
|
||||
|
||||
|
||||
def status_filter_widget(page: Page):
|
||||
return page.locator('[data-search-select][data-name="status"]')
|
||||
return page.locator('search-select[name="status"]')
|
||||
|
||||
|
||||
def test_search_select_initializes_on_page_load(authenticated_page: Page, live_server):
|
||||
@@ -78,12 +78,11 @@ def test_range_slider_mode_toggle_fires_exactly_once(
|
||||
page.goto(f"{live_server.url}{reverse('games:list_games')}")
|
||||
open_filter_bar(page)
|
||||
|
||||
block = page.locator(".range-slider-block").first
|
||||
slider = block.locator(".range-slider")
|
||||
expect(slider).to_have_attribute("data-mode", "range")
|
||||
slider = page.locator("range-slider").first
|
||||
expect(slider).to_have_attribute("mode", "range")
|
||||
|
||||
block.locator(".range-mode-toggle").click()
|
||||
expect(slider).to_have_attribute("data-mode", "point")
|
||||
slider.locator(".range-mode-toggle").click()
|
||||
expect(slider).to_have_attribute("mode", "point")
|
||||
|
||||
|
||||
def test_widgets_initialize_inside_htmx_swapped_content(
|
||||
@@ -110,11 +109,10 @@ def test_widgets_initialize_inside_htmx_swapped_content(
|
||||
widget.locator("[data-search-select-search]").click()
|
||||
expect(widget.locator("[data-search-select-options]")).to_be_visible()
|
||||
|
||||
block = page.locator(".range-slider-block").first
|
||||
slider = block.locator(".range-slider")
|
||||
expect(slider).to_have_attribute("data-mode", "range")
|
||||
block.locator(".range-mode-toggle").click()
|
||||
expect(slider).to_have_attribute("data-mode", "point")
|
||||
slider = page.locator("range-slider").first
|
||||
expect(slider).to_have_attribute("mode", "range")
|
||||
slider.locator(".range-mode-toggle").click()
|
||||
expect(slider).to_have_attribute("mode", "point")
|
||||
|
||||
|
||||
def test_add_purchase_type_toggles_disabled_fields(
|
||||
@@ -149,9 +147,9 @@ def test_add_purchase_related_game_is_flat_game_search(
|
||||
page = authenticated_page
|
||||
page.goto(f"{live_server.url}{reverse('games:add_purchase')}")
|
||||
|
||||
related = page.locator('[data-search-select][data-name="related_game"]')
|
||||
related = page.locator('search-select[name="related_game"]')
|
||||
expect(related).to_have_count(1)
|
||||
expect(related).to_have_attribute("data-search-url", "/api/games/search")
|
||||
expect(related).to_have_attribute("search-url", "/api/games/search")
|
||||
|
||||
|
||||
def test_searchselect_border_matches_native_input(
|
||||
|
||||
Reference in New Issue
Block a user