From 312ed613b37b47d54ce31827514e090e350a84b1 Mon Sep 17 00:00:00 2001 From: Toma Gagne Date: Fri, 25 Apr 2025 16:35:07 -0400 Subject: [PATCH 1/8] Handle onScrolled, onUnscrolled and add option to call onScrolled only on first scroll --- src/primitives/Column.tsx | 1 + src/primitives/types.ts | 9 +++++++ src/primitives/utils/handleNavigation.ts | 1 + src/primitives/utils/withScrolling.ts | 33 ++++++++++++++++++++++++ 4 files changed, 44 insertions(+) diff --git a/src/primitives/Column.tsx b/src/primitives/Column.tsx index a42518ad..1f4d1c3a 100644 --- a/src/primitives/Column.tsx +++ b/src/primitives/Column.tsx @@ -50,6 +50,7 @@ export const Column: Component = (props) => { ) } style={/* @once */ combineStyles(props.style, ColumnStyles)} + onScrolled={/* @once */ props.onScrolled} /> ); }; diff --git a/src/primitives/types.ts b/src/primitives/types.ts index e84b6ba5..098c6581 100644 --- a/src/primitives/types.ts +++ b/src/primitives/types.ts @@ -1,11 +1,17 @@ import type { ElementNode, NodeProps, NodeStyles } from '@lightningtv/solid'; import type { KeyHandler } from '@lightningtv/core/focusManager'; +export type OnScrolled = { + perform: () => void; + onUnscrolled?: () => void; + options?: Record<'onlyOnFirstScroll', boolean>; +}; export type OnSelectedChanged = ( this: NavigableElement, selectedIndex: number, elm: NavigableElement, active: ElementNode, lastSelectedIndex?: number, + onScrolled?: OnScrolled, ) => void; export interface NavigableProps extends NodeProps { /** function to be called when the selected of the component changes */ @@ -40,6 +46,9 @@ export interface NavigableProps extends NodeProps { * Wrap the row so active goes back to the beginning of the row */ wrap?: boolean; + + /** function to be called when the column is scrolled */ + onScrolled?: OnScrolled; } // @ts-expect-error animationSettings is not identical - weird diff --git a/src/primitives/utils/handleNavigation.ts b/src/primitives/utils/handleNavigation.ts index f6060f28..5b5e3111 100644 --- a/src/primitives/utils/handleNavigation.ts +++ b/src/primitives/utils/handleNavigation.ts @@ -84,6 +84,7 @@ export function handleNavigation( navigableThis, active, lastSelected, + navigableThis.onScrolled, ); if (this.plinko) { diff --git a/src/primitives/utils/withScrolling.ts b/src/primitives/utils/withScrolling.ts index ad5dea3f..10f572d4 100644 --- a/src/primitives/utils/withScrolling.ts +++ b/src/primitives/utils/withScrolling.ts @@ -4,6 +4,7 @@ import type { INode, Styles, } from '@lightningtv/core'; +import { OnScrolled } from '../types.js'; // Adds properties expected by withScrolling export interface ScrollableElement extends ElementNode { @@ -13,6 +14,7 @@ export interface ScrollableElement extends ElementNode { endOffset?: number; _targetPosition?: number; _screenOffset?: number; + _initialPosition?: number; } // From the renderer, not exported @@ -35,6 +37,7 @@ export function withScrolling(isRow: boolean) { component?: ElementNode, selectedElement?: ElementNode | ElementText, lastSelected?: number, + onScrolled?: OnScrolled, ) => { let componentRef = component as ScrollableElement; if (typeof selected !== 'number') { @@ -48,6 +51,10 @@ export function withScrolling(isRow: boolean) { ) return; + if (componentRef._initialPosition === undefined) { + componentRef._initialPosition = componentRef[axis]; + } + const lng = componentRef.lng as INode; const screenSize = isRow ? lng.stage.root.width : lng.stage.root.height; // Determine if movement is incremental or decremental @@ -156,9 +163,35 @@ export function withScrolling(isRow: boolean) { // Update position if it has changed if (componentRef[axis] !== nextPosition) { + if (onScrolled) { + handleOnScrolled(onScrolled, componentRef, nextPosition, axis); + } + componentRef[axis] = nextPosition; // Store the new position to keep track during animations componentRef._targetPosition = nextPosition; } }; } + +function handleOnScrolled( + onScrolled: OnScrolled, + componentRef: ScrollableElement, + nextPosition: number, + axis: 'x' | 'y', +) { + if (componentRef._initialPosition !== nextPosition) { + if ( + onScrolled.options?.onlyOnFirstScroll && + componentRef[axis] !== componentRef._initialPosition + ) { + return; + } + onScrolled.perform(); + } else if ( + onScrolled.onUnscrolled && + nextPosition === componentRef._initialPosition + ) { + onScrolled.onUnscrolled(); + } +} From 5e7d4b719bb81cd941207012a6d4458efcc9f34c Mon Sep 17 00:00:00 2001 From: Toma Gagne Date: Mon, 28 Apr 2025 11:58:28 -0400 Subject: [PATCH 2/8] unscrolled not within onScrolled --- src/primitives/Column.tsx | 3 ++- src/primitives/Row.tsx | 2 ++ src/primitives/types.ts | 15 ++++++++++----- src/primitives/utils/handleNavigation.ts | 1 + src/primitives/utils/withScrolling.ts | 23 ++++++++++++++--------- 5 files changed, 29 insertions(+), 15 deletions(-) diff --git a/src/primitives/Column.tsx b/src/primitives/Column.tsx index 1f4d1c3a..1ce9e392 100644 --- a/src/primitives/Column.tsx +++ b/src/primitives/Column.tsx @@ -50,7 +50,8 @@ export const Column: Component = (props) => { ) } style={/* @once */ combineStyles(props.style, ColumnStyles)} - onScrolled={/* @once */ props.onScrolled} + onScrolled={props.onScrolled} + onUnscrolled={props.onUnscrolled} /> ); }; diff --git a/src/primitives/Row.tsx b/src/primitives/Row.tsx index 87f1085f..471d2e99 100644 --- a/src/primitives/Row.tsx +++ b/src/primitives/Row.tsx @@ -49,6 +49,8 @@ export const Row: Component = (props) => { ) } style={/* @once */ combineStyles(props.style, RowStyles)} + onScrolled={props.onScrolled} + onUnscrolled={props.onUnscrolled} /> ); }; diff --git a/src/primitives/types.ts b/src/primitives/types.ts index 098c6581..cb099b05 100644 --- a/src/primitives/types.ts +++ b/src/primitives/types.ts @@ -1,18 +1,22 @@ import type { ElementNode, NodeProps, NodeStyles } from '@lightningtv/solid'; import type { KeyHandler } from '@lightningtv/core/focusManager'; -export type OnScrolled = { +export type OnScrolledCallback = { perform: () => void; - onUnscrolled?: () => void; - options?: Record<'onlyOnFirstScroll', boolean>; + options?: Record; }; + +export type OnScrolledOptions = 'onlyOnFirstScroll'; + export type OnSelectedChanged = ( this: NavigableElement, selectedIndex: number, elm: NavigableElement, active: ElementNode, lastSelectedIndex?: number, - onScrolled?: OnScrolled, + onScrolled?: OnScrolledCallback, + onUnscrolled?: () => void, ) => void; + export interface NavigableProps extends NodeProps { /** function to be called when the selected of the component changes */ onSelectedChanged?: OnSelectedChanged; @@ -48,7 +52,8 @@ export interface NavigableProps extends NodeProps { wrap?: boolean; /** function to be called when the column is scrolled */ - onScrolled?: OnScrolled; + onScrolled?: OnScrolledCallback; + onUnscrolled?: () => void; } // @ts-expect-error animationSettings is not identical - weird diff --git a/src/primitives/utils/handleNavigation.ts b/src/primitives/utils/handleNavigation.ts index 5b5e3111..e5561435 100644 --- a/src/primitives/utils/handleNavigation.ts +++ b/src/primitives/utils/handleNavigation.ts @@ -85,6 +85,7 @@ export function handleNavigation( active, lastSelected, navigableThis.onScrolled, + navigableThis.onUnscrolled, ); if (this.plinko) { diff --git a/src/primitives/utils/withScrolling.ts b/src/primitives/utils/withScrolling.ts index 10f572d4..01ddb53f 100644 --- a/src/primitives/utils/withScrolling.ts +++ b/src/primitives/utils/withScrolling.ts @@ -4,7 +4,7 @@ import type { INode, Styles, } from '@lightningtv/core'; -import { OnScrolled } from '../types.js'; +import { OnScrolledOptions, OnScrolledCallback } from '../types.js'; // Adds properties expected by withScrolling export interface ScrollableElement extends ElementNode { @@ -37,7 +37,8 @@ export function withScrolling(isRow: boolean) { component?: ElementNode, selectedElement?: ElementNode | ElementText, lastSelected?: number, - onScrolled?: OnScrolled, + onScrolled?: OnScrolledCallback, + onUnscrolled?: () => void, ) => { let componentRef = component as ScrollableElement; if (typeof selected !== 'number') { @@ -164,7 +165,13 @@ export function withScrolling(isRow: boolean) { // Update position if it has changed if (componentRef[axis] !== nextPosition) { if (onScrolled) { - handleOnScrolled(onScrolled, componentRef, nextPosition, axis); + handleOnScrolled( + onScrolled, + componentRef, + nextPosition, + axis, + onUnscrolled, + ); } componentRef[axis] = nextPosition; @@ -175,10 +182,11 @@ export function withScrolling(isRow: boolean) { } function handleOnScrolled( - onScrolled: OnScrolled, + onScrolled: OnScrolledCallback, componentRef: ScrollableElement, nextPosition: number, axis: 'x' | 'y', + onUnscrolled?: () => void, ) { if (componentRef._initialPosition !== nextPosition) { if ( @@ -188,10 +196,7 @@ function handleOnScrolled( return; } onScrolled.perform(); - } else if ( - onScrolled.onUnscrolled && - nextPosition === componentRef._initialPosition - ) { - onScrolled.onUnscrolled(); + } else if (onUnscrolled && nextPosition === componentRef._initialPosition) { + onUnscrolled(); } } From b76972ffdce11c9a495e8ece274e98c2955666ce Mon Sep 17 00:00:00 2001 From: Toma Gagne Date: Mon, 28 Apr 2025 12:01:30 -0400 Subject: [PATCH 3/8] refactoring --- src/primitives/utils/withScrolling.ts | 31 ++++++++++----------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/src/primitives/utils/withScrolling.ts b/src/primitives/utils/withScrolling.ts index 01ddb53f..3cf544df 100644 --- a/src/primitives/utils/withScrolling.ts +++ b/src/primitives/utils/withScrolling.ts @@ -164,14 +164,11 @@ export function withScrolling(isRow: boolean) { // Update position if it has changed if (componentRef[axis] !== nextPosition) { - if (onScrolled) { - handleOnScrolled( - onScrolled, - componentRef, - nextPosition, - axis, - onUnscrolled, - ); + if (onScrolled && componentRef._initialPosition !== nextPosition) { + handleOnScrolled(onScrolled, componentRef, nextPosition, axis); + } + if (onUnscrolled && componentRef._initialPosition === nextPosition) { + onUnscrolled(); } componentRef[axis] = nextPosition; @@ -184,19 +181,13 @@ export function withScrolling(isRow: boolean) { function handleOnScrolled( onScrolled: OnScrolledCallback, componentRef: ScrollableElement, - nextPosition: number, axis: 'x' | 'y', - onUnscrolled?: () => void, ) { - if (componentRef._initialPosition !== nextPosition) { - if ( - onScrolled.options?.onlyOnFirstScroll && - componentRef[axis] !== componentRef._initialPosition - ) { - return; - } - onScrolled.perform(); - } else if (onUnscrolled && nextPosition === componentRef._initialPosition) { - onUnscrolled(); + if ( + onScrolled.options?.onlyOnFirstScroll && + componentRef[axis] !== componentRef._initialPosition + ) { + return; } + onScrolled.perform(); } From 80e9b5baa241d9d748f9e614cf816a67a1cdbb5d Mon Sep 17 00:00:00 2001 From: Toma Gagne Date: Mon, 28 Apr 2025 12:01:47 -0400 Subject: [PATCH 4/8] fix --- 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 3cf544df..72488200 100644 --- a/src/primitives/utils/withScrolling.ts +++ b/src/primitives/utils/withScrolling.ts @@ -165,7 +165,7 @@ export function withScrolling(isRow: boolean) { // Update position if it has changed if (componentRef[axis] !== nextPosition) { if (onScrolled && componentRef._initialPosition !== nextPosition) { - handleOnScrolled(onScrolled, componentRef, nextPosition, axis); + handleOnScrolled(onScrolled, componentRef, axis); } if (onUnscrolled && componentRef._initialPosition === nextPosition) { onUnscrolled(); From e7671261405a5c16f9ed09baed9d8b68594d94c7 Mon Sep 17 00:00:00 2001 From: Toma Gagne Date: Mon, 28 Apr 2025 13:14:50 -0400 Subject: [PATCH 5/8] OnScrolled always called on first scroll, no on scroll --- src/primitives/types.ts | 14 +++++--------- src/primitives/utils/withScrolling.ts | 26 ++++++++------------------ 2 files changed, 13 insertions(+), 27 deletions(-) diff --git a/src/primitives/types.ts b/src/primitives/types.ts index cb099b05..3782ec33 100644 --- a/src/primitives/types.ts +++ b/src/primitives/types.ts @@ -1,11 +1,5 @@ import type { ElementNode, NodeProps, NodeStyles } from '@lightningtv/solid'; import type { KeyHandler } from '@lightningtv/core/focusManager'; -export type OnScrolledCallback = { - perform: () => void; - options?: Record; -}; - -export type OnScrolledOptions = 'onlyOnFirstScroll'; export type OnSelectedChanged = ( this: NavigableElement, @@ -13,7 +7,7 @@ export type OnSelectedChanged = ( elm: NavigableElement, active: ElementNode, lastSelectedIndex?: number, - onScrolled?: OnScrolledCallback, + onScrolled?: () => void, onUnscrolled?: () => void, ) => void; @@ -51,8 +45,10 @@ export interface NavigableProps extends NodeProps { */ wrap?: boolean; - /** function to be called when the column is scrolled */ - onScrolled?: OnScrolledCallback; + /** function to be called when scrolled */ + onScrolled?: () => void; + + /** function to be called when unscrolled, back to its initial position */ onUnscrolled?: () => void; } diff --git a/src/primitives/utils/withScrolling.ts b/src/primitives/utils/withScrolling.ts index 72488200..8ee7b56b 100644 --- a/src/primitives/utils/withScrolling.ts +++ b/src/primitives/utils/withScrolling.ts @@ -4,7 +4,6 @@ import type { INode, Styles, } from '@lightningtv/core'; -import { OnScrolledOptions, OnScrolledCallback } from '../types.js'; // Adds properties expected by withScrolling export interface ScrollableElement extends ElementNode { @@ -37,7 +36,7 @@ export function withScrolling(isRow: boolean) { component?: ElementNode, selectedElement?: ElementNode | ElementText, lastSelected?: number, - onScrolled?: OnScrolledCallback, + onScrolled?: () => void, onUnscrolled?: () => void, ) => { let componentRef = component as ScrollableElement; @@ -164,9 +163,14 @@ export function withScrolling(isRow: boolean) { // Update position if it has changed if (componentRef[axis] !== nextPosition) { - if (onScrolled && componentRef._initialPosition !== nextPosition) { - handleOnScrolled(onScrolled, componentRef, axis); + if ( + onScrolled && + componentRef[axis] === componentRef._initialPosition && + componentRef._initialPosition !== nextPosition + ) { + onScrolled(); } + if (onUnscrolled && componentRef._initialPosition === nextPosition) { onUnscrolled(); } @@ -177,17 +181,3 @@ export function withScrolling(isRow: boolean) { } }; } - -function handleOnScrolled( - onScrolled: OnScrolledCallback, - componentRef: ScrollableElement, - axis: 'x' | 'y', -) { - if ( - onScrolled.options?.onlyOnFirstScroll && - componentRef[axis] !== componentRef._initialPosition - ) { - return; - } - onScrolled.perform(); -} From 5743d9607278b43f05b9b3975d47e9fe049f28e0 Mon Sep 17 00:00:00 2001 From: Toma Gagne Date: Tue, 29 Apr 2025 11:25:43 -0400 Subject: [PATCH 6/8] check targetPosition to call OnUnscrolled only once during animation --- src/primitives/utils/withScrolling.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/primitives/utils/withScrolling.ts b/src/primitives/utils/withScrolling.ts index 8ee7b56b..4e006aeb 100644 --- a/src/primitives/utils/withScrolling.ts +++ b/src/primitives/utils/withScrolling.ts @@ -171,7 +171,11 @@ export function withScrolling(isRow: boolean) { onScrolled(); } - if (onUnscrolled && componentRef._initialPosition === nextPosition) { + if ( + onUnscrolled && + componentRef._targetPosition !== componentRef._initialPosition && + componentRef._initialPosition === nextPosition + ) { onUnscrolled(); } From 2cc6e68530f54aed5008a6815025efe9574d825f Mon Sep 17 00:00:00 2001 From: Toma Gagne Date: Sun, 4 May 2025 08:32:42 -0400 Subject: [PATCH 7/8] remove redundant check and refactoring --- src/primitives/Column.tsx | 2 -- src/primitives/Row.tsx | 2 -- src/primitives/utils/handleNavigation.ts | 2 -- src/primitives/utils/withScrolling.ts | 15 +++++++-------- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/src/primitives/Column.tsx b/src/primitives/Column.tsx index 1ce9e392..a42518ad 100644 --- a/src/primitives/Column.tsx +++ b/src/primitives/Column.tsx @@ -50,8 +50,6 @@ export const Column: Component = (props) => { ) } style={/* @once */ combineStyles(props.style, ColumnStyles)} - onScrolled={props.onScrolled} - onUnscrolled={props.onUnscrolled} /> ); }; diff --git a/src/primitives/Row.tsx b/src/primitives/Row.tsx index 471d2e99..87f1085f 100644 --- a/src/primitives/Row.tsx +++ b/src/primitives/Row.tsx @@ -49,8 +49,6 @@ export const Row: Component = (props) => { ) } style={/* @once */ combineStyles(props.style, RowStyles)} - onScrolled={props.onScrolled} - onUnscrolled={props.onUnscrolled} /> ); }; diff --git a/src/primitives/utils/handleNavigation.ts b/src/primitives/utils/handleNavigation.ts index e5561435..f6060f28 100644 --- a/src/primitives/utils/handleNavigation.ts +++ b/src/primitives/utils/handleNavigation.ts @@ -84,8 +84,6 @@ export function handleNavigation( navigableThis, active, lastSelected, - navigableThis.onScrolled, - navigableThis.onUnscrolled, ); if (this.plinko) { diff --git a/src/primitives/utils/withScrolling.ts b/src/primitives/utils/withScrolling.ts index 4e006aeb..e744a18d 100644 --- a/src/primitives/utils/withScrolling.ts +++ b/src/primitives/utils/withScrolling.ts @@ -11,6 +11,8 @@ export interface ScrollableElement extends ElementNode { selected: number; offset?: number; endOffset?: number; + onScrolled?: () => void; + onUnscrolled?: () => void; _targetPosition?: number; _screenOffset?: number; _initialPosition?: number; @@ -36,8 +38,6 @@ export function withScrolling(isRow: boolean) { component?: ElementNode, selectedElement?: ElementNode | ElementText, lastSelected?: number, - onScrolled?: () => void, - onUnscrolled?: () => void, ) => { let componentRef = component as ScrollableElement; if (typeof selected !== 'number') { @@ -164,19 +164,18 @@ export function withScrolling(isRow: boolean) { // Update position if it has changed if (componentRef[axis] !== nextPosition) { if ( - onScrolled && - componentRef[axis] === componentRef._initialPosition && - componentRef._initialPosition !== nextPosition + componentRef.onScrolled && + componentRef[axis] === componentRef._initialPosition ) { - onScrolled(); + componentRef.onScrolled(); } if ( - onUnscrolled && + componentRef.onUnscrolled && componentRef._targetPosition !== componentRef._initialPosition && componentRef._initialPosition === nextPosition ) { - onUnscrolled(); + componentRef.onUnscrolled(); } componentRef[axis] = nextPosition; From f2da5ef2d9c0ab17690397809fcba28da475b7ed Mon Sep 17 00:00:00 2001 From: Toma Gagne Date: Sun, 4 May 2025 08:49:43 -0400 Subject: [PATCH 8/8] remove onScrolled and unScrolled from OnSelectedChanged function type param --- src/primitives/types.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/primitives/types.ts b/src/primitives/types.ts index 3782ec33..64466285 100644 --- a/src/primitives/types.ts +++ b/src/primitives/types.ts @@ -7,8 +7,6 @@ export type OnSelectedChanged = ( elm: NavigableElement, active: ElementNode, lastSelectedIndex?: number, - onScrolled?: () => void, - onUnscrolled?: () => void, ) => void; export interface NavigableProps extends NodeProps {