Skip to content

Commit

Permalink
Add support for onScrollEnd event (#26789)
Browse files Browse the repository at this point in the history
## 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)
sophiebits committed Oct 11, 2023
1 parent cfabdb3 commit 1713d0f
Showing 14 changed files with 270 additions and 64 deletions.
2 changes: 1 addition & 1 deletion compiled/facebook-www/REVISION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
be67db46b60d94f9fbefccf2523429af25873e5b
537228f9fd703d18bea1f6d20fa0e5006b795c42
2 changes: 1 addition & 1 deletion compiled/facebook-www/React-dev.classic.js
Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@ if (
}
"use strict";

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

// ATTENTION
// When adding new symbols to this file,
2 changes: 1 addition & 1 deletion compiled/facebook-www/React-prod.modern.js
Original file line number Diff line number Diff line change
@@ -615,4 +615,4 @@ exports.useSyncExternalStore = function (
exports.useTransition = function () {
return ReactCurrentDispatcher.current.useTransition();
};
exports.version = "18.3.0-www-modern-1eaea04e";
exports.version = "18.3.0-www-modern-00808b45";
2 changes: 1 addition & 1 deletion compiled/facebook-www/React-profiling.modern.js
Original file line number Diff line number Diff line change
@@ -626,7 +626,7 @@ exports.useSyncExternalStore = function (
exports.useTransition = function () {
return ReactCurrentDispatcher.current.useTransition();
};
exports.version = "18.3.0-www-modern-316f00ed";
exports.version = "18.3.0-www-modern-313f0ec6";

/* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */
if (
47 changes: 41 additions & 6 deletions compiled/facebook-www/ReactDOM-dev.classic.js
Original file line number Diff line number Diff line change
@@ -34036,7 +34036,7 @@ function createFiberRoot(
return root;
}

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

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

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'];
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'];

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

case "scroll":
case "scrollend":
SyntheticEventCtor = SyntheticUIEvent;
break;

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

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

var nonDelegatedEvents = new Set(
["cancel", "close", "invalid", "load", "scroll", "toggle"].concat(
mediaEventTypes
)
[
"cancel",
"close",
"invalid",
"load",
"scroll",
"scrollend",
"toggle"
].concat(mediaEventTypes)
);

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

case "onScrollEnd": {
if (value != null) {
if (typeof value !== "function") {
warnForInvalidEventListener(key, value);
}

listenToNonDelegatedEvent("scrollend", domElement);
}

break;
}

case "dangerouslySetInnerHTML": {
if (value != null) {
if (typeof value !== "object" || !("__html" in value)) {
@@ -39194,6 +39213,18 @@ function setPropOnCustomElement(domElement, tag, key, value, props, prevValue) {
break;
}

case "onScrollEnd": {
if (value != null) {
if (typeof value !== "function") {
warnForInvalidEventListener(key, value);
}

listenToNonDelegatedEvent("scrollend", domElement);
}

break;
}

case "onClick": {
// TODO: This cast may not be sound for SVG, MathML or custom elements.
if (value != null) {
@@ -41257,6 +41288,10 @@ function diffHydratedProperties(
listenToNonDelegatedEvent("scroll", domElement);
}

if (props.onScrollEnd != null) {
listenToNonDelegatedEvent("scrollend", domElement);
}

if (props.onClick != null) {
// TODO: This cast may not be sound for SVG, MathML or custom elements.
trapClickOnNonInteractiveElement(domElement);
47 changes: 41 additions & 6 deletions compiled/facebook-www/ReactDOM-dev.modern.js
Original file line number Diff line number Diff line change
@@ -33881,7 +33881,7 @@ function createFiberRoot(
return root;
}

var ReactVersion = "18.3.0-www-modern-c68162ba";
var ReactVersion = "18.3.0-www-modern-a7b8748a";

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

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'];
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'];

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

case "scroll":
case "scrollend":
SyntheticEventCtor = SyntheticUIEvent;
break;

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

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

var nonDelegatedEvents = new Set(
["cancel", "close", "invalid", "load", "scroll", "toggle"].concat(
mediaEventTypes
)
[
"cancel",
"close",
"invalid",
"load",
"scroll",
"scrollend",
"toggle"
].concat(mediaEventTypes)
);

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

case "onScrollEnd": {
if (value != null) {
if (typeof value !== "function") {
warnForInvalidEventListener(key, value);
}

listenToNonDelegatedEvent("scrollend", domElement);
}

break;
}

case "dangerouslySetInnerHTML": {
if (value != null) {
if (typeof value !== "object" || !("__html" in value)) {
@@ -39707,6 +39726,18 @@ function setPropOnCustomElement(domElement, tag, key, value, props, prevValue) {
break;
}

case "onScrollEnd": {
if (value != null) {
if (typeof value !== "function") {
warnForInvalidEventListener(key, value);
}

listenToNonDelegatedEvent("scrollend", domElement);
}

break;
}

case "onClick": {
// TODO: This cast may not be sound for SVG, MathML or custom elements.
if (value != null) {
@@ -41767,6 +41798,10 @@ function diffHydratedProperties(
listenToNonDelegatedEvent("scroll", domElement);
}

if (props.onScrollEnd != null) {
listenToNonDelegatedEvent("scrollend", domElement);
}

if (props.onClick != null) {
// TODO: This cast may not be sound for SVG, MathML or custom elements.
trapClickOnNonInteractiveElement(domElement);
23 changes: 17 additions & 6 deletions compiled/facebook-www/ReactDOM-prod.classic.js
Original file line number Diff line number Diff line change
@@ -12835,7 +12835,7 @@ var ANIMATION_END = getVendorPrefixedEventName("animationend"),
TRANSITION_END = getVendorPrefixedEventName("transitionend"),
topLevelEventsToReactNames = new Map(),
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".split(
"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(
" "
);
topLevelEventsToReactNames.set("beforeblur", null);
@@ -12905,7 +12905,9 @@ var mediaEventTypes =
" "
),
nonDelegatedEvents = new Set(
"cancel close invalid load scroll toggle".split(" ").concat(mediaEventTypes)
"cancel close invalid load scroll scrollend toggle"
.split(" ")
.concat(mediaEventTypes)
);
function executeDispatch(event, listener, currentTarget) {
var type = event.type || "unknown-event";
@@ -13182,6 +13184,7 @@ function dispatchEventForPluginEventSystem(
SyntheticEventCtor = SyntheticTransitionEvent;
break;
case "scroll":
case "scrollend":
SyntheticEventCtor = SyntheticUIEvent;
break;
case "wheel":
@@ -13226,7 +13229,8 @@ function dispatchEventForPluginEventSystem(
reactName,
nativeEvent.type,
inCapturePhase,
!inCapturePhase && "scroll" === domEventName,
!inCapturePhase &&
("scroll" === domEventName || "scrollend" === domEventName),
nativeEvent
)),
0 < inCapturePhase.length &&
@@ -13755,6 +13759,9 @@ function setProp(domElement, tag, key, value, props, prevValue) {
case "onScroll":
null != value && listenToNonDelegatedEvent("scroll", domElement);
break;
case "onScrollEnd":
null != value && listenToNonDelegatedEvent("scrollend", domElement);
break;
case "dangerouslySetInnerHTML":
if (null != value) {
if ("object" !== typeof value || !("__html" in value))
@@ -13988,6 +13995,9 @@ function setPropOnCustomElement(domElement, tag, key, value, props, prevValue) {
case "onScroll":
null != value && listenToNonDelegatedEvent("scroll", domElement);
break;
case "onScrollEnd":
null != value && listenToNonDelegatedEvent("scrollend", domElement);
break;
case "onClick":
null != value && (domElement.onclick = noop$1);
break;
@@ -14857,6 +14867,7 @@ function hydrateInstance(
normalizeMarkupForTextOrAttribute(internalInstanceHandle)),
"body" !== type && (instance.textContent = hostContext));
null != props.onScroll && listenToNonDelegatedEvent("scroll", instance);
null != props.onScrollEnd && listenToNonDelegatedEvent("scrollend", instance);
null != props.onClick && (instance.onclick = noop$1);
}
function getParentSuspenseInstance(targetInstance) {
@@ -16390,7 +16401,7 @@ Internals.Events = [
var devToolsConfig$jscomp$inline_1779 = {
findFiberByHostInstance: getClosestInstanceFromNode,
bundleType: 0,
version: "18.3.0-www-classic-4be75e6a",
version: "18.3.0-www-classic-526fea3c",
rendererPackageName: "react-dom"
};
var internals$jscomp$inline_2125 = {
@@ -16420,7 +16431,7 @@ var internals$jscomp$inline_2125 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "18.3.0-www-classic-4be75e6a"
reconcilerVersion: "18.3.0-www-classic-526fea3c"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_2126 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
@@ -16757,4 +16768,4 @@ exports.useFormState = function () {
exports.useFormStatus = function () {
throw Error(formatProdErrorMessage(248));
};
exports.version = "18.3.0-www-classic-4be75e6a";
exports.version = "18.3.0-www-classic-526fea3c";
Loading

0 comments on commit 1713d0f

Please sign in to comment.