From c82f7ddb6ab6aa546a6c49dff26c71ecddb62c0d Mon Sep 17 00:00:00 2001 From: ishikawa999 <14245262+ishikawa999@users.noreply.github.com> Date: Tue, 14 Apr 2026 09:14:15 +0900 Subject: [PATCH 3/4] Hide sticky header when table is outside viewport --- .../sticky_table_header_controller.js | 57 ++++++++++++------- test/system/sticky_table_header_test.rb | 22 +++++++ 2 files changed, 59 insertions(+), 20 deletions(-) diff --git a/app/javascript/controllers/sticky_table_header_controller.js b/app/javascript/controllers/sticky_table_header_controller.js index 2d322a9b6..7269381ca 100644 --- a/app/javascript/controllers/sticky_table_header_controller.js +++ b/app/javascript/controllers/sticky_table_header_controller.js @@ -11,10 +11,9 @@ export default class extends Controller { static values = {sticky: Boolean} connect() { - if (!this.isIntersecting && !this.isHeaderOverflowX) { - this.stickyValue = true; - } - + this.headIntersecting = this.isHeadIntersecting; + this.tableIntersecting = this.isTableIntersecting; + this.updateStickyVisibility(); this.observe(); } @@ -31,38 +30,46 @@ export default class extends Controller { } disconnect() { - this.intersectionObserver?.disconnect(); + this.headIntersectionObserver?.disconnect(); + this.tableIntersectionObserver?.disconnect(); this.resizeObserver?.disconnect(); } observe() { - this.intersectionObserver = new IntersectionObserver(entries => { + this.headIntersectionObserver = new IntersectionObserver(entries => { entries.forEach(entry => { - if (!entry.isIntersecting && !this.isHeaderOverflowX) { - this.stickyValue = true; - } - if (entry.isIntersecting && !this.isHeaderOverflowX) { - this.stickyValue = false; - } + this.headIntersecting = entry.isIntersecting; + this.updateStickyVisibility(); + }) + }, { + rootMargin: `-${this.stickyTopOffset}px 0px 0px 0px` + }); + + this.tableIntersectionObserver = new IntersectionObserver(entries => { + entries.forEach(entry => { + this.tableIntersecting = entry.isIntersecting; + this.updateStickyVisibility(); }) }, { rootMargin: `-${this.stickyTopOffset}px 0px 0px 0px` }); this.resizeObserver = new ResizeObserver(() => { - if (!this.isIntersecting && this.isHeaderOverflowX) { - this.stickyValue = false; - } - if (!this.isIntersecting && !this.isHeaderOverflowX) { - this.stickyValue = true; - } + this.headIntersecting = this.isHeadIntersecting; + this.tableIntersecting = this.isTableIntersecting; + this.updateStickyVisibility(); this.syncWidth(); }); - this.intersectionObserver.observe(this.headTarget); + this.headIntersectionObserver.observe(this.headTarget); + this.tableIntersectionObserver.observe(this.originalTable); this.resizeObserver.observe(this.headTarget); } + updateStickyVisibility() { + this.stickyValue = !this.headIntersecting && this.tableIntersecting && !this.isHeaderOverflowX; + } + syncWidth(e) { const headRect = this.headTarget.getBoundingClientRect() @@ -90,14 +97,24 @@ export default class extends Controller { } } - get isIntersecting() { + get isHeadIntersecting() { return this.headTarget.getBoundingClientRect().top > this.stickyTopOffset } + get isTableIntersecting() { + const tableRect = this.originalTable.getBoundingClientRect(); + + return tableRect.bottom > this.stickyTopOffset && tableRect.top < window.innerHeight + } + get isHeaderOverflowX() { return this.element.scrollWidth > this.element.clientWidth; } + get originalTable() { + return this.headTarget.closest('table'); + } + get stickyTopOffset() { this.prepare() diff --git a/test/system/sticky_table_header_test.rb b/test/system/sticky_table_header_test.rb index 629dfafa4..7339c0d73 100644 --- a/test/system/sticky_table_header_test.rb +++ b/test/system/sticky_table_header_test.rb @@ -65,6 +65,19 @@ class StickyTableHeaderSystemTest < ApplicationSystemTestCase assert page.has_no_css?('table.list.sticky thead', visible: true) end + def test_sticky_table_header_is_hidden_when_issue_list_is_below_viewport + log_user('jsmith', 'jsmith') + page.current_window.resize_to(1600, 300) + + visit '/projects/ecookbook/issues' + # expand the filters to make the issue list further down the page + find('fieldset#options > legend').click + assert page.has_css?('fieldset#options > div', visible: true) + + assert issue_table_below_viewport? + assert page.has_no_css?('table.list.sticky thead', visible: true) + end + private def scroll_issue_list_header_out_of_view @@ -86,6 +99,15 @@ class StickyTableHeaderSystemTest < ApplicationSystemTestCase JS end + def issue_table_below_viewport? + page.evaluate_script(<<~JS) + (function() { + const table = document.querySelector('table.list.issues'); + return table.getBoundingClientRect().top >= window.innerHeight; + })(); + JS + end + def issue_table_head_width page.evaluate_script(<<~JS) (function() { -- 2.50.1 (Apple Git-155)