@@ -502,6 +502,8 @@ def __init__(
502502 """Used to cache :odd pseudoclass state."""
503503 self ._last_scroll_time = monotonic ()
504504 """Time of last scroll."""
505+ self ._user_scroll_interrupt : bool = False
506+ """Has the user interrupted a scroll to end?"""
505507
506508 @property
507509 def is_mounted (self ) -> bool :
@@ -1698,6 +1700,8 @@ def watch_scroll_x(self, old_value: float, new_value: float) -> None:
16981700 self ._refresh_scroll ()
16991701
17001702 def watch_scroll_y (self , old_value : float , new_value : float ) -> None :
1703+ if new_value >= self .max_scroll_y :
1704+ self ._user_scroll_interrupt = True
17011705 self .vertical_scrollbar .position = new_value
17021706 if round (old_value ) != round (new_value ):
17031707 self ._refresh_scroll ()
@@ -2688,9 +2692,20 @@ def scroll_end(
26882692 y_axis: Allow scrolling on Y axis?
26892693
26902694 """
2695+
2696+ if self ._user_scroll_interrupt and not force :
2697+ # Do not scroll to end if a user action has interrupted scrolling
2698+ return
2699+
26912700 if speed is None and duration is None :
26922701 duration = 1.0
26932702
2703+ async def scroll_end_on_complete () -> None :
2704+ """It's possible new content was added before we reached the end."""
2705+ self .scroll_y = self .max_scroll_y
2706+ if on_complete is not None :
2707+ self .call_next (on_complete )
2708+
26942709 # In most cases we'd call self.scroll_to and let it handle the call
26952710 # to do things after a refresh, but here we need the refresh to
26962711 # happen first so that we can get the new self.max_scroll_y (that
@@ -2707,7 +2722,7 @@ def _lazily_scroll_end() -> None:
27072722 duration = duration ,
27082723 easing = easing ,
27092724 force = force ,
2710- on_complete = on_complete ,
2725+ on_complete = scroll_end_on_complete ,
27112726 level = level ,
27122727 )
27132728
@@ -4450,13 +4465,15 @@ def _on_unmount(self) -> None:
44504465 def action_scroll_home (self ) -> None :
44514466 if not self ._allow_scroll :
44524467 raise SkipAction ()
4468+ self ._user_scroll_interrupt = True
44534469 self ._clear_anchor ()
44544470 self .scroll_home (x_axis = self .scroll_y == 0 )
44554471
44564472 def action_scroll_end (self ) -> None :
44574473 if not self ._allow_scroll :
44584474 raise SkipAction ()
44594475 self ._clear_anchor ()
4476+ self ._user_scroll_interrupt = False
44604477 self .scroll_end (x_axis = self .scroll_y == self .is_vertical_scroll_end )
44614478
44624479 def action_scroll_left (self ) -> None :
@@ -4474,24 +4491,28 @@ def action_scroll_right(self) -> None:
44744491 def action_scroll_up (self ) -> None :
44754492 if not self .allow_vertical_scroll :
44764493 raise SkipAction ()
4494+ self ._user_scroll_interrupt = True
44774495 self ._clear_anchor ()
44784496 self .scroll_up ()
44794497
44804498 def action_scroll_down (self ) -> None :
44814499 if not self .allow_vertical_scroll :
44824500 raise SkipAction ()
4501+ self ._user_scroll_interrupt = True
44834502 self ._clear_anchor ()
44844503 self .scroll_down ()
44854504
44864505 def action_page_down (self ) -> None :
44874506 if not self .allow_vertical_scroll :
44884507 raise SkipAction ()
4508+ self ._user_scroll_interrupt = True
44894509 self ._clear_anchor ()
44904510 self .scroll_page_down ()
44914511
44924512 def action_page_up (self ) -> None :
44934513 if not self .allow_vertical_scroll :
44944514 raise SkipAction ()
4515+ self ._user_scroll_interrupt = True
44954516 self ._clear_anchor ()
44964517 self .scroll_page_up ()
44974518
0 commit comments