diff --git a/app/assets/stylesheets/responsive.css b/app/assets/stylesheets/responsive.css index ce7512619..e8a031008 100644 --- a/app/assets/stylesheets/responsive.css +++ b/app/assets/stylesheets/responsive.css @@ -867,6 +867,11 @@ inset-block-start: 64px; } + /* Offset sticky issue list headers below the fixed mobile header. */ + table.list.sticky thead { + inset-block-start: 64px; + } + /* Prevent content from being hidden behind #sticky-issue-header and project-jump when scrolling via anchor links. */ .controller-issues.action-show div.wiki a[name], .controller-issues.action-show #history div[id^="note-"], diff --git a/app/javascript/controllers/sticky_table_header_controller.js b/app/javascript/controllers/sticky_table_header_controller.js index 202d4fe4b..dca61f16c 100644 --- a/app/javascript/controllers/sticky_table_header_controller.js +++ b/app/javascript/controllers/sticky_table_header_controller.js @@ -49,6 +49,8 @@ export default class extends Controller { this.stickyValue = false } }) + }, { + rootMargin: `-${this.stickyTopOffset}px 0px 0px 0px` }) this.resizeObserver = new ResizeObserver(() => { @@ -66,6 +68,9 @@ export default class extends Controller { } syncWidth() { + const headRect = this.headTarget.getBoundingClientRect() + + this.stickyHeader.style.insetInlineStart = `${headRect.left}px` this.stickyHeader.style.width = window.getComputedStyle(this.headTarget).width this.bodyColumns.forEach((col, i) => { const style = window.getComputedStyle(col) @@ -90,10 +95,17 @@ export default class extends Controller { } get isIntersecting() { - return this.headTarget.getBoundingClientRect().top > 0 + return this.headTarget.getBoundingClientRect().top > this.stickyTopOffset } get isHeaderOverflowX() { return this.element.scrollWidth > this.element.clientWidth } + + get stickyTopOffset() { + this.prepare() + + const top = window.getComputedStyle(this.stickyHeader).top + return top === "auto" ? 0 : parseFloat(top) + } }