diff --git a/lib/mv_web/components/core_components.ex b/lib/mv_web/components/core_components.ex index 10cd6a42..972abdff 100644 --- a/lib/mv_web/components/core_components.ex +++ b/lib/mv_web/components/core_components.ex @@ -1024,10 +1024,10 @@ defmodule MvWeb.CoreComponents do doc: "when true, first header/body column gets sticky left positioning to keep selection controls visible" - attr :viewport_bottom, :any, - default: nil, + attr :infinite_scroll, :boolean, + default: false, doc: - "optional phx-viewport-bottom event (string) rendered on the streamed tbody so the built-in InfiniteScroll hook appends the next page without shifting the scroll position; nil omits the binding" + "when true, renders the :footer slot as a full-width infinite-scroll sentinel row (typically only while more pages remain). The caller wires the actual load trigger via a hook in the slot (e.g. an IntersectionObserver sentinel). This component deliberately adds NO phx-viewport-bottom binding: LiveView's built-in InfiniteScroll scrolls the last child of that element into view after each load, which — on a sentinel/loading row — keeps it in view and cascades into loading every page. An IntersectionObserver sentinel has no such side effect." slot :col, required: true do attr :label, :string @@ -1265,13 +1265,15 @@ defmodule MvWeb.CoreComponents do <%!-- After-rows footer in its own, non-streamed tbody so it stays put - outside the phx-update="stream" container. When viewport_bottom is set it - doubles as the infinite-scroll sentinel: rendered only while more pages - exist, so once the last page loads it disappears and stops firing. --%> + outside the phx-update="stream" container. When infinite_scroll is set it + holds the load sentinel (a hook lives in the :footer slot): rendered only + while more pages remain, so once the last page loads it disappears and the + sentinel stops firing. No phx-viewport-bottom here on purpose — LiveView's + built-in InfiniteScroll would scroll this row into view after each load and + cascade into loading everything. --%> diff --git a/lib/mv_web/live/member_live/index.html.heex b/lib/mv_web/live/member_live/index.html.heex index b985cba8..87e76ff3 100644 --- a/lib/mv_web/live/member_live/index.html.heex +++ b/lib/mv_web/live/member_live/index.html.heex @@ -125,7 +125,7 @@ wrapper_overflow_class="overflow-visible" sticky_header={true} sticky_first_col={true} - viewport_bottom={@more? && "load_more"} + infinite_scroll={@more?} row_click={ fn {_dom_id, member} -> JS.push("select_row_and_navigate", value: %{id: member.id}) diff --git a/test/mv_web/member_live/index_pagination_test.exs b/test/mv_web/member_live/index_pagination_test.exs index e10ffd45..0907ade9 100644 --- a/test/mv_web/member_live/index_pagination_test.exs +++ b/test/mv_web/member_live/index_pagination_test.exs @@ -41,10 +41,10 @@ defmodule MvWeb.MemberLive.IndexPaginationTest do assert row_count(html) == @page_limit # More rows remain, so the infinite-scroll sentinel is armed. - assert html =~ ~s(phx-viewport-bottom="load_more") + assert html =~ ~s(id="members-load-more-sentinel") end - test "viewport-bottom appends the next page and stops at the last page", %{conn: conn} do + test "the sentinel appends the next page and stops at the last page", %{conn: conn} do seed_members(@page_limit + 10) conn = conn_with_oidc_user(conn) {:ok, view, html} = live(conn, ~p"/members") @@ -56,7 +56,7 @@ defmodule MvWeb.MemberLive.IndexPaginationTest do assert row_count(html_after) == @page_limit + 10 # Last page reached: the sentinel is disarmed and a further fetch is a no-op. - refute html_after =~ ~s(phx-viewport-bottom="load_more") + refute html_after =~ ~s(id="members-load-more-sentinel") html_again = render_hook(view, "load_more", %{}) assert row_count(html_again) == @page_limit + 10 end