From 1e294312bb28c5ab02fd10645b4578e1a93b6305 Mon Sep 17 00:00:00 2001 From: Mirko Pecora Date: Tue, 16 Jun 2026 16:57:10 +0200 Subject: [PATCH 1/3] feat: add scrollStopLast property and update scrolling behavior for last element --- src/primitives/types.ts | 6 ++++++ src/primitives/utils/withScrolling.ts | 14 ++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/primitives/types.ts b/src/primitives/types.ts index dd683a6..188e481 100644 --- a/src/primitives/types.ts +++ b/src/primitives/types.ts @@ -25,6 +25,12 @@ export interface NavigableProps extends NodeProps { /** When auto scrolling, item index at which scrolling begins */ scrollIndex?: number; + /** + * When true and scroll is 'edge' or 'auto', the last item keeps the same + * position as the second-to-last instead of scrolling to remove trailing space. + */ + scrollStopLast?: boolean; + /** The initial index */ selected?: number; diff --git a/src/primitives/utils/withScrolling.ts b/src/primitives/utils/withScrolling.ts index db01780..1ed41ae 100644 --- a/src/primitives/utils/withScrolling.ts +++ b/src/primitives/utils/withScrolling.ts @@ -28,6 +28,7 @@ export interface ScrollableElement extends ElementNode { _targetPosition?: number; _screenOffset?: number; _initialPosition?: number; + scrollStopLast?: boolean; } // From the renderer, not exported @@ -185,7 +186,11 @@ export function withScrolling(isRow: boolean): Scroller { nextPosition = Math.min(Math.max(centerPosition, maxOffset), offset); } else if (!nextElement) { // If at the last element, align to end - nextPosition = isIncrementing ? maxOffset : offset; + if (componentRef.scrollStopLast && isIncrementing) { + nextPosition = rootPosition - selectedSize - gap; + } else { + nextPosition = isIncrementing ? maxOffset : offset; + } } else if (scroll === 'auto') { if (componentRef.scrollIndex && componentRef.scrollIndex > 0) { // Prevent scrolling if the selected item is within the last scrollIndex items @@ -213,8 +218,13 @@ export function withScrolling(isRow: boolean): Scroller { } // Prevent container from moving beyond bounds + const isScrollStopLastCase = + componentRef.scrollStopLast && !nextElement && isIncrementing; nextPosition = - isIncrementing && scroll !== 'always' && scroll !== 'bounded' + isIncrementing && + scroll !== 'always' && + scroll !== 'bounded' && + !isScrollStopLastCase ? Math.max(nextPosition, maxOffset) : Math.min(nextPosition, offset); From dd4cfa8ddb81738f76e06ad349131fb125a25ca8 Mon Sep 17 00:00:00 2001 From: Mirko Pecora Date: Wed, 17 Jun 2026 11:05:37 +0200 Subject: [PATCH 2/3] fix: Add return type to isNotShown --- src/primitives/utils/withScrolling.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/primitives/utils/withScrolling.ts b/src/primitives/utils/withScrolling.ts index 1ed41ae..7884bb9 100644 --- a/src/primitives/utils/withScrolling.ts +++ b/src/primitives/utils/withScrolling.ts @@ -33,7 +33,7 @@ export interface ScrollableElement extends ElementNode { // From the renderer, not exported const InViewPort = 8 as const; -const isNotShown = (node: ElementNode | ElementText) => { +const isNotShown = (node: ElementNode | ElementText): boolean => { return (node.lng.renderState as number) !== InViewPort; }; /* From d2a56de6a3dde72ebc075380a231bf51767d2c7b Mon Sep 17 00:00:00 2001 From: Mirko Pecora Date: Wed, 17 Jun 2026 11:12:01 +0200 Subject: [PATCH 3/3] refactor: Clean up import statements and format scrollToIndex function --- src/primitives/Column.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/primitives/Column.tsx b/src/primitives/Column.tsx index 673530d..2782fb4 100644 --- a/src/primitives/Column.tsx +++ b/src/primitives/Column.tsx @@ -1,9 +1,5 @@ import { type Component } from 'solid-js'; -import { - ElementNode, - combineStyles, - type NodeStyles, -} from '@solidtv/solid'; +import { ElementNode, combineStyles, type NodeStyles } from '@solidtv/solid'; import { navigableForwardFocus, handleNavigation, @@ -20,7 +16,11 @@ const ColumnStyles: NodeStyles = { gap: 30, }; -function scrollToIndex(this: ElementNode, index: number, options?: {noFocus?: boolean}) { +function scrollToIndex( + this: ElementNode, + index: number, + options?: { noFocus?: boolean }, +): void { this.selected = index; scrollColumn(index, this); if (!options?.noFocus) {