Skip to content

Commit 1713d0f

Browse files
committed
Add support for onScrollEnd event (#26789)
## Summary This adds support for the new [scrollend](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollend_event) event. It was recently added to the spec, and is currently supported in Firefox 109 and Chrome Canary (shipping in Chrome 114). You can read more about this event [here](https://developer.chrome.com/blog/scrollend-a-new-javascript-event/). This PR adds support for the `onScrollEnd` prop, following the implementation for `onScroll`. ## How did you test this change? Added unit tests. DiffTrain build for [537228f](537228f)
1 parent cfabdb3 commit 1713d0f

14 files changed

+270
-64
lines changed

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
be67db46b60d94f9fbefccf2523429af25873e5b
1+
537228f9fd703d18bea1f6d20fa0e5006b795c42

compiled/facebook-www/React-dev.classic.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ if (
2727
}
2828
"use strict";
2929

30-
var ReactVersion = "18.3.0-www-classic-5bedd128";
30+
var ReactVersion = "18.3.0-www-classic-01108d4a";
3131

3232
// ATTENTION
3333
// When adding new symbols to this file,

compiled/facebook-www/React-prod.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -615,4 +615,4 @@ exports.useSyncExternalStore = function (
615615
exports.useTransition = function () {
616616
return ReactCurrentDispatcher.current.useTransition();
617617
};
618-
exports.version = "18.3.0-www-modern-1eaea04e";
618+
exports.version = "18.3.0-www-modern-00808b45";

compiled/facebook-www/React-profiling.modern.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,7 @@ exports.useSyncExternalStore = function (
626626
exports.useTransition = function () {
627627
return ReactCurrentDispatcher.current.useTransition();
628628
};
629-
exports.version = "18.3.0-www-modern-316f00ed";
629+
exports.version = "18.3.0-www-modern-313f0ec6";
630630

631631
/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
632632
if (

compiled/facebook-www/ReactDOM-dev.classic.js

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34036,7 +34036,7 @@ function createFiberRoot(
3403634036
return root;
3403734037
}
3403834038

34039-
var ReactVersion = "18.3.0-www-classic-ba4a7e73";
34039+
var ReactVersion = "18.3.0-www-classic-2d079347";
3404034040

3404134041
function createPortal$1(
3404234042
children,
@@ -37189,7 +37189,7 @@ var topLevelEventsToReactNames = new Map(); // NOTE: Capitalization is important
3718937189
//
3719037190
// prettier-ignore
3719137191

37192-
var simpleEventPluginEvents = ['abort', 'auxClick', 'cancel', 'canPlay', 'canPlayThrough', 'click', 'close', 'contextMenu', 'copy', 'cut', 'drag', 'dragEnd', 'dragEnter', 'dragExit', 'dragLeave', 'dragOver', 'dragStart', 'drop', 'durationChange', 'emptied', 'encrypted', 'ended', 'error', 'gotPointerCapture', 'input', 'invalid', 'keyDown', 'keyPress', 'keyUp', 'load', 'loadedData', 'loadedMetadata', 'loadStart', 'lostPointerCapture', 'mouseDown', 'mouseMove', 'mouseOut', 'mouseOver', 'mouseUp', 'paste', 'pause', 'play', 'playing', 'pointerCancel', 'pointerDown', 'pointerMove', 'pointerOut', 'pointerOver', 'pointerUp', 'progress', 'rateChange', 'reset', 'resize', 'seeked', 'seeking', 'stalled', 'submit', 'suspend', 'timeUpdate', 'touchCancel', 'touchEnd', 'touchStart', 'volumeChange', 'scroll', 'toggle', 'touchMove', 'waiting', 'wheel'];
37192+
var simpleEventPluginEvents = ['abort', 'auxClick', 'cancel', 'canPlay', 'canPlayThrough', 'click', 'close', 'contextMenu', 'copy', 'cut', 'drag', 'dragEnd', 'dragEnter', 'dragExit', 'dragLeave', 'dragOver', 'dragStart', 'drop', 'durationChange', 'emptied', 'encrypted', 'ended', 'error', 'gotPointerCapture', 'input', 'invalid', 'keyDown', 'keyPress', 'keyUp', 'load', 'loadedData', 'loadedMetadata', 'loadStart', 'lostPointerCapture', 'mouseDown', 'mouseMove', 'mouseOut', 'mouseOver', 'mouseUp', 'paste', 'pause', 'play', 'playing', 'pointerCancel', 'pointerDown', 'pointerMove', 'pointerOut', 'pointerOver', 'pointerUp', 'progress', 'rateChange', 'reset', 'resize', 'seeked', 'seeking', 'stalled', 'submit', 'suspend', 'timeUpdate', 'touchCancel', 'touchEnd', 'touchStart', 'volumeChange', 'scroll', 'scrollEnd', 'toggle', 'touchMove', 'waiting', 'wheel'];
3719337193

3719437194
{
3719537195
// Special case: these two events don't have on* React handler
@@ -37325,6 +37325,7 @@ function extractEvents$1(
3732537325
break;
3732637326

3732737327
case "scroll":
37328+
case "scrollend":
3732837329
SyntheticEventCtor = SyntheticUIEvent;
3732937330
break;
3733037331

@@ -37385,7 +37386,7 @@ function extractEvents$1(
3738537386
// nonDelegatedEvents list in DOMPluginEventSystem.
3738637387
// Then we can remove this special list.
3738737388
// This is a breaking change that can wait until React 18.
37388-
domEventName === "scroll";
37389+
(domEventName === "scroll" || domEventName === "scrollend");
3738937390

3739037391
var _listeners = accumulateSinglePhaseListeners(
3739137392
targetInst,
@@ -37525,9 +37526,15 @@ var mediaEventTypes = [
3752537526
// because these events do not consistently bubble in the DOM.
3752637527

3752737528
var nonDelegatedEvents = new Set(
37528-
["cancel", "close", "invalid", "load", "scroll", "toggle"].concat(
37529-
mediaEventTypes
37530-
)
37529+
[
37530+
"cancel",
37531+
"close",
37532+
"invalid",
37533+
"load",
37534+
"scroll",
37535+
"scrollend",
37536+
"toggle"
37537+
].concat(mediaEventTypes)
3753137538
);
3753237539

3753337540
function executeDispatch(event, listener, currentTarget) {
@@ -38786,6 +38793,18 @@ function setProp(domElement, tag, key, value, props, prevValue) {
3878638793
break;
3878738794
}
3878838795

38796+
case "onScrollEnd": {
38797+
if (value != null) {
38798+
if (typeof value !== "function") {
38799+
warnForInvalidEventListener(key, value);
38800+
}
38801+
38802+
listenToNonDelegatedEvent("scrollend", domElement);
38803+
}
38804+
38805+
break;
38806+
}
38807+
3878938808
case "dangerouslySetInnerHTML": {
3879038809
if (value != null) {
3879138810
if (typeof value !== "object" || !("__html" in value)) {
@@ -39194,6 +39213,18 @@ function setPropOnCustomElement(domElement, tag, key, value, props, prevValue) {
3919439213
break;
3919539214
}
3919639215

39216+
case "onScrollEnd": {
39217+
if (value != null) {
39218+
if (typeof value !== "function") {
39219+
warnForInvalidEventListener(key, value);
39220+
}
39221+
39222+
listenToNonDelegatedEvent("scrollend", domElement);
39223+
}
39224+
39225+
break;
39226+
}
39227+
3919739228
case "onClick": {
3919839229
// TODO: This cast may not be sound for SVG, MathML or custom elements.
3919939230
if (value != null) {
@@ -41257,6 +41288,10 @@ function diffHydratedProperties(
4125741288
listenToNonDelegatedEvent("scroll", domElement);
4125841289
}
4125941290

41291+
if (props.onScrollEnd != null) {
41292+
listenToNonDelegatedEvent("scrollend", domElement);
41293+
}
41294+
4126041295
if (props.onClick != null) {
4126141296
// TODO: This cast may not be sound for SVG, MathML or custom elements.
4126241297
trapClickOnNonInteractiveElement(domElement);

compiled/facebook-www/ReactDOM-dev.modern.js

Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33881,7 +33881,7 @@ function createFiberRoot(
3388133881
return root;
3388233882
}
3388333883

33884-
var ReactVersion = "18.3.0-www-modern-c68162ba";
33884+
var ReactVersion = "18.3.0-www-modern-a7b8748a";
3388533885

3388633886
function createPortal$1(
3388733887
children,
@@ -37697,7 +37697,7 @@ var topLevelEventsToReactNames = new Map(); // NOTE: Capitalization is important
3769737697
//
3769837698
// prettier-ignore
3769937699

37700-
var simpleEventPluginEvents = ['abort', 'auxClick', 'cancel', 'canPlay', 'canPlayThrough', 'click', 'close', 'contextMenu', 'copy', 'cut', 'drag', 'dragEnd', 'dragEnter', 'dragExit', 'dragLeave', 'dragOver', 'dragStart', 'drop', 'durationChange', 'emptied', 'encrypted', 'ended', 'error', 'gotPointerCapture', 'input', 'invalid', 'keyDown', 'keyPress', 'keyUp', 'load', 'loadedData', 'loadedMetadata', 'loadStart', 'lostPointerCapture', 'mouseDown', 'mouseMove', 'mouseOut', 'mouseOver', 'mouseUp', 'paste', 'pause', 'play', 'playing', 'pointerCancel', 'pointerDown', 'pointerMove', 'pointerOut', 'pointerOver', 'pointerUp', 'progress', 'rateChange', 'reset', 'resize', 'seeked', 'seeking', 'stalled', 'submit', 'suspend', 'timeUpdate', 'touchCancel', 'touchEnd', 'touchStart', 'volumeChange', 'scroll', 'toggle', 'touchMove', 'waiting', 'wheel'];
37700+
var simpleEventPluginEvents = ['abort', 'auxClick', 'cancel', 'canPlay', 'canPlayThrough', 'click', 'close', 'contextMenu', 'copy', 'cut', 'drag', 'dragEnd', 'dragEnter', 'dragExit', 'dragLeave', 'dragOver', 'dragStart', 'drop', 'durationChange', 'emptied', 'encrypted', 'ended', 'error', 'gotPointerCapture', 'input', 'invalid', 'keyDown', 'keyPress', 'keyUp', 'load', 'loadedData', 'loadedMetadata', 'loadStart', 'lostPointerCapture', 'mouseDown', 'mouseMove', 'mouseOut', 'mouseOver', 'mouseUp', 'paste', 'pause', 'play', 'playing', 'pointerCancel', 'pointerDown', 'pointerMove', 'pointerOut', 'pointerOver', 'pointerUp', 'progress', 'rateChange', 'reset', 'resize', 'seeked', 'seeking', 'stalled', 'submit', 'suspend', 'timeUpdate', 'touchCancel', 'touchEnd', 'touchStart', 'volumeChange', 'scroll', 'scrollEnd', 'toggle', 'touchMove', 'waiting', 'wheel'];
3770137701

3770237702
{
3770337703
// Special case: these two events don't have on* React handler
@@ -37833,6 +37833,7 @@ function extractEvents$1(
3783337833
break;
3783437834

3783537835
case "scroll":
37836+
case "scrollend":
3783637837
SyntheticEventCtor = SyntheticUIEvent;
3783737838
break;
3783837839

@@ -37893,7 +37894,7 @@ function extractEvents$1(
3789337894
// nonDelegatedEvents list in DOMPluginEventSystem.
3789437895
// Then we can remove this special list.
3789537896
// This is a breaking change that can wait until React 18.
37896-
domEventName === "scroll";
37897+
(domEventName === "scroll" || domEventName === "scrollend");
3789737898

3789837899
var _listeners = accumulateSinglePhaseListeners(
3789937900
targetInst,
@@ -38033,9 +38034,15 @@ var mediaEventTypes = [
3803338034
// because these events do not consistently bubble in the DOM.
3803438035

3803538036
var nonDelegatedEvents = new Set(
38036-
["cancel", "close", "invalid", "load", "scroll", "toggle"].concat(
38037-
mediaEventTypes
38038-
)
38037+
[
38038+
"cancel",
38039+
"close",
38040+
"invalid",
38041+
"load",
38042+
"scroll",
38043+
"scrollend",
38044+
"toggle"
38045+
].concat(mediaEventTypes)
3803938046
);
3804038047

3804138048
function executeDispatch(event, listener, currentTarget) {
@@ -39299,6 +39306,18 @@ function setProp(domElement, tag, key, value, props, prevValue) {
3929939306
break;
3930039307
}
3930139308

39309+
case "onScrollEnd": {
39310+
if (value != null) {
39311+
if (typeof value !== "function") {
39312+
warnForInvalidEventListener(key, value);
39313+
}
39314+
39315+
listenToNonDelegatedEvent("scrollend", domElement);
39316+
}
39317+
39318+
break;
39319+
}
39320+
3930239321
case "dangerouslySetInnerHTML": {
3930339322
if (value != null) {
3930439323
if (typeof value !== "object" || !("__html" in value)) {
@@ -39707,6 +39726,18 @@ function setPropOnCustomElement(domElement, tag, key, value, props, prevValue) {
3970739726
break;
3970839727
}
3970939728

39729+
case "onScrollEnd": {
39730+
if (value != null) {
39731+
if (typeof value !== "function") {
39732+
warnForInvalidEventListener(key, value);
39733+
}
39734+
39735+
listenToNonDelegatedEvent("scrollend", domElement);
39736+
}
39737+
39738+
break;
39739+
}
39740+
3971039741
case "onClick": {
3971139742
// TODO: This cast may not be sound for SVG, MathML or custom elements.
3971239743
if (value != null) {
@@ -41767,6 +41798,10 @@ function diffHydratedProperties(
4176741798
listenToNonDelegatedEvent("scroll", domElement);
4176841799
}
4176941800

41801+
if (props.onScrollEnd != null) {
41802+
listenToNonDelegatedEvent("scrollend", domElement);
41803+
}
41804+
4177041805
if (props.onClick != null) {
4177141806
// TODO: This cast may not be sound for SVG, MathML or custom elements.
4177241807
trapClickOnNonInteractiveElement(domElement);

compiled/facebook-www/ReactDOM-prod.classic.js

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12835,7 +12835,7 @@ var ANIMATION_END = getVendorPrefixedEventName("animationend"),
1283512835
TRANSITION_END = getVendorPrefixedEventName("transitionend"),
1283612836
topLevelEventsToReactNames = new Map(),
1283712837
simpleEventPluginEvents =
12838-
"abort auxClick cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll toggle touchMove waiting wheel".split(
12838+
"abort auxClick cancel canPlay canPlayThrough click close contextMenu copy cut drag dragEnd dragEnter dragExit dragLeave dragOver dragStart drop durationChange emptied encrypted ended error gotPointerCapture input invalid keyDown keyPress keyUp load loadedData loadedMetadata loadStart lostPointerCapture mouseDown mouseMove mouseOut mouseOver mouseUp paste pause play playing pointerCancel pointerDown pointerMove pointerOut pointerOver pointerUp progress rateChange reset resize seeked seeking stalled submit suspend timeUpdate touchCancel touchEnd touchStart volumeChange scroll scrollEnd toggle touchMove waiting wheel".split(
1283912839
" "
1284012840
);
1284112841
topLevelEventsToReactNames.set("beforeblur", null);
@@ -12905,7 +12905,9 @@ var mediaEventTypes =
1290512905
" "
1290612906
),
1290712907
nonDelegatedEvents = new Set(
12908-
"cancel close invalid load scroll toggle".split(" ").concat(mediaEventTypes)
12908+
"cancel close invalid load scroll scrollend toggle"
12909+
.split(" ")
12910+
.concat(mediaEventTypes)
1290912911
);
1291012912
function executeDispatch(event, listener, currentTarget) {
1291112913
var type = event.type || "unknown-event";
@@ -13182,6 +13184,7 @@ function dispatchEventForPluginEventSystem(
1318213184
SyntheticEventCtor = SyntheticTransitionEvent;
1318313185
break;
1318413186
case "scroll":
13187+
case "scrollend":
1318513188
SyntheticEventCtor = SyntheticUIEvent;
1318613189
break;
1318713190
case "wheel":
@@ -13226,7 +13229,8 @@ function dispatchEventForPluginEventSystem(
1322613229
reactName,
1322713230
nativeEvent.type,
1322813231
inCapturePhase,
13229-
!inCapturePhase && "scroll" === domEventName,
13232+
!inCapturePhase &&
13233+
("scroll" === domEventName || "scrollend" === domEventName),
1323013234
nativeEvent
1323113235
)),
1323213236
0 < inCapturePhase.length &&
@@ -13755,6 +13759,9 @@ function setProp(domElement, tag, key, value, props, prevValue) {
1375513759
case "onScroll":
1375613760
null != value && listenToNonDelegatedEvent("scroll", domElement);
1375713761
break;
13762+
case "onScrollEnd":
13763+
null != value && listenToNonDelegatedEvent("scrollend", domElement);
13764+
break;
1375813765
case "dangerouslySetInnerHTML":
1375913766
if (null != value) {
1376013767
if ("object" !== typeof value || !("__html" in value))
@@ -13988,6 +13995,9 @@ function setPropOnCustomElement(domElement, tag, key, value, props, prevValue) {
1398813995
case "onScroll":
1398913996
null != value && listenToNonDelegatedEvent("scroll", domElement);
1399013997
break;
13998+
case "onScrollEnd":
13999+
null != value && listenToNonDelegatedEvent("scrollend", domElement);
14000+
break;
1399114001
case "onClick":
1399214002
null != value && (domElement.onclick = noop$1);
1399314003
break;
@@ -14857,6 +14867,7 @@ function hydrateInstance(
1485714867
normalizeMarkupForTextOrAttribute(internalInstanceHandle)),
1485814868
"body" !== type && (instance.textContent = hostContext));
1485914869
null != props.onScroll && listenToNonDelegatedEvent("scroll", instance);
14870+
null != props.onScrollEnd && listenToNonDelegatedEvent("scrollend", instance);
1486014871
null != props.onClick && (instance.onclick = noop$1);
1486114872
}
1486214873
function getParentSuspenseInstance(targetInstance) {
@@ -16390,7 +16401,7 @@ Internals.Events = [
1639016401
var devToolsConfig$jscomp$inline_1779 = {
1639116402
findFiberByHostInstance: getClosestInstanceFromNode,
1639216403
bundleType: 0,
16393-
version: "18.3.0-www-classic-4be75e6a",
16404+
version: "18.3.0-www-classic-526fea3c",
1639416405
rendererPackageName: "react-dom"
1639516406
};
1639616407
var internals$jscomp$inline_2125 = {
@@ -16420,7 +16431,7 @@ var internals$jscomp$inline_2125 = {
1642016431
scheduleRoot: null,
1642116432
setRefreshHandler: null,
1642216433
getCurrentFiber: null,
16423-
reconcilerVersion: "18.3.0-www-classic-4be75e6a"
16434+
reconcilerVersion: "18.3.0-www-classic-526fea3c"
1642416435
};
1642516436
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
1642616437
var hook$jscomp$inline_2126 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
@@ -16757,4 +16768,4 @@ exports.useFormState = function () {
1675716768
exports.useFormStatus = function () {
1675816769
throw Error(formatProdErrorMessage(248));
1675916770
};
16760-
exports.version = "18.3.0-www-classic-4be75e6a";
16771+
exports.version = "18.3.0-www-classic-526fea3c";

0 commit comments

Comments
 (0)