Skip to content

Commit 7e4e3ed

Browse files
committed
[Bug Fix] Message Scroller: direction-aware scroll button visibility
updateButton now derives each button's active state from its own data-direction — an end button activates when away from the bottom, a start button when away from the top — and iterates all button targets. Previously a start-direction button used end logic, so it was inert at the live edge and showed at the wrong end. (cubic P2)
1 parent fb46ef4 commit 7e4e3ed

3 files changed

Lines changed: 31 additions & 9 deletions

File tree

docs/app/javascript/controllers/ruby_ui/message_scroller_controller.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -309,16 +309,27 @@ export default class extends Controller {
309309
return scrollHeight - (scrollTop + clientHeight) <= this.endThresholdValue;
310310
}
311311

312+
isAtStart() {
313+
if (!this.hasViewportTarget) return true;
314+
return this.viewportTarget.scrollTop <= this.endThresholdValue;
315+
}
316+
312317
hasOverflow() {
313318
if (!this.hasViewportTarget) return false;
314319
return this.viewportTarget.scrollHeight - this.viewportTarget.clientHeight > this.endThresholdValue;
315320
}
316321

322+
// Each button activates based on its own direction: an end button when the
323+
// reader is away from the bottom, a start button when away from the top.
317324
updateButton() {
318325
if (!this.hasButtonTarget) return;
319-
const active = this.hasOverflow() && !this.isAtEnd();
320-
this.buttonTarget.setAttribute("data-active", active ? "true" : "false");
321-
// Remove the inert button from the tab order so there are no ghost stops.
322-
this.buttonTarget.setAttribute("tabindex", active ? "0" : "-1");
326+
const overflow = this.hasOverflow();
327+
this.buttonTargets.forEach((button) => {
328+
const toStart = button.dataset.direction === "start";
329+
const active = overflow && (toStart ? !this.isAtStart() : !this.isAtEnd());
330+
button.setAttribute("data-active", active ? "true" : "false");
331+
// Remove the inert button from the tab order so there are no ghost stops.
332+
button.setAttribute("tabindex", active ? "0" : "-1");
333+
});
323334
}
324335
}

gem/lib/ruby_ui/message_scroller/message_scroller_controller.js

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -309,16 +309,27 @@ export default class extends Controller {
309309
return scrollHeight - (scrollTop + clientHeight) <= this.endThresholdValue;
310310
}
311311

312+
isAtStart() {
313+
if (!this.hasViewportTarget) return true;
314+
return this.viewportTarget.scrollTop <= this.endThresholdValue;
315+
}
316+
312317
hasOverflow() {
313318
if (!this.hasViewportTarget) return false;
314319
return this.viewportTarget.scrollHeight - this.viewportTarget.clientHeight > this.endThresholdValue;
315320
}
316321

322+
// Each button activates based on its own direction: an end button when the
323+
// reader is away from the bottom, a start button when away from the top.
317324
updateButton() {
318325
if (!this.hasButtonTarget) return;
319-
const active = this.hasOverflow() && !this.isAtEnd();
320-
this.buttonTarget.setAttribute("data-active", active ? "true" : "false");
321-
// Remove the inert button from the tab order so there are no ghost stops.
322-
this.buttonTarget.setAttribute("tabindex", active ? "0" : "-1");
326+
const overflow = this.hasOverflow();
327+
this.buttonTargets.forEach((button) => {
328+
const toStart = button.dataset.direction === "start";
329+
const active = overflow && (toStart ? !this.isAtStart() : !this.isAtEnd());
330+
button.setAttribute("data-active", active ? "true" : "false");
331+
// Remove the inert button from the tab order so there are no ghost stops.
332+
button.setAttribute("tabindex", active ? "0" : "-1");
333+
});
323334
}
324335
}

0 commit comments

Comments
 (0)