From 41691503d2f587049152d1367572af66b73420d7 Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Fri, 29 Aug 2025 15:22:15 +0300 Subject: [PATCH 01/30] Changed scroll animation --- packages/ui/Scroller/ScrollerBasic.js | 46 ++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index 5091edc81a..7d4043bc8a 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -1,5 +1,6 @@ import EnactPropTypes from '@enact/core/internal/prop-types'; import {platform} from '@enact/core/platform'; +import {perfNow} from '@enact/core/util'; import classNames from 'classnames'; import PropTypes from 'prop-types'; import {Component} from 'react'; @@ -79,6 +80,8 @@ class ScrollerBasic extends Component { } } + scrollAnimationId = null; + scrollBounds = { clientWidth: 0, clientHeight: 0, @@ -116,9 +119,50 @@ class ScrollerBasic extends Component { } } + startTime = 0; + // scrollMode 'native' scrollToPosition (left, top, behavior) { - this.props.scrollContentRef.current.scrollTo({left: this.getRtlPositionX(left), top, behavior}); + const node = this.props.scrollContentRef.current; + const targetX = this.getRtlPositionX(left); + const targetY = top; + + if (platform.chrome && behavior === 'smooth') { + this.animateScroll(targetX, targetY, node); + } + + node.scrollTo({left: targetX, top: targetY, behavior}); + } + + // scrollMode 'native' + animateScroll (left, top, node) { + if (this.scrollAnimationId) { + window.cancelAnimationFrame(this.scrollAnimationId); + this.scrollAnimationId = null; + } + + const startX = node.scrollLeft; + const startY = node.scrollTop; + + const duration = 300; + const startTime = perfNow(); + + const animateScroll = (now) => { + const elapsed = (now - startTime) / duration; + const time = Math.min(1, elapsed); + + const currX = Math.round(startX + (left - startX) * elapsed); + const currY = Math.round(startY + (top - startY) * elapsed); + + if (time < 1) { + node.scrollTo({left: currX, top: currY}); + this.scrollAnimationId = window.requestAnimationFrame(animateScroll); + } + } + + if (this.scrollBounds.maxTop === startY) return; + + this.scrollAnimationId = window.requestAnimationFrame(animateScroll); } // scrollMode 'native' From e89ac70c684f92e4751005d50f1cb20822a21f9d Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Mon, 1 Sep 2025 15:07:23 +0300 Subject: [PATCH 02/30] Changed timing animation function --- packages/ui/Scroller/ScrollerBasic.js | 32 +++++++++++++-------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index 7d4043bc8a..2bca14d46d 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -119,49 +119,47 @@ class ScrollerBasic extends Component { } } - startTime = 0; - // scrollMode 'native' scrollToPosition (left, top, behavior) { const node = this.props.scrollContentRef.current; - const targetX = this.getRtlPositionX(left); - const targetY = top; if (platform.chrome && behavior === 'smooth') { - this.animateScroll(targetX, targetY, node); + this.animateScroll(this.getRtlPositionX(left), top, node); } - node.scrollTo({left: targetX, top: targetY, behavior}); + node.scrollTo({left: this.getRtlPositionX(left), top, behavior}); } // scrollMode 'native' animateScroll (left, top, node) { + const {scrollLeft, scrollTop} = node; + if (this.scrollAnimationId) { window.cancelAnimationFrame(this.scrollAnimationId); this.scrollAnimationId = null; } - const startX = node.scrollLeft; - const startY = node.scrollTop; + if (this.scrollBounds.maxTop === scrollTop) return; - const duration = 300; + const animationDuration = 1000; const startTime = perfNow(); + const easeOutQuart = (t) => 1 - Math.pow(1 - t, 4); const animateScroll = (now) => { - const elapsed = (now - startTime) / duration; - const time = Math.min(1, elapsed); + const elapsed = (now - startTime); + const time = Math.min(animationDuration, elapsed); + const e = easeOutQuart(time / animationDuration); - const currX = Math.round(startX + (left - startX) * elapsed); - const currY = Math.round(startY + (top - startY) * elapsed); + const currX = Math.round(scrollLeft + (left - scrollLeft) * e); + const currY = Math.round(scrollTop + (top - scrollTop) * e); - if (time < 1) { - node.scrollTo({left: currX, top: currY}); + node.scrollTo({left: currX, top: currY}); + + if (time < animationDuration) { this.scrollAnimationId = window.requestAnimationFrame(animateScroll); } } - if (this.scrollBounds.maxTop === startY) return; - this.scrollAnimationId = window.requestAnimationFrame(animateScroll); } From 0c030260d242ab2dff7c0cff6216af8001c9f841 Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Mon, 1 Sep 2025 15:16:30 +0300 Subject: [PATCH 03/30] Fixed lint warnings --- packages/ui/Scroller/ScrollerBasic.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index 2bca14d46d..5c9b1a811b 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -158,7 +158,7 @@ class ScrollerBasic extends Component { if (time < animationDuration) { this.scrollAnimationId = window.requestAnimationFrame(animateScroll); } - } + }; this.scrollAnimationId = window.requestAnimationFrame(animateScroll); } From 814da10bf23630c1c8c29d0888564771ab8826c2 Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Tue, 2 Sep 2025 18:03:55 +0300 Subject: [PATCH 04/30] Adjusted animation function --- packages/ui/Scroller/ScrollerBasic.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index 5c9b1a811b..c2aff363ad 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -133,14 +133,13 @@ class ScrollerBasic extends Component { // scrollMode 'native' animateScroll (left, top, node) { const {scrollLeft, scrollTop} = node; + const {maxLeft, maxTop} = this.scrollBounds; if (this.scrollAnimationId) { window.cancelAnimationFrame(this.scrollAnimationId); this.scrollAnimationId = null; } - if (this.scrollBounds.maxTop === scrollTop) return; - const animationDuration = 1000; const startTime = perfNow(); const easeOutQuart = (t) => 1 - Math.pow(1 - t, 4); @@ -153,9 +152,10 @@ class ScrollerBasic extends Component { const currX = Math.round(scrollLeft + (left - scrollLeft) * e); const currY = Math.round(scrollTop + (top - scrollTop) * e); - node.scrollTo({left: currX, top: currY}); - - if (time < animationDuration) { + if ((left < 0 || top < 0 || (maxLeft > 0 && left > maxLeft) || (maxTop > 0 && top > maxTop))) { + window.cancelAnimationFrame(this.scrollAnimationId); + } else if (time < animationDuration) { + node.scrollTo({left: currX, top: currY}); this.scrollAnimationId = window.requestAnimationFrame(animateScroll); } }; From 4437865fe0a2da96e3a2205dc0f686d636263e66 Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Thu, 4 Sep 2025 11:31:35 +0300 Subject: [PATCH 05/30] Adjusted timing function --- packages/ui/Scroller/ScrollerBasic.js | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index c2aff363ad..03e2ea5c89 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -125,15 +125,14 @@ class ScrollerBasic extends Component { if (platform.chrome && behavior === 'smooth') { this.animateScroll(this.getRtlPositionX(left), top, node); + } else { + node.scrollTo({left: this.getRtlPositionX(left), top, behavior}); } - - node.scrollTo({left: this.getRtlPositionX(left), top, behavior}); } // scrollMode 'native' animateScroll (left, top, node) { const {scrollLeft, scrollTop} = node; - const {maxLeft, maxTop} = this.scrollBounds; if (this.scrollAnimationId) { window.cancelAnimationFrame(this.scrollAnimationId); @@ -144,19 +143,18 @@ class ScrollerBasic extends Component { const startTime = perfNow(); const easeOutQuart = (t) => 1 - Math.pow(1 - t, 4); - const animateScroll = (now) => { - const elapsed = (now - startTime); - const time = Math.min(animationDuration, elapsed); - const e = easeOutQuart(time / animationDuration); + const animateScroll = () => { + const elapsed = Math.max(15, perfNow() - startTime); + const e = easeOutQuart(elapsed / animationDuration); const currX = Math.round(scrollLeft + (left - scrollLeft) * e); const currY = Math.round(scrollTop + (top - scrollTop) * e); - if ((left < 0 || top < 0 || (maxLeft > 0 && left > maxLeft) || (maxTop > 0 && top > maxTop))) { - window.cancelAnimationFrame(this.scrollAnimationId); - } else if (time < animationDuration) { - node.scrollTo({left: currX, top: currY}); + if (elapsed < animationDuration) { + node.scrollTo({top: currY, left: currX}); this.scrollAnimationId = window.requestAnimationFrame(animateScroll); + } else { + window.cancelAnimationFrame(this.scrollAnimationId); } }; From 47bf3f8f369e5b8997714cbfb29455f8cc1257a9 Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Thu, 4 Sep 2025 17:11:46 +0300 Subject: [PATCH 06/30] Added `unit-tests` --- .../ui/Scroller/tests/ScrollerBasic-specs.js | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 packages/ui/Scroller/tests/ScrollerBasic-specs.js diff --git a/packages/ui/Scroller/tests/ScrollerBasic-specs.js b/packages/ui/Scroller/tests/ScrollerBasic-specs.js new file mode 100644 index 0000000000..b7ffe9a934 --- /dev/null +++ b/packages/ui/Scroller/tests/ScrollerBasic-specs.js @@ -0,0 +1,41 @@ +import '@testing-library/jest-dom'; + +import {ScrollerBasic} from '../Scroller'; + +describe('ScrollBasic', () => { + let scrollContentRef; + + beforeEach(() => { + scrollContentRef = { + current: { + scrollLeft: 0, + scrollTop: 0, + scrollTo: jest.fn() + } + }; + }); + + test( + 'should call scrollTo on scrollToPosition', + async () => { + const instance = new ScrollerBasic({scrollContentRef, direction: 'both'}); + + instance.scrollToPosition(30, 40, 'smooth'); + expect(scrollContentRef.current.scrollTo).toHaveBeenCalledWith({left: 30, top: 40, behavior: 'smooth'}); + + instance.scrollToPosition(30, 40, 'instant'); + expect(scrollContentRef.current.scrollTo).toHaveBeenCalledWith({left: 30, top: 40, behavior: 'instant'}); + } + ); + + test( + 'should call requestAnimationFrame on animateScroll', + async () => { + const requestAnimationFrame = jest.spyOn(window, 'requestAnimationFrame').mockImplementation(() => {}); + const instance = new ScrollerBasic({scrollContentRef, direction: 'both'}); + + instance.animateScroll(30, 40, scrollContentRef.current); + expect(requestAnimationFrame).toHaveBeenCalled(); + } + ); +}); \ No newline at end of file From 15066bec15d444212124d8502e04f8586d8b988a Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Thu, 4 Sep 2025 18:12:57 +0300 Subject: [PATCH 07/30] Added `unit-tests` --- packages/ui/Scroller/tests/ScrollerBasic-specs.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/ui/Scroller/tests/ScrollerBasic-specs.js b/packages/ui/Scroller/tests/ScrollerBasic-specs.js index b7ffe9a934..f020c912e4 100644 --- a/packages/ui/Scroller/tests/ScrollerBasic-specs.js +++ b/packages/ui/Scroller/tests/ScrollerBasic-specs.js @@ -31,11 +31,12 @@ describe('ScrollBasic', () => { test( 'should call requestAnimationFrame on animateScroll', async () => { - const requestAnimationFrame = jest.spyOn(window, 'requestAnimationFrame').mockImplementation(() => {}); + const requestAnimationFrame = jest.spyOn(window, 'requestAnimationFrame').mockImplementation(() => 1); const instance = new ScrollerBasic({scrollContentRef, direction: 'both'}); - instance.animateScroll(30, 40, scrollContentRef.current); + instance.animateScroll(0, 100, scrollContentRef.current); + instance.animateScroll(0, 200, scrollContentRef.current); expect(requestAnimationFrame).toHaveBeenCalled(); } ); -}); \ No newline at end of file +}); From 42c9da21fdc0bc1f70ebf94f89057dbd00c19166 Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Thu, 4 Sep 2025 18:46:08 +0300 Subject: [PATCH 08/30] Added `unit-tests` --- .../ui/Scroller/tests/ScrollerBasic-specs.js | 46 ++++++++++++++++--- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/packages/ui/Scroller/tests/ScrollerBasic-specs.js b/packages/ui/Scroller/tests/ScrollerBasic-specs.js index f020c912e4..c61559d80a 100644 --- a/packages/ui/Scroller/tests/ScrollerBasic-specs.js +++ b/packages/ui/Scroller/tests/ScrollerBasic-specs.js @@ -17,7 +17,7 @@ describe('ScrollBasic', () => { test( 'should call scrollTo on scrollToPosition', - async () => { + () => { const instance = new ScrollerBasic({scrollContentRef, direction: 'both'}); instance.scrollToPosition(30, 40, 'smooth'); @@ -29,14 +29,46 @@ describe('ScrollBasic', () => { ); test( - 'should call requestAnimationFrame on animateScroll', - async () => { - const requestAnimationFrame = jest.spyOn(window, 'requestAnimationFrame').mockImplementation(() => 1); + 'should call scrollTo with animated values during animateScroll', + () => { + let now = 0; + let rafCallback; const instance = new ScrollerBasic({scrollContentRef, direction: 'both'}); - instance.animateScroll(0, 100, scrollContentRef.current); - instance.animateScroll(0, 200, scrollContentRef.current); - expect(requestAnimationFrame).toHaveBeenCalled(); + jest.useFakeTimers(); + jest.spyOn(require('@enact/core/util'), 'perfNow').mockImplementation(() => now); + window.requestAnimationFrame = jest.fn((cb) => { + rafCallback = cb; + return 1; + }); + window.cancelAnimationFrame = jest.fn(); + + instance.animateScroll(100, 200, scrollContentRef.current); + + now = 500; + rafCallback(); + expect(scrollContentRef.current.scrollTo).toHaveBeenCalled(); + + now = 1001; + rafCallback(); + expect(window.cancelAnimationFrame).toHaveBeenCalled(); + + jest.useRealTimers(); + } + ); + + test( + 'should cancel previous animation frame if one exists', + () => { + const instance = new ScrollerBasic({scrollContentRef, direction: 'both'}); + instance.scrollAnimationId = 1; + window.cancelAnimationFrame = jest.fn(); + window.requestAnimationFrame = jest.fn(() => 2); + + instance.animateScroll(100, 200, scrollContentRef.current); + + expect(window.cancelAnimationFrame).toHaveBeenCalledWith(1); + expect(instance.scrollAnimationId).toBe(2); } ); }); From fe649752255fd83359563bb7960985c46a3711f3 Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Wed, 17 Sep 2025 10:38:48 +0300 Subject: [PATCH 09/30] Adjusted animation function --- packages/ui/Scroller/ScrollerBasic.js | 63 ++++++++++++++------------- packages/ui/useScroll/useScroll.js | 6 +++ 2 files changed, 39 insertions(+), 30 deletions(-) diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index 03e2ea5c89..9cf1701309 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -4,6 +4,8 @@ import {perfNow} from '@enact/core/util'; import classNames from 'classnames'; import PropTypes from 'prop-types'; import {Component} from 'react'; +import Lenis from 'lenis'; +import {ReactLenis} from 'lenis/react'; import css from './Scroller.module.less'; @@ -69,6 +71,12 @@ class ScrollerBasic extends Component { scrollContentRef: EnactPropTypes.ref }; + constructor(props) { + super(props); + + this.state = {animating: false}; + } + componentDidMount () { this.calculateMetrics(); } @@ -80,6 +88,10 @@ class ScrollerBasic extends Component { } } + scrollAnimation = { + id: null, + isAnimating: false + } scrollAnimationId = null; scrollBounds = { @@ -96,6 +108,8 @@ class ScrollerBasic extends Component { left: 0 }; + scrollAnimating = false; + getScrollBounds = () => this.scrollBounds; getRtlPositionX = (x) => { @@ -132,33 +146,20 @@ class ScrollerBasic extends Component { // scrollMode 'native' animateScroll (left, top, node) { - const {scrollLeft, scrollTop} = node; - - if (this.scrollAnimationId) { - window.cancelAnimationFrame(this.scrollAnimationId); - this.scrollAnimationId = null; - } - - const animationDuration = 1000; - const startTime = perfNow(); - const easeOutQuart = (t) => 1 - Math.pow(1 - t, 4); - const animateScroll = () => { - const elapsed = Math.max(15, perfNow() - startTime); - const e = easeOutQuart(elapsed / animationDuration); - - const currX = Math.round(scrollLeft + (left - scrollLeft) * e); - const currY = Math.round(scrollTop + (top - scrollTop) * e); - - if (elapsed < animationDuration) { - node.scrollTo({top: currY, left: currX}); - this.scrollAnimationId = window.requestAnimationFrame(animateScroll); - } else { - window.cancelAnimationFrame(this.scrollAnimationId); - } + this.scrollAnimation.isAnimating = true; + node.scrollBy({top: 20, left: 0}); + this.scrollAnimation.id = window.requestAnimationFrame(animateScroll); }; - this.scrollAnimationId = window.requestAnimationFrame(animateScroll); + if (!this.scrollAnimation.isAnimating) { + this.scrollAnimation.id = window.requestAnimationFrame(animateScroll); + } + } + + stopAnimatedScroll () { + this.scrollAnimation.isAnimating = false; + window.cancelAnimationFrame(this.scrollAnimation.id); } // scrollMode 'native' @@ -242,12 +243,14 @@ class ScrollerBasic extends Component { delete rest.setThemeScrollContentHandle; return ( -
+ +
+ ); } } diff --git a/packages/ui/useScroll/useScroll.js b/packages/ui/useScroll/useScroll.js index 0307b8027b..7eebf4e3b1 100644 --- a/packages/ui/useScroll/useScroll.js +++ b/packages/ui/useScroll/useScroll.js @@ -810,11 +810,16 @@ const useScrollBase = (props) => { scrollByPage(ev.keyCode); } } else { + if (ev.repeat) return; props.preventScroll?.(ev); forward('onKeyDown', ev, props); } } + function onKeyUp () { + scrollContentHandle.current.stopAnimatedScroll(); + } + function scrollToAccumulatedTarget (delta, vertical, overscrollEffect) { if (!mutableRef.current.isScrollAnimationTargetAccumulated) { mutableRef.current.accumulatedTargetX = mutableRef.current.scrollLeft; @@ -1454,6 +1459,7 @@ const useScrollBase = (props) => { function addEventListeners () { utilEvent('wheel').addEventListener(scrollContainerRef, onWheel); utilEvent('keydown').addEventListener(scrollContainerRef, onKeyDown); + utilEvent('keyup').addEventListener(scrollContainerRef, onKeyUp); utilEvent('mousedown').addEventListener(scrollContainerRef, onMouseDown); // scrollMode 'native' [[ From 1d790e701fa997e043183387cc3c0387070f05af Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Wed, 17 Sep 2025 11:03:47 +0300 Subject: [PATCH 10/30] Fixed lint warnings --- packages/ui/Scroller/ScrollerBasic.js | 28 +++++++-------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index 9cf1701309..d7bfed71af 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -1,11 +1,8 @@ import EnactPropTypes from '@enact/core/internal/prop-types'; import {platform} from '@enact/core/platform'; -import {perfNow} from '@enact/core/util'; import classNames from 'classnames'; import PropTypes from 'prop-types'; import {Component} from 'react'; -import Lenis from 'lenis'; -import {ReactLenis} from 'lenis/react'; import css from './Scroller.module.less'; @@ -71,12 +68,6 @@ class ScrollerBasic extends Component { scrollContentRef: EnactPropTypes.ref }; - constructor(props) { - super(props); - - this.state = {animating: false}; - } - componentDidMount () { this.calculateMetrics(); } @@ -91,8 +82,7 @@ class ScrollerBasic extends Component { scrollAnimation = { id: null, isAnimating: false - } - scrollAnimationId = null; + }; scrollBounds = { clientWidth: 0, @@ -108,8 +98,6 @@ class ScrollerBasic extends Component { left: 0 }; - scrollAnimating = false; - getScrollBounds = () => this.scrollBounds; getRtlPositionX = (x) => { @@ -243,14 +231,12 @@ class ScrollerBasic extends Component { delete rest.setThemeScrollContentHandle; return ( - -
- +
); } } From acaf2525e9705070e12497ec704f2e729ba4868f Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Wed, 17 Sep 2025 12:57:48 +0300 Subject: [PATCH 11/30] Adjustments --- packages/ui/Scroller/ScrollerBasic.js | 8 ++++++-- packages/ui/useScroll/useScroll.js | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index d7bfed71af..9214ab6afb 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -81,7 +81,11 @@ class ScrollerBasic extends Component { scrollAnimation = { id: null, - isAnimating: false + isAnimating: false, + previousPosition: { + top: 0, + left: 0 + }, }; scrollBounds = { @@ -136,7 +140,7 @@ class ScrollerBasic extends Component { animateScroll (left, top, node) { const animateScroll = () => { this.scrollAnimation.isAnimating = true; - node.scrollBy({top: 20, left: 0}); + node.scrollBy({top: 20, left: 0, behavior: 'auto'}); this.scrollAnimation.id = window.requestAnimationFrame(animateScroll); }; diff --git a/packages/ui/useScroll/useScroll.js b/packages/ui/useScroll/useScroll.js index 7eebf4e3b1..67cd82be68 100644 --- a/packages/ui/useScroll/useScroll.js +++ b/packages/ui/useScroll/useScroll.js @@ -1459,12 +1459,12 @@ const useScrollBase = (props) => { function addEventListeners () { utilEvent('wheel').addEventListener(scrollContainerRef, onWheel); utilEvent('keydown').addEventListener(scrollContainerRef, onKeyDown); - utilEvent('keyup').addEventListener(scrollContainerRef, onKeyUp); utilEvent('mousedown').addEventListener(scrollContainerRef, onMouseDown); // scrollMode 'native' [[ if (scrollMode === 'native' && scrollContentRef.current) { utilEvent('scroll').addEventListener(scrollContentRef, onScroll, {passive: true}); + utilEvent('keyup').addEventListener(scrollContainerRef, onKeyUp); } // scrollMode 'native' ]] @@ -1485,6 +1485,7 @@ const useScrollBase = (props) => { // scrollMode 'native' [[ utilEvent('scroll').removeEventListener(scrollContentRef, onScroll, {passive: true}); + utilEvent('keyup').addEventListener(scrollContainerRef, onKeyUp); // scrollMode 'native' ]] if (props.removeEventListeners) { From 8e39da97a8e223e11acd0f50d2ddb64cc8a78361 Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Wed, 17 Sep 2025 15:38:39 +0300 Subject: [PATCH 12/30] Added acceleration to scroll --- packages/ui/Scroller/ScrollerBasic.js | 16 +++++++++++----- packages/ui/useScroll/useScroll.js | 12 ++++++++---- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index 9214ab6afb..b5ee369702 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -1,5 +1,6 @@ import EnactPropTypes from '@enact/core/internal/prop-types'; import {platform} from '@enact/core/platform'; +import {perfNow} from '@enact/core/util'; import classNames from 'classnames'; import PropTypes from 'prop-types'; import {Component} from 'react'; @@ -82,10 +83,8 @@ class ScrollerBasic extends Component { scrollAnimation = { id: null, isAnimating: false, - previousPosition: { - top: 0, - left: 0 - }, + distance: 20, + startTime: null }; scrollBounds = { @@ -140,11 +139,18 @@ class ScrollerBasic extends Component { animateScroll (left, top, node) { const animateScroll = () => { this.scrollAnimation.isAnimating = true; - node.scrollBy({top: 20, left: 0, behavior: 'auto'}); + const duration = (perfNow() - this.scrollAnimation.startTime) / 1000; + const multiplier = duration < 1 ? 1 : duration; + + const dy = Math.sign(top - node.scrollTop) * (this.scrollAnimation.distance * multiplier); + const dx = Math.sign(left - node.scrollLeft) * (this.scrollAnimation.distance * multiplier); + + node.scrollBy({top: dy, left: dx, behavior: 'auto'}); this.scrollAnimation.id = window.requestAnimationFrame(animateScroll); }; if (!this.scrollAnimation.isAnimating) { + this.scrollAnimation.startTime = perfNow(); this.scrollAnimation.id = window.requestAnimationFrame(animateScroll); } } diff --git a/packages/ui/useScroll/useScroll.js b/packages/ui/useScroll/useScroll.js index 67cd82be68..a2c017ede1 100644 --- a/packages/ui/useScroll/useScroll.js +++ b/packages/ui/useScroll/useScroll.js @@ -810,14 +810,18 @@ const useScrollBase = (props) => { scrollByPage(ev.keyCode); } } else { - if (ev.repeat) return; props.preventScroll?.(ev); - forward('onKeyDown', ev, props); + if (!ev.repeat) { + forward('onKeyDown', ev, props); + } } } - function onKeyUp () { - scrollContentHandle.current.stopAnimatedScroll(); + function onKeyUp (ev) { + if (scrollContentHandle.current?.stopAnimatedScroll && scrollMode === 'native') { + scrollContentHandle.current?.stopAnimatedScroll(); + } + forward('onKeyUp', ev, props); } function scrollToAccumulatedTarget (delta, vertical, overscrollEffect) { From c0048aa174fbc2eb32ba97a5a58c817fb9a5a10f Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Wed, 17 Sep 2025 16:44:03 +0300 Subject: [PATCH 13/30] Small fix --- packages/ui/Scroller/ScrollerBasic.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index b5ee369702..3b6ad80630 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -137,13 +137,16 @@ class ScrollerBasic extends Component { // scrollMode 'native' animateScroll (left, top, node) { + const deltaX = left - node.scrollLeft; + const deltaY = top - node.scrollTop; + const animateScroll = () => { this.scrollAnimation.isAnimating = true; const duration = (perfNow() - this.scrollAnimation.startTime) / 1000; - const multiplier = duration < 1 ? 1 : duration; + const multiplier = (duration > 1 || duration <= 3) ? duration : 1; - const dy = Math.sign(top - node.scrollTop) * (this.scrollAnimation.distance * multiplier); - const dx = Math.sign(left - node.scrollLeft) * (this.scrollAnimation.distance * multiplier); + const dx = Math.sign(deltaX) * (this.scrollAnimation.distance * multiplier); + const dy = Math.sign(deltaY) * (this.scrollAnimation.distance * multiplier); node.scrollBy({top: dy, left: dx, behavior: 'auto'}); this.scrollAnimation.id = window.requestAnimationFrame(animateScroll); From 7e0a4fc63ff79b6010e35d7aa34628ef06d598af Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Wed, 17 Sep 2025 17:09:05 +0300 Subject: [PATCH 14/30] Small fix --- packages/ui/Scroller/ScrollerBasic.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index 3b6ad80630..0d745e4dcb 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -137,8 +137,8 @@ class ScrollerBasic extends Component { // scrollMode 'native' animateScroll (left, top, node) { - const deltaX = left - node.scrollLeft; - const deltaY = top - node.scrollTop; + const deltaX = this.isHorizontal() ? left - node.scrollLeft : 0; + const deltaY = this.isVertical() ? top - node.scrollTop : 0; const animateScroll = () => { this.scrollAnimation.isAnimating = true; From a4149bc7efb8ce3d37a080da39fb9a3ed70275b3 Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Wed, 17 Sep 2025 18:00:34 +0300 Subject: [PATCH 15/30] Small fix --- packages/ui/Scroller/ScrollerBasic.js | 8 ++++---- packages/ui/useScroll/useScroll.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index 0d745e4dcb..794af657f8 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -125,10 +125,10 @@ class ScrollerBasic extends Component { } // scrollMode 'native' - scrollToPosition (left, top, behavior) { + scrollToPosition (left, top, behavior, type) { const node = this.props.scrollContentRef.current; - if (platform.chrome && behavior === 'smooth') { + if (platform.chrome && behavior === 'smooth' && type !== 'wheel') { this.animateScroll(this.getRtlPositionX(left), top, node); } else { node.scrollTo({left: this.getRtlPositionX(left), top, behavior}); @@ -137,8 +137,8 @@ class ScrollerBasic extends Component { // scrollMode 'native' animateScroll (left, top, node) { - const deltaX = this.isHorizontal() ? left - node.scrollLeft : 0; - const deltaY = this.isVertical() ? top - node.scrollTop : 0; + const deltaX = left - node.scrollLeft; + const deltaY = top - node.scrollTop; const animateScroll = () => { this.scrollAnimation.isAnimating = true; diff --git a/packages/ui/useScroll/useScroll.js b/packages/ui/useScroll/useScroll.js index a2c017ede1..f24b656527 100644 --- a/packages/ui/useScroll/useScroll.js +++ b/packages/ui/useScroll/useScroll.js @@ -1154,7 +1154,7 @@ const useScrollBase = (props) => { } } else { // scrollMode 'native' if (animate) { - scrollContentHandle.current.scrollToPosition(targetX, targetY, 'smooth'); + scrollContentHandle.current.scrollToPosition(targetX, targetY, 'smooth', mutableRef.current.lastInputType); } else { scrollContentHandle.current.scrollToPosition(targetX, targetY, 'instant'); } From f2d68006acfb821931b9f983c8e9b6a77c084a0d Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Tue, 23 Sep 2025 14:18:42 +0300 Subject: [PATCH 16/30] Small fix --- packages/ui/Scroller/ScrollerBasic.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index 794af657f8..ddf6b45cbe 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -127,8 +127,10 @@ class ScrollerBasic extends Component { // scrollMode 'native' scrollToPosition (left, top, behavior, type) { const node = this.props.scrollContentRef.current; + const scrollByKeys = type === 'arrowKey' || type === 'pageKey'; + const smoothBehavior = behavior === 'smooth'; - if (platform.chrome && behavior === 'smooth' && type !== 'wheel') { + if (platform.chrome && smoothBehavior && scrollByKeys) { this.animateScroll(this.getRtlPositionX(left), top, node); } else { node.scrollTo({left: this.getRtlPositionX(left), top, behavior}); @@ -148,7 +150,7 @@ class ScrollerBasic extends Component { const dx = Math.sign(deltaX) * (this.scrollAnimation.distance * multiplier); const dy = Math.sign(deltaY) * (this.scrollAnimation.distance * multiplier); - node.scrollBy({top: dy, left: dx, behavior: 'auto'}); + node.scrollBy({top: dy, left: dx, behavior: 'instant'}); this.scrollAnimation.id = window.requestAnimationFrame(animateScroll); }; From bea75523a3548829cc0c1589b5f73c8041512101 Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Tue, 23 Sep 2025 15:54:13 +0300 Subject: [PATCH 17/30] Small fix --- packages/ui/Scroller/ScrollerBasic.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index ddf6b45cbe..80cbd4248a 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -139,8 +139,8 @@ class ScrollerBasic extends Component { // scrollMode 'native' animateScroll (left, top, node) { - const deltaX = left - node.scrollLeft; - const deltaY = top - node.scrollTop; + const deltaX = left - this.scrollPos.left; + const deltaY = top - this.scrollPos.top; const animateScroll = () => { this.scrollAnimation.isAnimating = true; From ca5213e1d32dfa5452aef31c3b9508a3d20c5503 Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Wed, 24 Sep 2025 13:54:59 +0300 Subject: [PATCH 18/30] Fixed `unit-tests` --- .../ui/Scroller/tests/ScrollerBasic-specs.js | 51 ++++++------------- 1 file changed, 16 insertions(+), 35 deletions(-) diff --git a/packages/ui/Scroller/tests/ScrollerBasic-specs.js b/packages/ui/Scroller/tests/ScrollerBasic-specs.js index c61559d80a..c889cdc2de 100644 --- a/packages/ui/Scroller/tests/ScrollerBasic-specs.js +++ b/packages/ui/Scroller/tests/ScrollerBasic-specs.js @@ -6,69 +6,50 @@ describe('ScrollBasic', () => { let scrollContentRef; beforeEach(() => { + jest.createMockFromModule('@enact/core/platform'); + scrollContentRef = { current: { scrollLeft: 0, scrollTop: 0, + scrollBy: jest.fn(), scrollTo: jest.fn() } }; }); + afterEach(() => { + jest.clearAllMocks(); + }); + test( - 'should call scrollTo on scrollToPosition', + 'should call scrollBy on scrollToPosition', () => { const instance = new ScrollerBasic({scrollContentRef, direction: 'both'}); - instance.scrollToPosition(30, 40, 'smooth'); - expect(scrollContentRef.current.scrollTo).toHaveBeenCalledWith({left: 30, top: 40, behavior: 'smooth'}); + instance.scrollToPosition(100, 200, 'smooth', 'arrowKey'); + expect(scrollContentRef.current.scrollTo).toHaveBeenCalledWith({left: 100, top: 200, behavior: 'smooth'}); - instance.scrollToPosition(30, 40, 'instant'); - expect(scrollContentRef.current.scrollTo).toHaveBeenCalledWith({left: 30, top: 40, behavior: 'instant'}); + instance.scrollToPosition(100, 200, 'instant'); + expect(scrollContentRef.current.scrollTo).toHaveBeenCalledWith({left: 100, top: 200, behavior: 'instant'}); } ); test( - 'should call scrollTo with animated values during animateScroll', + 'should call scrollBy with animated values during animateScroll', () => { - let now = 0; let rafCallback; const instance = new ScrollerBasic({scrollContentRef, direction: 'both'}); - jest.useFakeTimers(); - jest.spyOn(require('@enact/core/util'), 'perfNow').mockImplementation(() => now); - window.requestAnimationFrame = jest.fn((cb) => { - rafCallback = cb; - return 1; - }); + window.requestAnimationFrame = jest.fn((cb) => {rafCallback = cb}); window.cancelAnimationFrame = jest.fn(); instance.animateScroll(100, 200, scrollContentRef.current); - - now = 500; rafCallback(); - expect(scrollContentRef.current.scrollTo).toHaveBeenCalled(); + expect(scrollContentRef.current.scrollBy).toHaveBeenCalled(); - now = 1001; - rafCallback(); + instance.stopAnimatedScroll(); expect(window.cancelAnimationFrame).toHaveBeenCalled(); - - jest.useRealTimers(); - } - ); - - test( - 'should cancel previous animation frame if one exists', - () => { - const instance = new ScrollerBasic({scrollContentRef, direction: 'both'}); - instance.scrollAnimationId = 1; - window.cancelAnimationFrame = jest.fn(); - window.requestAnimationFrame = jest.fn(() => 2); - - instance.animateScroll(100, 200, scrollContentRef.current); - - expect(window.cancelAnimationFrame).toHaveBeenCalledWith(1); - expect(instance.scrollAnimationId).toBe(2); } ); }); From 933c0198a9ad6e0b9e547bde364d713ab0baa6ac Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Wed, 24 Sep 2025 13:57:12 +0300 Subject: [PATCH 19/30] Fixed lint warnings --- packages/ui/Scroller/tests/ScrollerBasic-specs.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/ui/Scroller/tests/ScrollerBasic-specs.js b/packages/ui/Scroller/tests/ScrollerBasic-specs.js index c889cdc2de..5bd4f89474 100644 --- a/packages/ui/Scroller/tests/ScrollerBasic-specs.js +++ b/packages/ui/Scroller/tests/ScrollerBasic-specs.js @@ -41,7 +41,9 @@ describe('ScrollBasic', () => { let rafCallback; const instance = new ScrollerBasic({scrollContentRef, direction: 'both'}); - window.requestAnimationFrame = jest.fn((cb) => {rafCallback = cb}); + window.requestAnimationFrame = jest.fn((cb) => { + rafCallback = cb; + }); window.cancelAnimationFrame = jest.fn(); instance.animateScroll(100, 200, scrollContentRef.current); From ef0dbeef8e508f94453ecb73e564c95fa3f2beef Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Wed, 24 Sep 2025 17:55:59 +0300 Subject: [PATCH 20/30] Added `CHANGELOG` --- CHANGELOG.md | 6 ++++++ packages/ui/CHANGELOG.md | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3600c9ace3..886ad213c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ The following is a curated list of changes in the Enact project, newest changes on the top. +## [unreleased] + +### Changed + +- `ui/Scroller` scroll animation method for `scrollMode: native` + ## [5.3.0] - 2025-09-24 ### Added diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index 828da16521..4c38fcb89d 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -2,6 +2,12 @@ The following is a curated list of changes in the Enact ui module, newest changes on the top. +## [unreleased] + +### Changed + +- `ui/Scroller` scroll animation method for `scrollMode: native` + ## [5.3.0] - 2025-09-24 ### Added From 50fa46f982832d385e7d404c5d88a1fe2d11cc53 Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Thu, 25 Sep 2025 19:17:56 +0300 Subject: [PATCH 21/30] Review fixes --- packages/ui/Scroller/ScrollerBasic.js | 14 +++++++++++++- packages/ui/useScroll/useScroll.js | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index 80cbd4248a..a6de49c740 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -82,6 +82,8 @@ class ScrollerBasic extends Component { scrollAnimation = { id: null, + timeoutId: null, + timeoutDelay: 600, isAnimating: false, distance: 20, startTime: null @@ -132,6 +134,7 @@ class ScrollerBasic extends Component { if (platform.chrome && smoothBehavior && scrollByKeys) { this.animateScroll(this.getRtlPositionX(left), top, node); + this.stopAnimatedScrollLoop(this.scrollAnimation.timeoutDelay); } else { node.scrollTo({left: this.getRtlPositionX(left), top, behavior}); } @@ -143,7 +146,6 @@ class ScrollerBasic extends Component { const deltaY = top - this.scrollPos.top; const animateScroll = () => { - this.scrollAnimation.isAnimating = true; const duration = (perfNow() - this.scrollAnimation.startTime) / 1000; const multiplier = (duration > 1 || duration <= 3) ? duration : 1; @@ -155,8 +157,11 @@ class ScrollerBasic extends Component { }; if (!this.scrollAnimation.isAnimating) { + this.scrollAnimation.isAnimating = true; this.scrollAnimation.startTime = perfNow(); this.scrollAnimation.id = window.requestAnimationFrame(animateScroll); + } else { + this.stopAnimatedScrollLoop(this.scrollAnimation.timeoutDelay / 2); } } @@ -165,6 +170,13 @@ class ScrollerBasic extends Component { window.cancelAnimationFrame(this.scrollAnimation.id); } + stopAnimatedScrollLoop (timeout) { + clearTimeout(this.scrollAnimation.timeoutId); + this.scrollAnimation.timeoutId = setTimeout(() => { + this.stopAnimatedScroll(); + }, timeout); + } + // scrollMode 'native' didScroll (x, y) { this.scrollPos.left = x; diff --git a/packages/ui/useScroll/useScroll.js b/packages/ui/useScroll/useScroll.js index f24b656527..705d390abb 100644 --- a/packages/ui/useScroll/useScroll.js +++ b/packages/ui/useScroll/useScroll.js @@ -1489,7 +1489,7 @@ const useScrollBase = (props) => { // scrollMode 'native' [[ utilEvent('scroll').removeEventListener(scrollContentRef, onScroll, {passive: true}); - utilEvent('keyup').addEventListener(scrollContainerRef, onKeyUp); + utilEvent('keyup').removeEventListener(scrollContainerRef, onKeyUp); // scrollMode 'native' ]] if (props.removeEventListeners) { From 6cd37e74d89379f7d724f6da78ba783b933bd0a4 Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Wed, 15 Oct 2025 16:50:40 +0300 Subject: [PATCH 22/30] Adjusted animation function --- CHANGELOG.md | 6 + lerna.json | 2 +- package-lock.json | 4 +- package.json | 2 +- packages/core/package.json | 2 +- packages/i18n/package.json | 4 +- packages/sampler/npm-shrinkwrap.json | 296 +++++++++--------- packages/sampler/package.json | 12 +- packages/spotlight/CHANGELOG.md | 6 + packages/spotlight/package.json | 4 +- packages/spotlight/src/target.js | 2 +- packages/ui/CHANGELOG.md | 5 + packages/ui/Scroller/ScrollerBasic.js | 52 +-- packages/ui/package.json | 6 +- .../ui/useScroll/tests/useScroll-specs.js | 72 +++++ packages/ui/useScroll/useScroll.js | 30 +- packages/webos/package.json | 4 +- 17 files changed, 291 insertions(+), 218 deletions(-) create mode 100644 packages/ui/useScroll/tests/useScroll-specs.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 886ad213c1..a0baec1542 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,12 @@ The following is a curated list of changes in the Enact project, newest changes ### Changed - `ui/Scroller` scroll animation method for `scrollMode: native` +## [5.3.1] - 2025-10-14 + +### Fixed + +- `ui/Scroller` to calculate correctly targetX and targetY when scrolling to a position +- `spotlight` navigation from an element to a different container ## [5.3.0] - 2025-09-24 diff --git a/lerna.json b/lerna.json index f20ae9c0b9..79cdcd9e89 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "5.3.0", + "version": "5.3.1", "packages": [ "packages/*" ], diff --git a/package-lock.json b/package-lock.json index 3d24d53c26..1c8dce314d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "enact", - "version": "5.2.1", + "version": "5.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "enact", - "version": "5.2.1", + "version": "5.3.0", "license": "Apache-2.0", "devDependencies": { "@enact/docs-utils": "^0.4.14", diff --git a/package.json b/package.json index f6eaeafb76..f491b0fa18 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "enact", - "version": "5.3.0", + "version": "5.3.1", "description": "Monorepo for all Enact front end libraries.", "private": true, "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index 8221662513..0cf24254d9 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@enact/core", - "version": "5.3.0", + "version": "5.3.1", "description": "Enact is an open source JavaScript framework containing everything you need to create a fast, scalable mobile or web application.", "repository": { "type": "git", diff --git a/packages/i18n/package.json b/packages/i18n/package.json index 17d1d1a21f..07af023084 100644 --- a/packages/i18n/package.json +++ b/packages/i18n/package.json @@ -1,7 +1,7 @@ { "name": "@enact/i18n", "main": "./src/index.js", - "version": "5.3.0", + "version": "5.3.1", "description": "Internationalization support for Enact using iLib", "repository": { "type": "git", @@ -48,7 +48,7 @@ ] }, "dependencies": { - "@enact/core": "^5.3.0", + "@enact/core": "^5.3.1", "prop-types": "^15.8.1", "ramda": "^0.31.3", "react": "^19.1.1", diff --git a/packages/sampler/npm-shrinkwrap.json b/packages/sampler/npm-shrinkwrap.json index c532596e68..ad0f35fa7f 100644 --- a/packages/sampler/npm-shrinkwrap.json +++ b/packages/sampler/npm-shrinkwrap.json @@ -1,20 +1,20 @@ { "name": "enact-sampler", - "version": "5.3.0", + "version": "5.3.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "enact-sampler", - "version": "5.3.0", + "version": "5.3.1", "license": "Apache-2.0", "dependencies": { - "@enact/core": "^5.3.0", - "@enact/i18n": "^5.3.0", - "@enact/spotlight": "^5.3.0", + "@enact/core": "^5.3.1", + "@enact/i18n": "^5.3.1", + "@enact/spotlight": "^5.3.1", "@enact/storybook-utils": "^7.0.1", - "@enact/ui": "^5.3.0", - "@enact/webos": "^5.3.0", + "@enact/ui": "^5.3.1", + "@enact/webos": "^5.3.1", "@storybook/addon-docs": "^9.1.6", "@storybook/react-webpack5": "^9.1.6", "classnames": "^2.5.1", @@ -284,9 +284,9 @@ } }, "node_modules/@enact/core": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@enact/core/-/core-5.3.0.tgz", - "integrity": "sha512-Epmngit6WTUv9aFO32IroebE4d7zkbOF8gfmYRAu69s5iBXJfx3TunYnQKNuYRc8E9UrLPEPbnPEuR3wuQBwpw==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@enact/core/-/core-5.3.1.tgz", + "integrity": "sha512-/aJ7p2wQKVH7kv22SA1nZWsRYbhqMGd3Rc99r2F1ahKVuIx956DSJIRBNlZnW6G2pqSNYWR7JpLIU+gy5yaUzQ==", "license": "Apache-2.0", "dependencies": { "classnames": "^2.5.1", @@ -300,12 +300,12 @@ } }, "node_modules/@enact/i18n": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@enact/i18n/-/i18n-5.3.0.tgz", - "integrity": "sha512-PQNT0U92v5IeOkexd6Tet1GtauQprDGN8PZnvbWKEZpR/DKZZUHqO7yf9+2/Ux+J4Oi32bOcx3Afwbz4J73AYg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@enact/i18n/-/i18n-5.3.1.tgz", + "integrity": "sha512-ICGx3y20D/0jjb3K2IzU56Fv0bm39jKrUo7YZa1Cq81oqBZDjXUwnrY33gAXcLV1JFuSgOJv5cQzreGmgP2w7Q==", "license": "Apache-2.0", "dependencies": { - "@enact/core": "^5.3.0", + "@enact/core": "^5.3.1", "prop-types": "^15.8.1", "ramda": "^0.31.3", "react": "^19.1.1", @@ -317,12 +317,12 @@ } }, "node_modules/@enact/spotlight": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@enact/spotlight/-/spotlight-5.3.0.tgz", - "integrity": "sha512-BOQptI8aQ2FEd0zWBbPb3aXm+QSrtDSWd7rikomADEljLj4T0ukaJcGAGJgk5rJgEte7dyNpXmYyiiuvS+pbTQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@enact/spotlight/-/spotlight-5.3.1.tgz", + "integrity": "sha512-EHzD260Vm0o0LgyhaEMtzE9OK4HFQxB56PC3PnPmP+HAYKIrF97uEkBxZ58BbATEH+7Tsg++Ls97VynFsx1Y/A==", "license": "Apache-2.0", "dependencies": { - "@enact/core": "^5.3.0", + "@enact/core": "^5.3.1", "classnames": "^2.5.1", "prop-types": "^15.8.1", "ramda": "^0.31.3", @@ -32504,13 +32504,13 @@ } }, "node_modules/@enact/ui": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@enact/ui/-/ui-5.3.0.tgz", - "integrity": "sha512-bKyYa3kcnvHuqNTCSVv9kG1uB6PGx951ur+mF0r4UJEvi8BkQRzXlC8SubjR+sNoxhB3BjnJ17NnqoSbL4Mzhw==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@enact/ui/-/ui-5.3.1.tgz", + "integrity": "sha512-U9DRiyEiGfnFrAt4Y3BDtUJN7QkKrxnKv1R+/SICCH6099borHYCIXW+eKfVQAV5VMB6ge4yCHnIYRbNTJX1/g==", "license": "Apache-2.0", "dependencies": { - "@enact/core": "^5.3.0", - "@enact/i18n": "^5.3.0", + "@enact/core": "^5.3.1", + "@enact/i18n": "^5.3.1", "classnames": "^2.5.1", "ilib": "^14.21.1", "invariant": "^2.2.4", @@ -32523,12 +32523,12 @@ } }, "node_modules/@enact/webos": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@enact/webos/-/webos-5.3.0.tgz", - "integrity": "sha512-GzcmG2DnWXbNwtNqQrpFst7EOvNa3TY7gght7neHClQ+pKUmY14cEFI40L20Mli8D9Jf/U82TmFDAsHZVGOKxQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@enact/webos/-/webos-5.3.1.tgz", + "integrity": "sha512-CwzhhsEEzDB4fDQ/CqKAUc6cBJLCBXZMQge3XVPzEYJsjKm249rEO03UUFsMVBz2dAGjAsNqPTFL2JYyLP5DcQ==", "license": "Apache-2.0", "dependencies": { - "@enact/core": "^5.3.0", + "@enact/core": "^5.3.1", "prop-types": "^15.8.1", "react": "^19.1.1", "react-dom": "^19.1.1" @@ -33023,15 +33023,15 @@ } }, "node_modules/@storybook/addon-docs": { - "version": "9.1.8", - "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-9.1.8.tgz", - "integrity": "sha512-GVrNVEdNRRo6r1hawfgyy6x+HJqPx1oOHm0U0wz0SGAxgS/Xh6SQVZL+RDoh7NpXkNi1GbezVlT931UsHQTyvQ==", + "version": "9.1.10", + "resolved": "https://registry.npmjs.org/@storybook/addon-docs/-/addon-docs-9.1.10.tgz", + "integrity": "sha512-LYK3oXy/0PgY39FhkYVd9D0bzatLGTsMhaPPwSjBOmZgK0f0yBLqaePy7urUFeHYm/rjwAaRmDJNBqUnGTVoig==", "license": "MIT", "dependencies": { "@mdx-js/react": "^3.0.0", - "@storybook/csf-plugin": "9.1.8", + "@storybook/csf-plugin": "9.1.10", "@storybook/icons": "^1.4.0", - "@storybook/react-dom-shim": "9.1.8", + "@storybook/react-dom-shim": "9.1.10", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "ts-dedent": "^2.0.0" @@ -33041,16 +33041,16 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^9.1.8" + "storybook": "^9.1.10" } }, "node_modules/@storybook/builder-webpack5": { - "version": "9.1.8", - "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-9.1.8.tgz", - "integrity": "sha512-xnJNmqmwgoIeJ4gIDqIBI68qTyzJUYyThKpD1DJx6l+0caBxebEPoYxCzC9vKI0Thb6VzjCpmJXxDUJJpx8zsw==", + "version": "9.1.10", + "resolved": "https://registry.npmjs.org/@storybook/builder-webpack5/-/builder-webpack5-9.1.10.tgz", + "integrity": "sha512-6Cw0lJ0SxGPGhBPx5zOozxEtu7Nm3rOMCuYGQy3OYUbAmznk82NB6pnQ4cdCIBmX61ZyGykhXShLjtCWHh/8tw==", "license": "MIT", "dependencies": { - "@storybook/core-webpack": "9.1.8", + "@storybook/core-webpack": "9.1.10", "case-sensitive-paths-webpack-plugin": "^2.4.0", "cjs-module-lexer": "^1.2.3", "css-loader": "^6.7.1", @@ -33071,7 +33071,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^9.1.8" + "storybook": "^9.1.10" }, "peerDependenciesMeta": { "typescript": { @@ -33080,9 +33080,9 @@ } }, "node_modules/@storybook/core-webpack": { - "version": "9.1.8", - "resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-9.1.8.tgz", - "integrity": "sha512-osNCuxPFtU1WiE/VTD93njxplxg7P/94p2Kc0NIwF2cdUQ64xlx68DWB5xJDaoZiVtcrY3/kjk/VWnf7X3Z6zg==", + "version": "9.1.10", + "resolved": "https://registry.npmjs.org/@storybook/core-webpack/-/core-webpack-9.1.10.tgz", + "integrity": "sha512-zborBOQtZBkGl0NcOgNygRdiPFqseh9UnLWVV1yLZglCZN5BTnXjEGhpRFHBOhPux3gdHNcvrvgYSgcitFpEPA==", "license": "MIT", "dependencies": { "ts-dedent": "^2.0.0" @@ -33092,13 +33092,13 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^9.1.8" + "storybook": "^9.1.10" } }, "node_modules/@storybook/csf-plugin": { - "version": "9.1.8", - "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-9.1.8.tgz", - "integrity": "sha512-KnrXPz87bn+8ZGkzFEBc7TT5HkWpR1Xz7ojxPclSvkKxTfzazuaw0JlOQMzJoI1+wHXDAIw/4MIsO8HEiaWyfQ==", + "version": "9.1.10", + "resolved": "https://registry.npmjs.org/@storybook/csf-plugin/-/csf-plugin-9.1.10.tgz", + "integrity": "sha512-247F/JU0Naxm/RIUnQYpqXeCL0wG8UNJkZe+/GkLjdqzsyML0lb+8OwBsWFfG8zfj6fkjmRU2mF44TnNkzoQcg==", "license": "MIT", "dependencies": { "unplugin": "^1.3.1" @@ -33108,7 +33108,7 @@ "url": "https://opencollective.com/storybook" }, "peerDependencies": { - "storybook": "^9.1.8" + "storybook": "^9.1.10" } }, "node_modules/@storybook/global": { @@ -33131,12 +33131,12 @@ } }, "node_modules/@storybook/preset-react-webpack": { - "version": "9.1.8", - "resolved": "https://registry.npmjs.org/@storybook/preset-react-webpack/-/preset-react-webpack-9.1.8.tgz", - "integrity": "sha512-iHAwK+frDROMTjLVIRSW+bfuXwiy1/BXlsCuLSbcGm/03EV748wQ1uyES1Wy8yKgZKQXqoW4w1C1i21I/JvLUQ==", + "version": "9.1.10", + "resolved": "https://registry.npmjs.org/@storybook/preset-react-webpack/-/preset-react-webpack-9.1.10.tgz", + "integrity": "sha512-2fiFqaWP8yl5WW9nDi++chmcy3A69DRU8UM1w7JtByw147GC7PCY+mt6L8k8TMZtAcqvLgZEx9j+llsbv0AemQ==", "license": "MIT", "dependencies": { - "@storybook/core-webpack": "9.1.8", + "@storybook/core-webpack": "9.1.10", "@storybook/react-docgen-typescript-plugin": "1.0.6--canary.9.0c3f3b7.0", "@types/semver": "^7.3.4", "find-up": "^7.0.0", @@ -33157,7 +33157,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^9.1.8" + "storybook": "^9.1.10" }, "peerDependenciesMeta": { "typescript": { @@ -33166,13 +33166,13 @@ } }, "node_modules/@storybook/react": { - "version": "9.1.8", - "resolved": "https://registry.npmjs.org/@storybook/react/-/react-9.1.8.tgz", - "integrity": "sha512-EULkwHroJ4IDYcjIBj9VpGhaZ9E5b8LI84hlfBkJ9rnK44a/GrK1yFRIusukO58qTJSh2Y7zfAFKNuiaWh3Sfw==", + "version": "9.1.10", + "resolved": "https://registry.npmjs.org/@storybook/react/-/react-9.1.10.tgz", + "integrity": "sha512-flG3Gn3EHZnxn92C7vrA2U4aGqpOKdf85fL43+J/2k9HF5AIyOFGlcv4LGVyKZ3LOAow/nGBVSXL9961h+ICRA==", "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", - "@storybook/react-dom-shim": "9.1.8" + "@storybook/react-dom-shim": "9.1.10" }, "engines": { "node": ">=20.0.0" @@ -33184,7 +33184,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^9.1.8", + "storybook": "^9.1.10", "typescript": ">= 4.9.x" }, "peerDependenciesMeta": { @@ -33213,9 +33213,9 @@ } }, "node_modules/@storybook/react-dom-shim": { - "version": "9.1.8", - "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-9.1.8.tgz", - "integrity": "sha512-OepccjVZh/KQugTH8/RL2CIyf1g5Lwc5ESC8x8BH3iuYc82WMQBwMJzRI5EofQdirau63NGrqkWCgQASoVreEA==", + "version": "9.1.10", + "resolved": "https://registry.npmjs.org/@storybook/react-dom-shim/-/react-dom-shim-9.1.10.tgz", + "integrity": "sha512-cxy8GTj73RMJIFPrgqdnMXePGX5iFohM5pDCZ63Te5m5GtzKqsILRXtBBLO6Ouexm/ZYRVznkKiwNKX/Fu24fQ==", "license": "MIT", "funding": { "type": "opencollective", @@ -33224,18 +33224,18 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^9.1.8" + "storybook": "^9.1.10" } }, "node_modules/@storybook/react-webpack5": { - "version": "9.1.8", - "resolved": "https://registry.npmjs.org/@storybook/react-webpack5/-/react-webpack5-9.1.8.tgz", - "integrity": "sha512-+ZznhhMKHNnwBdQyd1bFe1y7/bBX/cUCPQ7W+3DMj/p64QIpvNbmIf+K7Qp6lUOaIC5hUINZcGeCpEbFOKH18A==", + "version": "9.1.10", + "resolved": "https://registry.npmjs.org/@storybook/react-webpack5/-/react-webpack5-9.1.10.tgz", + "integrity": "sha512-jJ5B704R2iIpF1gxlGaaV4I3feAFV2Bwva+/KaGEIHqG7yfvlX+0u4Mki2/xFxhLdHlIzx5FEZjfh41QlBsqxQ==", "license": "MIT", "dependencies": { - "@storybook/builder-webpack5": "9.1.8", - "@storybook/preset-react-webpack": "9.1.8", - "@storybook/react": "9.1.8" + "@storybook/builder-webpack5": "9.1.10", + "@storybook/preset-react-webpack": "9.1.10", + "@storybook/react": "9.1.10" }, "engines": { "node": ">=20.0.0" @@ -33247,7 +33247,7 @@ "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta", - "storybook": "^9.1.8", + "storybook": "^9.1.10", "typescript": ">= 4.9.x" }, "peerDependenciesMeta": { @@ -33277,9 +33277,9 @@ } }, "node_modules/@testing-library/jest-dom": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.8.0.tgz", - "integrity": "sha512-WgXcWzVM6idy5JaftTVC8Vs83NKRmGJz4Hqs4oyOuO2J4r/y79vvKZsb+CaGyCSEbUPI6OsewfPd0G1A0/TUZQ==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", "license": "MIT", "dependencies": { "@adobe/css-tools": "^4.4.0", @@ -33428,12 +33428,12 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.5.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz", - "integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==", + "version": "24.7.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.2.tgz", + "integrity": "sha512-/NbVmcGTP+lj5oa4yiYxxeBjRivKQ5Ns1eSZeB99ExsEQ6rX5XYU1Zy/gGxY/ilqtD4Etx9mKyrPxZRetiahhA==", "license": "MIT", "dependencies": { - "undici-types": "~7.12.0" + "undici-types": "~7.14.0" } }, "node_modules/@types/parse-json": { @@ -33443,9 +33443,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.1.13", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.13.tgz", - "integrity": "sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==", + "version": "19.2.2", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", + "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "license": "MIT", "peer": true, "dependencies": { @@ -33850,9 +33850,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.6.tgz", - "integrity": "sha512-wrH5NNqren/QMtKUEEJf7z86YjfqW/2uw3IL3/xpqZUC95SSVIFXYQeeGjL6FT/X68IROu6RMehZQS5foy2BXw==", + "version": "2.8.16", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.16.tgz", + "integrity": "sha512-OMu3BGQ4E7P1ErFsIPpbJh0qvDudM/UuJeHgkAvfWe+0HFJCXh+t/l8L6fVLR55RI/UbKrVLnAXZSVwd9ysWYw==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -33911,9 +33911,9 @@ } }, "node_modules/browserslist": { - "version": "4.26.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.2.tgz", - "integrity": "sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==", + "version": "4.26.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", + "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", "funding": [ { "type": "opencollective", @@ -33930,9 +33930,9 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.3", - "caniuse-lite": "^1.0.30001741", - "electron-to-chromium": "^1.5.218", + "baseline-browser-mapping": "^2.8.9", + "caniuse-lite": "^1.0.30001746", + "electron-to-chromium": "^1.5.227", "node-releases": "^2.0.21", "update-browserslist-db": "^1.1.3" }, @@ -33969,9 +33969,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001743", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001743.tgz", - "integrity": "sha512-e6Ojr7RV14Un7dz6ASD0aZDmQPT/A+eZU+nuTNfjqmRrmkmQlnTNWH0SKmqagx9PeW87UVqapSurtAXifmtdmw==", + "version": "1.0.30001750", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001750.tgz", + "integrity": "sha512-cuom0g5sdX6rw00qOoLNSFCJ9/mYIsuSOA+yzpDw8eopiFqcVwQvZHqov0vmEighRxX++cfC0Vg1G+1Iy/mSpQ==", "funding": [ { "type": "opencollective", @@ -34408,9 +34408,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.223", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.223.tgz", - "integrity": "sha512-qKm55ic6nbEmagFlTFczML33rF90aU+WtrJ9MdTCThrcvDNdUHN4p6QfVN78U06ZmguqXIyMPyYhw2TrbDUwPQ==", + "version": "1.5.235", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.235.tgz", + "integrity": "sha512-i/7ntLFwOdoHY7sgjlTIDo4Sl8EdoTjWIaKinYOVfC6bOp71bmwenyZthWHcasxgHDNWbWxvG9M3Ia116zIaYQ==", "license": "ISC" }, "node_modules/endent": { @@ -35235,12 +35235,16 @@ "license": "MIT" }, "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "license": "MIT", "engines": { "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/locate-path": { @@ -35480,9 +35484,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.21", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.21.tgz", - "integrity": "sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==", + "version": "2.0.23", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz", + "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==", "license": "MIT" }, "node_modules/normalize-path": { @@ -35990,9 +35994,9 @@ } }, "node_modules/react": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", - "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", + "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -36029,21 +36033,21 @@ } }, "node_modules/react-dom": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", - "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", + "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", "dependencies": { - "scheduler": "^0.26.0" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.1.1" + "react": "^19.2.0" } }, "node_modules/react-is": { - "version": "19.1.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz", - "integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==", + "version": "19.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.0.tgz", + "integrity": "sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==", "license": "MIT" }, "node_modules/readdirp": { @@ -36196,9 +36200,9 @@ "license": "MIT" }, "node_modules/scheduler": { - "version": "0.26.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", - "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, "node_modules/schema-utils": { @@ -36220,9 +36224,9 @@ } }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -36269,9 +36273,9 @@ } }, "node_modules/storybook": { - "version": "9.1.8", - "resolved": "https://registry.npmjs.org/storybook/-/storybook-9.1.8.tgz", - "integrity": "sha512-/iP+DvieJ6Mnixy4PFY/KXnhsg/IHIDlTbZqly3EDbveuhsCuIUELfGnj+QSRGf9C6v/f4sZf9sZ3r80ZnKuEA==", + "version": "9.1.10", + "resolved": "https://registry.npmjs.org/storybook/-/storybook-9.1.10.tgz", + "integrity": "sha512-4+U7gF9hMpGilQmdVJwQaVZZEkD7XwC4ZDmBa51mobaPYelELEMoMfNM2hLyvB2x12gk1IJui1DnwOE4t+MXhw==", "license": "MIT", "dependencies": { "@storybook/global": "^5.0.0", @@ -36351,9 +36355,9 @@ } }, "node_modules/strip-indent": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.1.0.tgz", - "integrity": "sha512-OA95x+JPmL7kc7zCu+e+TeYxEiaIyndRx0OrBcK2QPPH09oAndr2ALvymxWA+Lx1PYYvFUm4O63pRkdJAaW96w==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-4.1.1.tgz", + "integrity": "sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==", "license": "MIT", "engines": { "node": ">=12" @@ -36403,9 +36407,9 @@ } }, "node_modules/tapable": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.3.tgz", - "integrity": "sha512-ZL6DDuAlRlLGghwcfmSn9sK3Hr6ArtyudlSAiCqQ6IfE+b+HHbydbYDIG15IfS5do+7XQQBdBiubF/cV2dnDzg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", "license": "MIT", "engines": { "node": ">=6" @@ -36502,9 +36506,9 @@ "license": "MIT" }, "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", @@ -36592,9 +36596,9 @@ "license": "0BSD" }, "node_modules/typescript": { - "version": "5.9.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", "peer": true, "bin": { @@ -36606,9 +36610,9 @@ } }, "node_modules/undici-types": { - "version": "7.12.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz", - "integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", + "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", "license": "MIT" }, "node_modules/unicorn-magic": { @@ -36719,9 +36723,9 @@ } }, "node_modules/webpack": { - "version": "5.101.3", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.101.3.tgz", - "integrity": "sha512-7b0dTKR3Ed//AD/6kkx/o7duS8H3f1a4w3BYpIriX4BzIhjkn4teo05cptsxvLesHFKK5KObnadmCHBwGc+51A==", + "version": "5.102.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.102.1.tgz", + "integrity": "sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==", "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", @@ -36732,7 +36736,7 @@ "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.15.0", "acorn-import-phases": "^1.0.3", - "browserslist": "^4.24.0", + "browserslist": "^4.26.3", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.17.3", "es-module-lexer": "^1.2.1", @@ -36744,10 +36748,10 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.1.1", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", + "watchpack": "^2.4.4", "webpack-sources": "^3.3.3" }, "bin": { @@ -36829,9 +36833,9 @@ "license": "MIT" }, "node_modules/webpack-dev-middleware/node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", @@ -36908,9 +36912,9 @@ "license": "MIT" }, "node_modules/webpack/node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.9", diff --git a/packages/sampler/package.json b/packages/sampler/package.json index 1e8beb6d49..2a51b05b42 100644 --- a/packages/sampler/package.json +++ b/packages/sampler/package.json @@ -1,6 +1,6 @@ { "name": "enact-sampler", - "version": "5.3.0", + "version": "5.3.1", "description": "Component and QA samples for Enact", "private": true, "main": "index.js", @@ -13,12 +13,12 @@ }, "license": "Apache-2.0", "dependencies": { - "@enact/core": "^5.3.0", - "@enact/i18n": "^5.3.0", - "@enact/spotlight": "^5.3.0", + "@enact/core": "^5.3.1", + "@enact/i18n": "^5.3.1", + "@enact/spotlight": "^5.3.1", "@enact/storybook-utils": "^7.0.1", - "@enact/ui": "^5.3.0", - "@enact/webos": "^5.3.0", + "@enact/ui": "^5.3.1", + "@enact/webos": "^5.3.1", "@storybook/addon-docs": "^9.1.6", "@storybook/react-webpack5": "^9.1.6", "classnames": "^2.5.1", diff --git a/packages/spotlight/CHANGELOG.md b/packages/spotlight/CHANGELOG.md index 19e19e665b..212243ad9d 100644 --- a/packages/spotlight/CHANGELOG.md +++ b/packages/spotlight/CHANGELOG.md @@ -2,6 +2,12 @@ The following is a curated list of changes in the Enact spotlight module, newest changes on the top. +## [5.3.1] - 2025-10-14 + +### Fixed + +- `spotlight` navigation from an element to a different container + ## [5.3.0] - 2025-09-24 ### Fixed diff --git a/packages/spotlight/package.json b/packages/spotlight/package.json index cd4a41506f..13734c66eb 100644 --- a/packages/spotlight/package.json +++ b/packages/spotlight/package.json @@ -1,6 +1,6 @@ { "name": "@enact/spotlight", - "version": "5.3.0", + "version": "5.3.1", "description": "A focus management library", "repository": { "type": "git", @@ -25,7 +25,7 @@ } }, "dependencies": { - "@enact/core": "^5.3.0", + "@enact/core": "^5.3.1", "classnames": "^2.5.1", "prop-types": "^15.8.1", "ramda": "^0.31.3", diff --git a/packages/spotlight/src/target.js b/packages/spotlight/src/target.js index 3b961fa91a..6a5412b3ed 100644 --- a/packages/spotlight/src/target.js +++ b/packages/spotlight/src/target.js @@ -352,7 +352,7 @@ function getTargetInContainerByDirectionFromElement (direction, containerId, ele // and it is restricted, return its target if (isRestrictedContainer(nextContainerId)) { - next = getTargetByContainer(nextContainerId); + return getTargetByContainer(nextContainerId); } else { // otherwise, recurse into it next = getTargetInContainerByDirectionFromElement( diff --git a/packages/ui/CHANGELOG.md b/packages/ui/CHANGELOG.md index 4c38fcb89d..2c2791b901 100644 --- a/packages/ui/CHANGELOG.md +++ b/packages/ui/CHANGELOG.md @@ -7,6 +7,11 @@ The following is a curated list of changes in the Enact ui module, newest change ### Changed - `ui/Scroller` scroll animation method for `scrollMode: native` +## [5.3.1] - 2025-10-14 + +### Fixed + +- `ui/Scroller` to calculate correctly targetX and targetY when scrolling to a position ## [5.3.0] - 2025-09-24 diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index a6de49c740..5611bf236b 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -1,6 +1,5 @@ import EnactPropTypes from '@enact/core/internal/prop-types'; import {platform} from '@enact/core/platform'; -import {perfNow} from '@enact/core/util'; import classNames from 'classnames'; import PropTypes from 'prop-types'; import {Component} from 'react'; @@ -80,14 +79,7 @@ class ScrollerBasic extends Component { } } - scrollAnimation = { - id: null, - timeoutId: null, - timeoutDelay: 600, - isAnimating: false, - distance: 20, - startTime: null - }; + scrollAnimationId = null; scrollBounds = { clientWidth: 0, @@ -127,14 +119,12 @@ class ScrollerBasic extends Component { } // scrollMode 'native' - scrollToPosition (left, top, behavior, type) { + scrollToPosition (left, top, behavior) { const node = this.props.scrollContentRef.current; - const scrollByKeys = type === 'arrowKey' || type === 'pageKey'; const smoothBehavior = behavior === 'smooth'; - if (platform.chrome && smoothBehavior && scrollByKeys) { + if (platform.chrome && smoothBehavior) { this.animateScroll(this.getRtlPositionX(left), top, node); - this.stopAnimatedScrollLoop(this.scrollAnimation.timeoutDelay); } else { node.scrollTo({left: this.getRtlPositionX(left), top, behavior}); } @@ -142,39 +132,21 @@ class ScrollerBasic extends Component { // scrollMode 'native' animateScroll (left, top, node) { - const deltaX = left - this.scrollPos.left; - const deltaY = top - this.scrollPos.top; + const directionX = Math.sign(left - node.scrollLeft); + const directionY = Math.sign(top - node.scrollTop); const animateScroll = () => { - const duration = (perfNow() - this.scrollAnimation.startTime) / 1000; - const multiplier = (duration > 1 || duration <= 3) ? duration : 1; + const scrollLeft = directionX > 0 ? node.scrollLeft < left : node.scrollLeft > left; + const scrollTop = directionY > 0 ? node.scrollTop < top : node.scrollTop > top; - const dx = Math.sign(deltaX) * (this.scrollAnimation.distance * multiplier); - const dy = Math.sign(deltaY) * (this.scrollAnimation.distance * multiplier); + node.scrollBy({top: directionY * 10, left: directionX * 10, behavior: 'instant'}); - node.scrollBy({top: dy, left: dx, behavior: 'instant'}); - this.scrollAnimation.id = window.requestAnimationFrame(animateScroll); + if (scrollTop || scrollLeft) { + this.scrollAnimationId = window.requestAnimationFrame(animateScroll); + } }; - if (!this.scrollAnimation.isAnimating) { - this.scrollAnimation.isAnimating = true; - this.scrollAnimation.startTime = perfNow(); - this.scrollAnimation.id = window.requestAnimationFrame(animateScroll); - } else { - this.stopAnimatedScrollLoop(this.scrollAnimation.timeoutDelay / 2); - } - } - - stopAnimatedScroll () { - this.scrollAnimation.isAnimating = false; - window.cancelAnimationFrame(this.scrollAnimation.id); - } - - stopAnimatedScrollLoop (timeout) { - clearTimeout(this.scrollAnimation.timeoutId); - this.scrollAnimation.timeoutId = setTimeout(() => { - this.stopAnimatedScroll(); - }, timeout); + this.scrollAnimationId = window.requestAnimationFrame(animateScroll); } // scrollMode 'native' diff --git a/packages/ui/package.json b/packages/ui/package.json index 53e1e0911e..bd2f0ad19f 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@enact/ui", - "version": "5.3.0", + "version": "5.3.1", "description": "A collection of simplified unstyled cross-platform UI components for Enact", "repository": { "type": "git", @@ -32,8 +32,8 @@ } }, "dependencies": { - "@enact/core": "^5.3.0", - "@enact/i18n": "^5.3.0", + "@enact/core": "^5.3.1", + "@enact/i18n": "^5.3.1", "classnames": "^2.5.1", "ilib": "^14.21.1", "invariant": "^2.2.4", diff --git a/packages/ui/useScroll/tests/useScroll-specs.js b/packages/ui/useScroll/tests/useScroll-specs.js new file mode 100644 index 0000000000..36ce564bf5 --- /dev/null +++ b/packages/ui/useScroll/tests/useScroll-specs.js @@ -0,0 +1,72 @@ +import '@testing-library/jest-dom'; + +describe('useScroll', () => { + describe('Rounding target scroll position', () => { + beforeEach(() => { + jest.resetModules(); // important! clears cached modules + }); + + test('should round target position upwards when target is bigger than current position', () => { + jest.doMock('@enact/core/platform', () => ({ + __esModule: true, + platform: {chrome: 132} + })); + + const {roundTarget} = require('../useScroll'); + + const currentPosition = { + scrollPos: { + left: 100, + top: 100 + } + }; + + const {roundedTargetX, roundedTargetY} = roundTarget(currentPosition, 120.5, 120.4); + + expect(roundedTargetX).toEqual(121); + expect(roundedTargetY).toEqual(121); + }); + + test('should round target position downwards when target is bigger than current position', () => { + jest.doMock('@enact/core/platform', () => ({ + __esModule: true, + platform: {chrome: 132} + })); + + const {roundTarget} = require('../useScroll'); + + const currentPosition = { + scrollPos: { + left: 100, + top: 100 + } + }; + + const {roundedTargetX, roundedTargetY} = roundTarget(currentPosition, 90.4, 80.8); + + expect(roundedTargetX).toEqual(90); + expect(roundedTargetY).toEqual(80); + }); + + test('should not round target position when chrome <= 120', () => { + jest.doMock('@enact/core/platform', () => ({ + __esModule: true, + platform: {chrome: 119} + })); + + const {roundTarget} = require('../useScroll'); + + const currentPosition = { + scrollPos: { + left: 100, + top: 100 + } + }; + + const {roundedTargetX, roundedTargetY} = roundTarget(currentPosition, 90.4, 80.8); + + expect(roundedTargetX).toEqual(90.4); + expect(roundedTargetY).toEqual(80.8); + }); + }); +}); diff --git a/packages/ui/useScroll/useScroll.js b/packages/ui/useScroll/useScroll.js index 705d390abb..7d0fced106 100644 --- a/packages/ui/useScroll/useScroll.js +++ b/packages/ui/useScroll/useScroll.js @@ -69,6 +69,20 @@ const TouchableDiv = Touchable(({ref, ...rest}) => (
) const useForceUpdate = () => (useReducer(x => x + 1, 0)); +function roundTarget (currentPosition, targetX, targetY) { + let roundedTargetX, roundedTargetY; + + if (currentPosition?.scrollPos && platform.chrome > 120) { + roundedTargetX = currentPosition?.scrollPos?.left < targetX ? Math.ceil(targetX) : Math.floor(targetX); + roundedTargetY = currentPosition?.scrollPos?.top < targetY ? Math.ceil(targetY) : Math.floor(targetY); + } else { + roundedTargetX = targetX; + roundedTargetY = targetY; + } + + return {roundedTargetX, roundedTargetY}; +} + /** * A custom hook that passes scrollable behavior information as its render prop. * @@ -817,13 +831,6 @@ const useScrollBase = (props) => { } } - function onKeyUp (ev) { - if (scrollContentHandle.current?.stopAnimatedScroll && scrollMode === 'native') { - scrollContentHandle.current?.stopAnimatedScroll(); - } - forward('onKeyUp', ev, props); - } - function scrollToAccumulatedTarget (delta, vertical, overscrollEffect) { if (!mutableRef.current.isScrollAnimationTargetAccumulated) { mutableRef.current.accumulatedTargetX = mutableRef.current.scrollLeft; @@ -1153,10 +1160,12 @@ const useScrollBase = (props) => { stop(); } } else { // scrollMode 'native' + let {roundedTargetX, roundedTargetY} = roundTarget(scrollContentHandle.current, targetX, targetY); + if (animate) { - scrollContentHandle.current.scrollToPosition(targetX, targetY, 'smooth', mutableRef.current.lastInputType); + scrollContentHandle.current.scrollToPosition(roundedTargetX, roundedTargetY, 'smooth'); } else { - scrollContentHandle.current.scrollToPosition(targetX, targetY, 'instant'); + scrollContentHandle.current.scrollToPosition(roundedTargetX, roundedTargetY, 'instant'); } if (props.start) { @@ -1468,7 +1477,6 @@ const useScrollBase = (props) => { // scrollMode 'native' [[ if (scrollMode === 'native' && scrollContentRef.current) { utilEvent('scroll').addEventListener(scrollContentRef, onScroll, {passive: true}); - utilEvent('keyup').addEventListener(scrollContainerRef, onKeyUp); } // scrollMode 'native' ]] @@ -1489,7 +1497,6 @@ const useScrollBase = (props) => { // scrollMode 'native' [[ utilEvent('scroll').removeEventListener(scrollContentRef, onScroll, {passive: true}); - utilEvent('keyup').removeEventListener(scrollContainerRef, onKeyUp); // scrollMode 'native' ]] if (props.removeEventListeners) { @@ -1686,6 +1693,7 @@ export default useScroll; export { assignPropertiesOf, constants, + roundTarget, useScroll, useScrollBase }; diff --git a/packages/webos/package.json b/packages/webos/package.json index 34065987a1..587c999d26 100644 --- a/packages/webos/package.json +++ b/packages/webos/package.json @@ -1,6 +1,6 @@ { "name": "@enact/webos", - "version": "5.3.0", + "version": "5.3.1", "description": "webOS support library", "repository": { "type": "git", @@ -32,7 +32,7 @@ } }, "dependencies": { - "@enact/core": "^5.3.0", + "@enact/core": "^5.3.1", "prop-types": "^15.8.1", "react": "^19.1.1", "react-dom": "^19.1.1" From c1e0570c8ec0d08ff99feccb1323aefc0212e9de Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Wed, 15 Oct 2025 16:53:00 +0300 Subject: [PATCH 23/30] Small fix --- packages/ui/Scroller/ScrollerBasic.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index 5611bf236b..6a70651141 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -79,8 +79,6 @@ class ScrollerBasic extends Component { } } - scrollAnimationId = null; - scrollBounds = { clientWidth: 0, clientHeight: 0, @@ -142,11 +140,11 @@ class ScrollerBasic extends Component { node.scrollBy({top: directionY * 10, left: directionX * 10, behavior: 'instant'}); if (scrollTop || scrollLeft) { - this.scrollAnimationId = window.requestAnimationFrame(animateScroll); + window.requestAnimationFrame(animateScroll); } }; - this.scrollAnimationId = window.requestAnimationFrame(animateScroll); + window.requestAnimationFrame(animateScroll); } // scrollMode 'native' From 7a9accbd3a971fdbaac0951f82973a093c4a3ddf Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Thu, 16 Oct 2025 12:07:11 +0300 Subject: [PATCH 24/30] Small fix --- packages/ui/Scroller/ScrollerBasic.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index 6a70651141..ed8e81fe3f 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -79,6 +79,8 @@ class ScrollerBasic extends Component { } } + scrollAnimationId = null; + scrollBounds = { clientWidth: 0, clientHeight: 0, @@ -137,14 +139,22 @@ class ScrollerBasic extends Component { const scrollLeft = directionX > 0 ? node.scrollLeft < left : node.scrollLeft > left; const scrollTop = directionY > 0 ? node.scrollTop < top : node.scrollTop > top; - node.scrollBy({top: directionY * 10, left: directionX * 10, behavior: 'instant'}); + if (top > this.scrollBounds.maxTop && node.scrollTop === this.scrollBounds.maxTop || + left > this.scrollBounds.maxLeft && node.scrollLeft === this.scrollBounds.maxLeft || + top < 0 && node.scrollTop === 0 || + left < 0 && node.scrollLeft === 0) + { + window.cancelAnimationFrame(this.scrollAnimationId); + return; + } if (scrollTop || scrollLeft) { - window.requestAnimationFrame(animateScroll); + node.scrollBy({top: directionY * 10, left: directionX * 10, behavior: 'instant'}); + this.scrollAnimationId = window.requestAnimationFrame(animateScroll); } }; - window.requestAnimationFrame(animateScroll); + this.scrollAnimationId = window.requestAnimationFrame(animateScroll); } // scrollMode 'native' From 2970d77216821a9f33b61f1fe4bd5bfd0400740e Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Thu, 16 Oct 2025 12:22:59 +0300 Subject: [PATCH 25/30] Small fix --- packages/ui/Scroller/ScrollerBasic.js | 1 + packages/ui/useScroll/useScroll.js | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index ed8e81fe3f..190b6a3ddb 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -139,6 +139,7 @@ class ScrollerBasic extends Component { const scrollLeft = directionX > 0 ? node.scrollLeft < left : node.scrollLeft > left; const scrollTop = directionY > 0 ? node.scrollTop < top : node.scrollTop > top; + // Check if we reached the scroll bounds and cancel the animation if (top > this.scrollBounds.maxTop && node.scrollTop === this.scrollBounds.maxTop || left > this.scrollBounds.maxLeft && node.scrollLeft === this.scrollBounds.maxLeft || top < 0 && node.scrollTop === 0 || diff --git a/packages/ui/useScroll/useScroll.js b/packages/ui/useScroll/useScroll.js index 7d0fced106..0caafcafee 100644 --- a/packages/ui/useScroll/useScroll.js +++ b/packages/ui/useScroll/useScroll.js @@ -825,9 +825,8 @@ const useScrollBase = (props) => { } } else { props.preventScroll?.(ev); - if (!ev.repeat) { - forward('onKeyDown', ev, props); - } + if (ev.repeat && mutableRef.current.lastInputType === 'arrowKey') return; + forward('onKeyDown', ev, props); } } From 0c7a07d330ffceb7c10fcf7c1b4d005a472efb31 Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Thu, 16 Oct 2025 13:06:52 +0300 Subject: [PATCH 26/30] Fixed lint warnings --- packages/ui/Scroller/ScrollerBasic.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index 190b6a3ddb..d95fb14829 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -140,11 +140,12 @@ class ScrollerBasic extends Component { const scrollTop = directionY > 0 ? node.scrollTop < top : node.scrollTop > top; // Check if we reached the scroll bounds and cancel the animation - if (top > this.scrollBounds.maxTop && node.scrollTop === this.scrollBounds.maxTop || + if ( + top > this.scrollBounds.maxTop && node.scrollTop === this.scrollBounds.maxTop || left > this.scrollBounds.maxLeft && node.scrollLeft === this.scrollBounds.maxLeft || top < 0 && node.scrollTop === 0 || - left < 0 && node.scrollLeft === 0) - { + left < 0 && node.scrollLeft === 0 + ) { window.cancelAnimationFrame(this.scrollAnimationId); return; } From ddb74d68883b485dd62d12339decf9c4a59f02b0 Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Thu, 16 Oct 2025 14:54:58 +0300 Subject: [PATCH 27/30] Fixed unit tests --- packages/ui/Scroller/tests/ScrollerBasic-specs.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/ui/Scroller/tests/ScrollerBasic-specs.js b/packages/ui/Scroller/tests/ScrollerBasic-specs.js index 5bd4f89474..f850df3463 100644 --- a/packages/ui/Scroller/tests/ScrollerBasic-specs.js +++ b/packages/ui/Scroller/tests/ScrollerBasic-specs.js @@ -27,7 +27,7 @@ describe('ScrollBasic', () => { () => { const instance = new ScrollerBasic({scrollContentRef, direction: 'both'}); - instance.scrollToPosition(100, 200, 'smooth', 'arrowKey'); + instance.scrollToPosition(100, 200, 'smooth'); expect(scrollContentRef.current.scrollTo).toHaveBeenCalledWith({left: 100, top: 200, behavior: 'smooth'}); instance.scrollToPosition(100, 200, 'instant'); @@ -40,6 +40,8 @@ describe('ScrollBasic', () => { () => { let rafCallback; const instance = new ScrollerBasic({scrollContentRef, direction: 'both'}); + instance.scrollBounds.maxTop = 500; + instance.scrollBounds.maxLeft = 500; window.requestAnimationFrame = jest.fn((cb) => { rafCallback = cb; @@ -50,7 +52,9 @@ describe('ScrollBasic', () => { rafCallback(); expect(scrollContentRef.current.scrollBy).toHaveBeenCalled(); - instance.stopAnimatedScroll(); + scrollContentRef.current.scrollTop = 500; + instance.animateScroll(550, 550, scrollContentRef.current); + rafCallback(); expect(window.cancelAnimationFrame).toHaveBeenCalled(); } ); From 58b942d80d90c19922d1cce0b35738b32e8d0c4e Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Fri, 17 Oct 2025 12:19:12 +0300 Subject: [PATCH 28/30] Added condition to apply animation only on `ev.repeat` --- packages/ui/Scroller/ScrollerBasic.js | 6 +++--- packages/ui/useScroll/useScroll.js | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index d95fb14829..3715b7fe5a 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -119,11 +119,11 @@ class ScrollerBasic extends Component { } // scrollMode 'native' - scrollToPosition (left, top, behavior) { + scrollToPosition (left, top, behavior, repeat) { const node = this.props.scrollContentRef.current; const smoothBehavior = behavior === 'smooth'; - if (platform.chrome && smoothBehavior) { + if (platform.chrome && smoothBehavior && repeat) { this.animateScroll(this.getRtlPositionX(left), top, node); } else { node.scrollTo({left: this.getRtlPositionX(left), top, behavior}); @@ -151,7 +151,7 @@ class ScrollerBasic extends Component { } if (scrollTop || scrollLeft) { - node.scrollBy({top: directionY * 10, left: directionX * 10, behavior: 'instant'}); + node.scrollBy({top: directionY * 18, left: directionX * 18, behavior: 'instant'}); this.scrollAnimationId = window.requestAnimationFrame(animateScroll); } }; diff --git a/packages/ui/useScroll/useScroll.js b/packages/ui/useScroll/useScroll.js index 0caafcafee..dc9637334b 100644 --- a/packages/ui/useScroll/useScroll.js +++ b/packages/ui/useScroll/useScroll.js @@ -825,6 +825,7 @@ const useScrollBase = (props) => { } } else { props.preventScroll?.(ev); + mutableRef.current.repeat = ev.repeat; if (ev.repeat && mutableRef.current.lastInputType === 'arrowKey') return; forward('onKeyDown', ev, props); } @@ -1162,7 +1163,7 @@ const useScrollBase = (props) => { let {roundedTargetX, roundedTargetY} = roundTarget(scrollContentHandle.current, targetX, targetY); if (animate) { - scrollContentHandle.current.scrollToPosition(roundedTargetX, roundedTargetY, 'smooth'); + scrollContentHandle.current.scrollToPosition(roundedTargetX, roundedTargetY, 'smooth', mutableRef.current.repeat); } else { scrollContentHandle.current.scrollToPosition(roundedTargetX, roundedTargetY, 'instant'); } From d1d9951208495620b13dc641caf2c74644fcda64 Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Tue, 21 Oct 2025 14:05:25 +0300 Subject: [PATCH 29/30] Added description for `animateScroll` --- packages/ui/Scroller/ScrollerBasic.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index 3715b7fe5a..221b33d5c5 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -130,7 +130,12 @@ class ScrollerBasic extends Component { } } - // scrollMode 'native' + /** + * Programmatically animates the native scroll position of `node` toward the target `left`/`top` + * offsets using `requestAnimationFrame`. It computes the scroll direction on each axis, then repeatedly + * calls `scrollBy` in small 18px steps (instant behavior) until the target is reached or a scroll bound + * is hit. Used as a Chrome fallback when repeating a smooth scroll. + */ animateScroll (left, top, node) { const directionX = Math.sign(left - node.scrollLeft); const directionY = Math.sign(top - node.scrollTop); From 538f7f636a4e768e201c153a94faf478713369f2 Mon Sep 17 00:00:00 2001 From: ion-andrusciac-lgp Date: Mon, 27 Oct 2025 17:14:46 +0200 Subject: [PATCH 30/30] Adjusted scroll pace --- packages/ui/Scroller/ScrollerBasic.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/Scroller/ScrollerBasic.js b/packages/ui/Scroller/ScrollerBasic.js index 221b33d5c5..58475ed266 100644 --- a/packages/ui/Scroller/ScrollerBasic.js +++ b/packages/ui/Scroller/ScrollerBasic.js @@ -156,7 +156,7 @@ class ScrollerBasic extends Component { } if (scrollTop || scrollLeft) { - node.scrollBy({top: directionY * 18, left: directionX * 18, behavior: 'instant'}); + node.scrollBy({top: directionY * 8, left: directionX * 8, behavior: 'instant'}); this.scrollAnimationId = window.requestAnimationFrame(animateScroll); } };