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) { diff --git a/src/primitives/types.ts b/src/primitives/types.ts index 0cfe6dd..4a44f73 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..7884bb9 100644 --- a/src/primitives/utils/withScrolling.ts +++ b/src/primitives/utils/withScrolling.ts @@ -28,11 +28,12 @@ export interface ScrollableElement extends ElementNode { _targetPosition?: number; _screenOffset?: number; _initialPosition?: number; + scrollStopLast?: boolean; } // 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; }; /* @@ -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);