@enact/ui
Version:
A collection of simplified unstyled cross-platform UI components for Enact
1,246 lines (1,191 loc) • 63.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useScrollBase = exports.useScroll = exports["default"] = exports.constants = exports.assignPropertiesOf = void 0;
var _classnames = _interopRequireDefault(require("classnames"));
var _handle = require("@enact/core/handle");
var _keymap = require("@enact/core/keymap");
var _platform = require("@enact/core/platform");
var _Registry = _interopRequireDefault(require("@enact/core/internal/Registry"));
var _util = require("@enact/core/util");
var _clamp = _interopRequireDefault(require("ramda/src/clamp"));
var _react = require("react");
var _warning = _interopRequireDefault(require("warning"));
var _Resizable = require("../Resizable");
var _resolution = _interopRequireDefault(require("../resolution"));
var _Touchable = _interopRequireDefault(require("../Touchable"));
var _ScrollAnimator = _interopRequireDefault(require("./ScrollAnimator"));
var _utilDOM = _interopRequireDefault(require("./utilDOM"));
var _utilEvent = _interopRequireDefault(require("./utilEvent"));
var _useScrollModule = _interopRequireDefault(require("./useScroll.module.css"));
var _jsxRuntime = require("react/jsx-runtime");
var _excluded = ["ref"],
_excluded2 = ["childProps", "children", "className", "clientSize", "assignProperties", "dataSize", "direction", "horizontalScrollbar", "horizontalScrollbarHandle", "itemRenderer", "itemSize", "itemSizes", "noScrollByDrag", "noScrollByWheel", "overhang", "overscrollEffectOn", "pageScroll", "role", "rtl", "scrollContainerRef", "scrollContentHandle", "scrollContentRef", "scrollMode", "setScrollContainerHandle", "snapToCenter", "spacing", "spotlightContainerDisabled", "verticalScrollbar", "verticalScrollbarHandle", "wrap"];
/* global ResizeObserver */
/**
* Unstyled scrollable hook and behaviors to be customized by a theme or application.
*
* @module ui/useScroll
* @exports assignPropertiesOf
* @exports constants
* @exports useScroll
* @exports useScrollBase
* @private
*/
// scrollMode 'native'
function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; }
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _objectWithoutProperties(e, t) { if (null == e) return {}; var o, r, i = _objectWithoutPropertiesLoose(e, t); if (Object.getOwnPropertySymbols) { var n = Object.getOwnPropertySymbols(e); for (r = 0; r < n.length; r++) o = n[r], -1 === t.indexOf(o) && {}.propertyIsEnumerable.call(e, o) && (i[o] = e[o]); } return i; }
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
var constants = exports.constants = {
animationDuration: 1000,
epsilon: 1,
flickConfig: {
maxDuration: null
},
isPageDown: (0, _keymap.is)('pageDown'),
isPageUp: (0, _keymap.is)('pageUp'),
nop: function nop() {},
overscrollDefaultRatio: 0.5,
overscrollTypeNone: 0,
overscrollTypeHold: 1,
overscrollTypeOnce: 2,
overscrollTypeDone: 9,
overscrollVelocityFactor: 300,
// scrollMode 'native'
paginationPageMultiplier: 0.66,
scrollStopWaiting: 200,
scrollWheelPageMultiplierForMaxPixel: 0.2 // The ratio of the maximum distance scrolled by wheel to the size of the viewport.
},
animationDuration = constants.animationDuration,
epsilon = constants.epsilon,
flickConfig = constants.flickConfig,
isPageDown = constants.isPageDown,
isPageUp = constants.isPageUp,
overscrollDefaultRatio = constants.overscrollDefaultRatio,
overscrollTypeDone = constants.overscrollTypeDone,
overscrollTypeHold = constants.overscrollTypeHold,
overscrollTypeNone = constants.overscrollTypeNone,
overscrollTypeOnce = constants.overscrollTypeOnce,
overscrollVelocityFactor = constants.overscrollVelocityFactor,
paginationPageMultiplier = constants.paginationPageMultiplier,
scrollStopWaiting = constants.scrollStopWaiting,
scrollWheelPageMultiplierForMaxPixel = constants.scrollWheelPageMultiplierForMaxPixel;
var TouchableDiv = (0, _Touchable["default"])(function (_ref) {
var ref = _ref.ref,
rest = _objectWithoutProperties(_ref, _excluded);
return /*#__PURE__*/(0, _jsxRuntime.jsx)("div", _objectSpread(_objectSpread({}, rest), {}, {
ref: ref
}));
});
var useForceUpdate = function useForceUpdate() {
return (0, _react.useReducer)(function (x) {
return x + 1;
}, 0);
};
/**
* A custom hook that passes scrollable behavior information as its render prop.
*
* @class
* @memberof ui/useScroll
* @ui
* @private
*/
var useScrollBase = exports.useScrollBase = function useScrollBase(props) {
var childProps = props.childProps,
children = props.children,
className = props.className,
clientSize = props.clientSize,
assignProperties = props.assignProperties,
dataSize = props.dataSize,
direction = props.direction,
horizontalScrollbar = props.horizontalScrollbar,
horizontalScrollbarHandle = props.horizontalScrollbarHandle,
itemRenderer = props.itemRenderer,
itemSize = props.itemSize,
itemSizes = props.itemSizes,
noScrollByDrag = props.noScrollByDrag,
noScrollByWheel = props.noScrollByWheel,
overhang = props.overhang,
overscrollEffectOn = props.overscrollEffectOn,
pageScroll = props.pageScroll,
role = props.role,
rtl = props.rtl,
scrollContainerRef = props.scrollContainerRef,
scrollContentHandle = props.scrollContentHandle,
scrollContentRef = props.scrollContentRef,
scrollMode = props.scrollMode,
setScrollContainerHandle = props.setScrollContainerHandle,
snapToCenter = props.snapToCenter,
spacing = props.spacing,
spotlightContainerDisabled = props.spotlightContainerDisabled,
verticalScrollbar = props.verticalScrollbar,
verticalScrollbarHandle = props.verticalScrollbarHandle,
wrap = props.wrap,
rest = _objectWithoutProperties(props, _excluded2),
scrollClasses = (0, _classnames["default"])(_useScrollModule["default"].scroll, className);
// The following props are the one having the same naming function in this scope.
// So it is better to use props[propName]
// instead of extracting it from the `props` and renaming it
delete rest.addEventListeners;
delete rest.applyOverscrollEffect;
delete rest.cbScrollTo;
delete rest.clearOverscrollEffect;
delete rest.handleResizeWindow;
delete rest.onFlick;
delete rest.onKeyDown;
delete rest.onMouseDown;
delete rest.onScroll;
delete rest.onScrollStart;
delete rest.onScrollStop;
delete rest.onWheel;
delete rest.preventScroll; // scrollMode 'native'
delete rest.removeEventListeners;
delete rest.scrollStopOnScroll; // scrollMode 'native'
delete rest.scrollTo;
delete rest.start; // scrollMode 'native'
delete rest.stop; // scrollMode 'translate'
// Mutable value and Hooks
var _useForceUpdate = useForceUpdate(),
_useForceUpdate2 = _slicedToArray(_useForceUpdate, 2),
forceUpdate = _useForceUpdate2[1];
var context = (0, _react.use)(_Resizable.ResizeContext);
var _useState = (0, _react.useState)(horizontalScrollbar === 'visible'),
_useState2 = _slicedToArray(_useState, 2),
isHorizontalScrollbarVisible = _useState2[0],
setIsHorizontalScrollbarVisible = _useState2[1];
var _useState3 = (0, _react.useState)(verticalScrollbar === 'visible'),
_useState4 = _slicedToArray(_useState3, 2),
isVerticalScrollbarVisible = _useState4[0],
setIsVerticalScrollbarVisible = _useState4[1];
var mutableRef = (0, _react.useRef)({
overscrollEnabled: !!props.applyOverscrollEffect,
// Enable the early bail out of repeated scrolling to the same position
animationInfo: null,
resizeRegistry: null,
// constants
pixelPerLine: 39,
scrollWheelMultiplierForDeltaPixel: 1.5,
// The ratio of wheel 'delta' units to pixels scrolled.
// status
deferScrollTo: true,
isScrollAnimationTargetAccumulated: false,
rtl: rtl,
// overscroll
lastInputType: null,
overscrollStatus: {
horizontal: {
before: {
type: overscrollTypeNone,
ratio: 0
},
after: {
type: overscrollTypeNone,
ratio: 0
}
},
vertical: {
before: {
type: overscrollTypeNone,
ratio: 0
},
after: {
type: overscrollTypeNone,
ratio: 0
}
}
},
// bounds info
bounds: {
clientWidth: 0,
clientHeight: 0,
scrollWidth: 0,
scrollHeight: 0,
maxTop: 0,
maxLeft: 0
},
// wheel/drag/flick info
wheelDirection: 0,
isDragging: false,
isTouching: false,
// scrollMode 'native'
// scroll info
scrolling: false,
scrollLeft: 0,
scrollTop: 0,
scrollToInfo: null,
// scroll animator
animator: null,
// scroll status observer
observerOnScroll: [],
// non-declared-variable.
accumulatedTargetX: null,
accumulatedTargetY: null,
flickTarget: null,
dragStartX: null,
dragStartY: null,
scrollStopJob: null,
prevState: {
isHorizontalScrollbarVisible: isHorizontalScrollbarVisible,
isVerticalScrollbarVisible: isVerticalScrollbarVisible
}
});
/* istanbul ignore next */
var themeScrollContainerHandle = (0, _react.useRef)({
get animator() {
return mutableRef.current.animator;
},
get bounds() {
return mutableRef.current.bounds;
},
get isDragging() {
return mutableRef.current.isDragging;
},
set isDragging(val) {
mutableRef.current.isDragging = val;
},
get isScrollAnimationTargetAccumulated() {
return mutableRef.current.isScrollAnimationTargetAccumulated;
},
set isScrollAnimationTargetAccumulated(val) {
mutableRef.current.isScrollAnimationTargetAccumulated = val;
},
get lastInputType() {
return mutableRef.current.lastInputType;
},
set lastInputType(val) {
mutableRef.current.lastInputType = val;
},
get rtl() {
return mutableRef.current.rtl;
},
get scrollBounds() {
return getScrollBounds();
},
get scrollHeight() {
return mutableRef.current.bounds.scrollHeight;
},
get scrolling() {
return mutableRef.current.scrolling;
},
get scrollLeft() {
return mutableRef.current.scrollLeft;
},
get scrollToInfo() {
return mutableRef.current.scrollToInfo;
},
get scrollTop() {
return mutableRef.current.scrollTop;
},
get wheelDirection() {
return mutableRef.current.wheelDirection;
},
set wheelDirection(val) {
mutableRef.current.wheelDirection = val;
}
});
if (mutableRef.current.animator == null) {
mutableRef.current.animator = new _ScrollAnimator["default"]();
}
if (mutableRef.current.rtl !== rtl) {
mutableRef.current.rtl = rtl;
}
(0, _react.useLayoutEffect)(function () {
/* istanbul ignore next */
if (setScrollContainerHandle) {
Object.assign(themeScrollContainerHandle.current, {
applyOverscrollEffect: applyOverscrollEffect,
calculateDistanceByWheel: calculateDistanceByWheel,
canScrollHorizontally: canScrollHorizontally,
canScrollVertically: canScrollVertically,
checkAndApplyOverscrollEffect: checkAndApplyOverscrollEffect,
getScrollBounds: getScrollBounds,
scrollTo: scrollTo,
scrollToAccumulatedTarget: scrollToAccumulatedTarget,
setOverscrollStatus: setOverscrollStatus,
showScrollbarTrack: showScrollbarTrack,
start: start,
startHidingScrollbarTrack: startHidingScrollbarTrack,
stop: stop
});
setScrollContainerHandle(themeScrollContainerHandle.current);
}
});
(0, _react.useLayoutEffect)(function () {
if (props.cbScrollTo) {
props.cbScrollTo(scrollTo);
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
(0, _react.useLayoutEffect)(function () {
var containerRef = scrollContainerRef.current;
if (!containerRef) {
return;
}
if (typeof ResizeObserver === 'function') {
var resizeObserver = new ResizeObserver(function () {
if (scrollContentHandle.current && scrollContentHandle.current.syncClientSize) {
scrollContentHandle.current.syncClientSize();
}
});
resizeObserver.observe(containerRef);
return function () {
if (resizeObserver) {
resizeObserver.disconnect();
resizeObserver = null;
}
};
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
(0, _react.useLayoutEffect)(function () {
mutableRef.current.resizeRegistry.parent = context;
// componentWillUnmount
return function () {
var _mutableRef$current = mutableRef.current,
animator = _mutableRef$current.animator,
resizeRegistry = _mutableRef$current.resizeRegistry,
scrolling = _mutableRef$current.scrolling,
scrollStopJob = _mutableRef$current.scrollStopJob; // eslint-disable-line react-hooks/exhaustive-deps
resizeRegistry.parent = null;
// Before call cancelAnimationFrame, you must send scrollStop Event.
if (scrolling) {
forwardScrollEvent('onScrollStop', getReachedEdgeInfo());
}
scrollStopJob.stop();
// scrollMode 'translate' [
if (animator.isAnimating()) {
animator.stop();
}
// scrollMode 'translate' ]
};
}, []); // eslint-disable-line react-hooks/exhaustive-deps
(0, _react.useEffect)(function () {
addEventListeners();
return function () {
removeEventListeners();
};
});
// scrollMode 'translate' [[
// TODO: consider replacing forceUpdate() by storing bounds in state rather than a non-
// state member.
var enqueueForceUpdate = (0, _react.useCallback)(function () {
scrollContentHandle.current.calculateMetrics(scrollContentHandle.current.props);
forceUpdate();
}, [forceUpdate, scrollContentHandle]);
// scrollMode 'translate' ]]
function handleResizeWindow() {
var propsHandleResizeWindow = props.handleResizeWindow;
// `handleSize` in `ui/resolution.ResolutionDecorator` should be executed first.
setTimeout(function () {
var handleResizeResult = propsHandleResizeWindow === null || propsHandleResizeWindow === void 0 ? void 0 : propsHandleResizeWindow();
if (handleResizeResult) {
if (scrollMode === 'translate') {
scrollTo({
position: {
x: 0,
y: 0
},
animate: false
});
} else {
scrollContentHandle.current.scrollToPosition(0, 0, 'instant');
}
}
enqueueForceUpdate();
});
}
// scrollMode 'translate' [[
var handleResize = (0, _react.useCallback)(function (ev) {
if (ev.action === 'invalidateBounds') {
enqueueForceUpdate();
}
}, [enqueueForceUpdate]);
// scrollMode 'translate' ]]
if (mutableRef.current.resizeRegistry == null) {
mutableRef.current.resizeRegistry = _Registry["default"].create(handleResize);
}
(0, _react.useLayoutEffect)(function () {
var ref = mutableRef.current;
if (scrollMode === 'translate') {
ref.scrollStopJob = new _util.Job(doScrollStop, scrollStopWaiting);
} else {
ref.scrollStopJob = new _util.Job(scrollStopOnScroll, scrollStopWaiting);
}
return function () {
if (ref.scrolling) {
ref.scrollStopJob.run();
}
ref.scrollStopJob.stop();
};
}, [direction, isHorizontalScrollbarVisible, isVerticalScrollbarVisible, rtl, scrollMode, spotlightContainerDisabled]); // eslint-disable-line react-hooks/exhaustive-deps
(0, _react.useLayoutEffect)(function () {
var hasDataSizeChanged = scrollContentHandle.current.hasDataSizeChanged,
_mutableRef$current2 = mutableRef.current,
prevState = _mutableRef$current2.prevState,
resizeRegistry = _mutableRef$current2.resizeRegistry,
scrollToInfo = _mutableRef$current2.scrollToInfo;
// Need to sync calculated client size if it is different from the real size
if (scrollContentHandle.current.syncClientSize) {
// If we actually synced, we need to reset scroll position.
if (scrollContentHandle.current.syncClientSize()) {
setScrollLeft(0);
setScrollTop(0);
}
}
clampScrollPosition(); // scrollMode 'translate'
if (hasDataSizeChanged === false && (isHorizontalScrollbarVisible && !prevState.isHorizontalScrollbarVisible || isVerticalScrollbarVisible && !prevState.isVerticalScrollbarVisible)) {
mutableRef.current.deferScrollTo = false;
updateScrollbarTrackSize();
} else {
updateScrollbars();
}
if (scrollToInfo !== null) {
if (!mutableRef.current.deferScrollTo) {
scrollTo(scrollToInfo);
}
}
// publish container resize changes
var horizontal = isHorizontalScrollbarVisible !== prevState.isHorizontalScrollbarVisible;
var vertical = isVerticalScrollbarVisible !== prevState.isVerticalScrollbarVisible;
if (horizontal || vertical) {
resizeRegistry.notify({});
}
});
// scrollMode 'translate' [[
function clampScrollPosition() {
var bounds = getScrollBounds();
if (mutableRef.current.scrollTop > bounds.maxTop) {
mutableRef.current.scrollTop = bounds.maxTop;
}
if (mutableRef.current.scrollLeft > bounds.maxLeft) {
mutableRef.current.scrollLeft = bounds.maxLeft;
}
}
// scrollMode 'translate' ]]
// drag/flick event handlers
function getRtlX(x) {
return rtl ? -x : x;
}
function onMouseDown(ev) {
if (snapToCenter) {
ev.preventDefault();
}
if ((0, _handle.forwardWithPrevent)('onMouseDown', ev, props) && !snapToCenter) {
stop();
}
}
// scrollMode 'native' [[
function onTouchStart() {
mutableRef.current.isTouching = true;
}
// scrollMode 'native' ]]
function onDragStart(ev) {
if (scrollMode === 'translate') {
if (ev.type === 'dragstart') return;
(0, _handle.forward)('onDragStart', ev, props);
stop();
mutableRef.current.isDragging = true;
mutableRef.current.dragStartX = mutableRef.current.scrollLeft + getRtlX(ev.x);
mutableRef.current.dragStartY = mutableRef.current.scrollTop + ev.y;
} else {
if (!mutableRef.current.isTouching) {
stop();
mutableRef.current.isDragging = true;
}
// these values are used also for touch inputs
mutableRef.current.dragStartX = mutableRef.current.scrollLeft + getRtlX(ev.x);
mutableRef.current.dragStartY = mutableRef.current.scrollTop + ev.y;
}
}
function onDrag(ev) {
if (scrollMode === 'translate') {
if (ev.type === 'drag') {
return;
}
mutableRef.current.lastInputType = 'drag';
(0, _handle.forward)('onDrag', ev, props);
start({
targetX: direction === 'vertical' ? 0 : mutableRef.current.dragStartX - getRtlX(ev.x),
// 'horizontal' or 'both'
targetY: direction === 'horizontal' ? 0 : mutableRef.current.dragStartY - ev.y,
// 'vertical' or 'both'
animate: false,
overscrollEffect: overscrollEffectOn && overscrollEffectOn.drag
});
} else {
var targetX = direction === 'vertical' ? 0 : mutableRef.current.dragStartX - getRtlX(ev.x),
// 'horizontal' or 'both'
targetY = direction === 'horizontal' ? 0 : mutableRef.current.dragStartY - ev.y; // 'vertical' or 'both'
mutableRef.current.lastInputType = 'drag';
if (!mutableRef.current.isTouching) {
start({
targetX: targetX,
targetY: targetY,
animate: false,
overscrollEffect: overscrollEffectOn && overscrollEffectOn.drag
});
} else if (mutableRef.current.overscrollEnabled && overscrollEffectOn && overscrollEffectOn.drag) {
checkAndApplyOverscrollEffectOnDrag(targetX, targetY, overscrollTypeHold);
}
}
}
function onDragEnd(ev) {
if (scrollMode === 'translate') {
if (ev.type === 'dragend') {
return;
}
mutableRef.current.isDragging = false;
(0, _handle.forward)('onDragEnd', ev, props);
if (mutableRef.current.flickTarget) {
var _mutableRef$current$f = mutableRef.current.flickTarget,
targetX = _mutableRef$current$f.targetX,
targetY = _mutableRef$current$f.targetY,
duration = _mutableRef$current$f.duration;
mutableRef.current.lastInputType = 'drag';
mutableRef.current.isScrollAnimationTargetAccumulated = false;
start({
targetX: targetX,
targetY: targetY,
duration: duration,
overscrollEffect: overscrollEffectOn && overscrollEffectOn.drag
});
} else {
stop();
}
if (mutableRef.current.overscrollEnabled) {
// not check overscrollEffectOn && overscrollEffectOn.drag for safety
clearAllOverscrollEffects();
}
mutableRef.current.flickTarget = null;
} else {
mutableRef.current.isDragging = false;
mutableRef.current.lastInputType = 'drag';
if (mutableRef.current.flickTarget) {
var _mutableRef$current$f2 = mutableRef.current.flickTarget,
_targetX = _mutableRef$current$f2.targetX,
_targetY = _mutableRef$current$f2.targetY;
if (!mutableRef.current.isTouching) {
mutableRef.current.isScrollAnimationTargetAccumulated = false;
start({
targetX: _targetX,
targetY: _targetY,
overscrollEffect: overscrollEffectOn && overscrollEffectOn.drag
});
} else if (mutableRef.current.overscrollEnabled && overscrollEffectOn && overscrollEffectOn.drag) {
checkAndApplyOverscrollEffectOnDrag(_targetX, _targetY, overscrollTypeOnce);
}
} else if (!mutableRef.current.isTouching) {
stop();
}
if (mutableRef.current.overscrollEnabled) {
// not check overscrollEffectOn && overscrollEffectOn.drag for safety
clearAllOverscrollEffects();
}
mutableRef.current.isTouching = false;
mutableRef.current.flickTarget = null;
}
}
function onFlick(ev) {
var isVerticalFlick = ev.direction === 'vertical';
if (scrollMode === 'translate' || !mutableRef.current.isTouching) {
// except touch input in 'native' mode
mutableRef.current.flickTarget = mutableRef.current.animator.simulate(mutableRef.current.scrollLeft, mutableRef.current.scrollTop, direction !== 'vertical' && !isVerticalFlick ? getRtlX(-ev.velocityX) : 0, direction !== 'horizontal' && isVerticalFlick ? -ev.velocityY : 0);
} else if (mutableRef.current.overscrollEnabled && overscrollEffectOn && overscrollEffectOn.drag) {
// overscroll is required on touch input in 'native' mode
mutableRef.current.flickTarget = {
targetX: mutableRef.current.scrollLeft + (!isVerticalFlick ? getRtlX(-ev.velocityX) : 0) * overscrollVelocityFactor,
// 'horizontal' or 'both'
targetY: mutableRef.current.scrollTop + (isVerticalFlick ? -ev.velocityY : 0) * overscrollVelocityFactor // 'vertical' or 'both'
};
}
if (props.onFlick) {
(0, _handle.forward)('onFlick', ev, props);
}
}
function calculateDistanceByWheel(deltaMode, delta, maxPixel) {
if (deltaMode === 0) {
delta = (0, _clamp["default"])(-maxPixel, maxPixel, _resolution["default"].scale(delta * mutableRef.current.scrollWheelMultiplierForDeltaPixel));
} else if (deltaMode === 1) {
// line; firefox
delta = (0, _clamp["default"])(-maxPixel, maxPixel, _resolution["default"].scale(delta * mutableRef.current.pixelPerLine * mutableRef.current.scrollWheelMultiplierForDeltaPixel));
} else if (deltaMode === 2) {
// page
delta = delta < 0 ? -maxPixel : maxPixel;
}
return delta;
}
/*
* wheel event handler;
* - for horizontal scroll, supports wheel action on any children nodes since web engine cannot support this
* - for vertical scroll, supports wheel action on scrollbars only
*/
function onWheel(ev) {
if (mutableRef.current.isDragging) {
ev.preventDefault();
ev.stopPropagation();
} else {
var bounds = getScrollBounds(),
canScrollH = canScrollHorizontally(bounds),
canScrollV = canScrollVertically(bounds),
eventDeltaMode = ev.deltaMode,
eventDelta = -ev.wheelDeltaY || ev.deltaY;
var delta = 0;
mutableRef.current.lastInputType = 'wheel';
if (noScrollByWheel) {
if (scrollMode === 'native' && canScrollV) {
ev.preventDefault();
}
return;
}
if (snapToCenter) {
if (scrollMode === 'native' && (canScrollV || canScrollH)) {
ev.preventDefault();
(0, _handle.forward)('onWheel', ev, props);
return;
}
}
if (scrollMode === 'translate') {
if (canScrollV) {
delta = calculateDistanceByWheel(eventDeltaMode, eventDelta, bounds.clientHeight * scrollWheelPageMultiplierForMaxPixel);
} else if (canScrollH) {
delta = calculateDistanceByWheel(eventDeltaMode, eventDelta, bounds.clientWidth * scrollWheelPageMultiplierForMaxPixel);
}
var dir = Math.sign(delta);
if (dir !== mutableRef.current.wheelDirection) {
mutableRef.current.isScrollAnimationTargetAccumulated = false;
mutableRef.current.wheelDirection = dir;
}
(0, _handle.forward)('onWheel', {
delta: delta,
horizontalScrollbarHandle: horizontalScrollbarHandle,
verticalScrollbarHandle: verticalScrollbarHandle
}, props);
if (delta !== 0) {
scrollToAccumulatedTarget(delta, canScrollV, overscrollEffectOn && overscrollEffectOn.wheel);
ev.preventDefault();
ev.stopPropagation();
}
} else {
// scrollMode 'native'
var overscrollEffectRequired = mutableRef.current.overscrollEnabled && overscrollEffectOn && overscrollEffectOn.wheel;
var needToHideScrollbarTrack = false;
if (props.onWheel) {
(0, _handle.forward)('onWheel', ev, props);
return;
}
showScrollbarTrack(bounds);
// FIXME This routine is a temporary support for horizontal wheel scroll.
// FIXME If web engine supports horizontal wheel, this routine should be refined or removed.
if (canScrollV) {
// This routine handles wheel events on scrollbars for vertical scroll.
if (eventDelta < 0 && mutableRef.current.scrollTop > 0 || eventDelta > 0 && mutableRef.current.scrollTop < bounds.maxTop) {
// Not to check if ev.target is a descendant of a wrapped component which may have a lot of nodes in it.
if (horizontalScrollbarHandle.current && horizontalScrollbarHandle.current.getContainerRef && _utilDOM["default"].containsDangerously(horizontalScrollbarHandle.current.getContainerRef(), ev.target) || verticalScrollbarHandle.current && verticalScrollbarHandle.current.getContainerRef && _utilDOM["default"].containsDangerously(verticalScrollbarHandle.current.getContainerRef(), ev.target)) {
delta = calculateDistanceByWheel(eventDeltaMode, eventDelta, bounds.clientHeight * scrollWheelPageMultiplierForMaxPixel);
needToHideScrollbarTrack = !delta;
ev.preventDefault();
} else if (overscrollEffectRequired) {
checkAndApplyOverscrollEffect('vertical', eventDelta > 0 ? 'after' : 'before', overscrollTypeOnce);
}
ev.stopPropagation();
} else {
if (overscrollEffectRequired && (eventDelta < 0 && mutableRef.current.scrollTop <= 0 || eventDelta > 0 && mutableRef.current.scrollTop >= bounds.maxTop)) {
applyOverscrollEffect('vertical', eventDelta > 0 ? 'after' : 'before', overscrollTypeOnce);
}
needToHideScrollbarTrack = true;
}
} else if (canScrollH) {
// this routine handles wheel events on any children for horizontal scroll.
if (eventDelta < 0 && mutableRef.current.scrollLeft > 0 || eventDelta > 0 && mutableRef.current.scrollLeft < bounds.maxLeft) {
delta = calculateDistanceByWheel(eventDeltaMode, eventDelta, bounds.clientWidth * scrollWheelPageMultiplierForMaxPixel);
needToHideScrollbarTrack = !delta;
ev.preventDefault();
ev.stopPropagation();
} else {
if (overscrollEffectRequired && (eventDelta < 0 && mutableRef.current.scrollLeft <= 0 || eventDelta > 0 && mutableRef.current.scrollLeft >= bounds.maxLeft)) {
applyOverscrollEffect('horizontal', eventDelta > 0 ? 'after' : 'before', overscrollTypeOnce);
}
needToHideScrollbarTrack = true;
}
}
if (delta !== 0) {
var _dir = Math.sign(delta);
// Not to accumulate scroll position if wheel direction is different from hold direction
if (_dir !== mutableRef.current.wheelDirection) {
mutableRef.current.isScrollAnimationTargetAccumulated = false;
mutableRef.current.wheelDirection = _dir;
}
scrollToAccumulatedTarget(delta, canScrollV, overscrollEffectOn && overscrollEffectOn.wheel);
}
if (needToHideScrollbarTrack) {
startHidingScrollbarTrack();
}
}
}
}
// scrollMode 'translate' [[
function scrollByPage(keyCode) {
var bounds = getScrollBounds(),
canScrollV = canScrollVertically(bounds),
pageDistance = (isPageUp(keyCode) ? -1 : 1) * (canScrollV ? bounds.clientHeight : bounds.clientWidth) * paginationPageMultiplier;
mutableRef.current.lastInputType = 'pageKey';
scrollToAccumulatedTarget(pageDistance, canScrollV, overscrollEffectOn && overscrollEffectOn.pageKey);
}
// scrollMode 'translate' ]]
// scrollMode 'native' [[
function onScroll(ev) {
var _ev$target = ev.target,
scrollLeft = _ev$target.scrollLeft,
scrollTop = _ev$target.scrollTop;
var bounds = getScrollBounds(),
canScrollH = canScrollHorizontally(bounds);
if (!mutableRef.current.scrolling) {
scrollStartOnScroll();
}
if (rtl && canScrollH) {
scrollLeft = _platform.platform.chrome < 85 ? bounds.maxLeft - scrollLeft : -scrollLeft;
}
if (scrollLeft !== mutableRef.current.scrollLeft) {
setScrollLeft(scrollLeft);
}
if (scrollTop !== mutableRef.current.scrollTop) {
setScrollTop(scrollTop);
}
if (scrollContentHandle.current.didScroll) {
scrollContentHandle.current.didScroll(mutableRef.current.scrollLeft, mutableRef.current.scrollTop);
}
forwardScrollEvent('onScroll');
mutableRef.current.scrollStopJob.start();
}
// scrollMode 'native' ]]
function onKeyDown(ev) {
if (scrollMode === 'translate') {
if (props.onKeyDown) {
(0, _handle.forward)('onKeyDown', ev, props);
} else if (isPageUp(ev.keyCode) || isPageDown(ev.keyCode)) {
scrollByPage(ev.keyCode);
}
} else {
var _props$preventScroll;
(_props$preventScroll = props.preventScroll) === null || _props$preventScroll === void 0 || _props$preventScroll.call(props, ev);
(0, _handle.forward)('onKeyDown', ev, props);
}
}
function scrollToAccumulatedTarget(delta, vertical, overscrollEffect) {
if (!mutableRef.current.isScrollAnimationTargetAccumulated) {
mutableRef.current.accumulatedTargetX = mutableRef.current.scrollLeft;
mutableRef.current.accumulatedTargetY = mutableRef.current.scrollTop;
mutableRef.current.isScrollAnimationTargetAccumulated = true;
}
if (vertical) {
mutableRef.current.accumulatedTargetY += delta;
} else {
mutableRef.current.accumulatedTargetX += delta;
}
start({
targetX: mutableRef.current.accumulatedTargetX,
targetY: mutableRef.current.accumulatedTargetY,
overscrollEffect: overscrollEffect
});
}
// overscroll effect
function getEdgeFromPosition(position, maxPosition) {
if (position <= 0) {
return 'before';
} else if (position >= maxPosition - epsilon) {
return 'after';
} else {
return null;
}
}
function setOverscrollStatus(orientation, edge, overscrollEffectType, ratio) {
var status = mutableRef.current.overscrollStatus[orientation][edge];
status.type = overscrollEffectType;
status.ratio = ratio;
}
function getOverscrollStatus(orientation, edge) {
return mutableRef.current.overscrollStatus[orientation][edge];
}
function calculateOverscrollRatio(orientation, position) {
var bounds = getScrollBounds(),
isVertical = orientation === 'vertical',
baseSize = isVertical ? bounds.clientHeight : bounds.clientWidth,
maxPos = bounds[isVertical ? 'maxTop' : 'maxLeft'];
var overDistance = 0;
if (position < 0) {
overDistance = -position;
} else if (position > maxPos) {
overDistance = position - maxPos;
} else {
return 0;
}
return Math.min(1, 2 * overDistance / baseSize);
}
function applyOverscrollEffect(orientation, edge, overscrollEffectType) {
var ratio = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : overscrollDefaultRatio;
props.applyOverscrollEffect(orientation, edge, overscrollEffectType, ratio);
setOverscrollStatus(orientation, edge, overscrollEffectType === overscrollTypeOnce ? overscrollTypeDone : overscrollEffectType, ratio);
}
function checkAndApplyOverscrollEffect(orientation, edge, overscrollEffectType) {
var ratio = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : overscrollDefaultRatio;
var isVertical = orientation === 'vertical',
curPos = isVertical ? mutableRef.current.scrollTop : mutableRef.current.scrollLeft,
maxPos = getScrollBounds()[isVertical ? 'maxTop' : 'maxLeft'];
if (edge === 'before' && curPos <= 0 || edge === 'after' && curPos >= maxPos - epsilon) {
// Already on the edge
applyOverscrollEffect(orientation, edge, overscrollEffectType, ratio);
} else {
setOverscrollStatus(orientation, edge, overscrollEffectType, ratio);
}
}
function clearOverscrollEffect(orientation, edge) {
if (getOverscrollStatus(orientation, edge).type !== overscrollTypeNone) {
if (props.clearOverscrollEffect) {
props.clearOverscrollEffect(orientation, edge);
} else {
applyOverscrollEffect(orientation, edge, overscrollTypeNone, 0);
}
}
}
function clearAllOverscrollEffects() {
['horizontal', 'vertical'].forEach(function (orientation) {
['before', 'after'].forEach(function (edge) {
clearOverscrollEffect(orientation, edge);
});
});
}
function applyOverscrollEffectOnDrag(orientation, edge, targetPosition, overscrollEffectType) {
if (edge) {
var oppositeEdge = edge === 'before' ? 'after' : 'before',
ratio = calculateOverscrollRatio(orientation, targetPosition);
applyOverscrollEffect(orientation, edge, overscrollEffectType, ratio);
clearOverscrollEffect(orientation, oppositeEdge);
} else {
clearOverscrollEffect(orientation, 'before');
clearOverscrollEffect(orientation, 'after');
}
}
// scrollMode 'native' [[
function checkAndApplyOverscrollEffectOnDrag(targetX, targetY, overscrollEffectType) {
var bounds = getScrollBounds();
if (canScrollHorizontally(bounds)) {
applyOverscrollEffectOnDrag('horizontal', getEdgeFromPosition(targetX, bounds.maxLeft), targetX, overscrollEffectType);
}
if (canScrollVertically(bounds)) {
applyOverscrollEffectOnDrag('vertical', getEdgeFromPosition(targetY, bounds.maxTop), targetY, overscrollEffectType);
}
}
// scrollMode 'native' ]]
function checkAndApplyOverscrollEffectOnScroll(orientation) {
['before', 'after'].forEach(function (edge) {
var _getOverscrollStatus = getOverscrollStatus(orientation, edge),
ratio = _getOverscrollStatus.ratio,
overscrollEffectType = _getOverscrollStatus.type;
if (overscrollEffectType === overscrollTypeOnce) {
checkAndApplyOverscrollEffect(orientation, edge, overscrollEffectType, ratio);
}
});
}
function checkAndApplyOverscrollEffectOnStart(orientation, edge, targetPosition) {
if (mutableRef.current.isDragging) {
applyOverscrollEffectOnDrag(orientation, edge, targetPosition, overscrollTypeHold);
} else if (edge) {
checkAndApplyOverscrollEffect(orientation, edge, overscrollTypeOnce);
}
}
// call scroll callbacks
var addObserverOnScroll = (0, _react.useCallback)(function (fn) {
var observerOnScroll = mutableRef.current.observerOnScroll;
if (typeof fn === 'function' && !observerOnScroll.includes(fn)) {
observerOnScroll.push(fn);
}
}, []);
var removeObserverOnScroll = (0, _react.useCallback)(function (fn) {
var observerOnScroll = mutableRef.current.observerOnScroll;
var index = observerOnScroll.indexOf(fn);
if (index !== -1) {
observerOnScroll.splice(index, 1);
}
}, []);
function forwardScrollEvent(type, reachedEdgeInfo) {
var data = {
scrollLeft: mutableRef.current.scrollLeft,
scrollTop: mutableRef.current.scrollTop,
moreInfo: getMoreInfo(),
reachedEdgeInfo: reachedEdgeInfo
};
(0, _handle.forward)(type, data, props);
if (type === 'onScroll') {
mutableRef.current.observerOnScroll.forEach(function (fn) {
return fn(data);
});
}
}
// scrollMode 'native' [[
// call scroll callbacks and update scrollbars for native scroll
function scrollStartOnScroll() {
mutableRef.current.scrolling = true;
showScrollbarTrack(getScrollBounds());
forwardScrollEvent('onScrollStart');
}
function scrollStopOnScroll() {
if (props.scrollStopOnScroll) {
props.scrollStopOnScroll();
}
if (mutableRef.current.overscrollEnabled && !mutableRef.current.isDragging) {
// not check overscrollEffectOn && overscrollEffectOn for safety
clearAllOverscrollEffects();
}
mutableRef.current.lastInputType = null;
mutableRef.current.isScrollAnimationTargetAccumulated = false;
mutableRef.current.scrolling = false;
forwardScrollEvent('onScrollStop', getReachedEdgeInfo());
startHidingScrollbarTrack();
}
// scrollMode 'native' ]]
// update scroll position
function setScrollLeft(value) {
var bounds = getScrollBounds();
mutableRef.current.scrollLeft = (0, _clamp["default"])(0, bounds.maxLeft, value);
if (mutableRef.current.overscrollEnabled && overscrollEffectOn && overscrollEffectOn[mutableRef.current.lastInputType]) {
checkAndApplyOverscrollEffectOnScroll('horizontal');
}
if (isHorizontalScrollbarVisible) {
updateScrollbarTrack(horizontalScrollbarHandle, bounds);
}
}
function setScrollTop(value) {
var bounds = getScrollBounds();
mutableRef.current.scrollTop = (0, _clamp["default"])(0, bounds.maxTop, value);
if (mutableRef.current.overscrollEnabled && overscrollEffectOn && overscrollEffectOn[mutableRef.current.lastInputType]) {
checkAndApplyOverscrollEffectOnScroll('vertical');
}
if (isVerticalScrollbarVisible) {
updateScrollbarTrack(verticalScrollbarHandle, bounds);
}
}
function getReachedEdgeInfo() {
var bounds = getScrollBounds(),
reachedEdgeInfo = {
bottom: false,
left: false,
right: false,
top: false
};
if (canScrollHorizontally(bounds)) {
var edge = getEdgeFromPosition(mutableRef.current.scrollLeft, bounds.maxLeft);
if (edge) {
// if edge is null, no need to check which edge is reached.
if (edge === 'before' && !rtl || edge === 'after' && rtl) {
reachedEdgeInfo.left = true;
} else {
reachedEdgeInfo.right = true;
}
}
}
if (canScrollVertically(bounds)) {
var _edge = getEdgeFromPosition(mutableRef.current.scrollTop, bounds.maxTop);
if (_edge === 'before') {
reachedEdgeInfo.top = true;
} else if (_edge === 'after') {
reachedEdgeInfo.bottom = true;
}
}
return reachedEdgeInfo;
}
// scroll start/stop
// scrollMode 'translate' [[
function doScrollStop() {
mutableRef.current.scrolling = false;
forwardScrollEvent('onScrollStop', getReachedEdgeInfo());
}
// scrollMode 'translate' ]]
function start(_ref2) {
var targetX = _ref2.targetX,
targetY = _ref2.targetY,
_ref2$animate = _ref2.animate,
animate = _ref2$animate === void 0 ? true : _ref2$animate,
_ref2$duration = _ref2.duration,
duration = _ref2$duration === void 0 ? animationDuration : _ref2$duration,
_ref2$overscrollEffec = _ref2.overscrollEffect,
overscrollEffect = _ref2$overscrollEffec === void 0 ? false : _ref2$overscrollEffec;
var _mutableRef$current3 = mutableRef.current,
scrollLeft = _mutableRef$current3.scrollLeft,
scrollTop = _mutableRef$current3.scrollTop,
bounds = getScrollBounds(),
maxLeft = bounds.maxLeft,
maxTop = bounds.maxTop;
var updatedAnimationInfo = scrollMode === 'translate' ? {
sourceX: scrollLeft,
sourceY: scrollTop,
targetX: targetX,
targetY: targetY,
duration: duration
} : {
targetX: targetX,
targetY: targetY
};
// bail early when scrolling to the same position
if ((scrollMode === 'translate' && mutableRef.current.animator.isAnimating() || scrollMode === 'native' && mutableRef.current.scrolling) && mutableRef.current.animationInfo && mutableRef.current.animationInfo.targetX === targetX && mutableRef.current.animationInfo.targetY === targetY) {
return;
}
mutableRef.current.animationInfo = updatedAnimationInfo;
if (scrollMode === 'translate') {
mutableRef.current.animator.stop();
if (!mutableRef.current.scrolling) {
mutableRef.current.scrolling = true;
forwardScrollEvent('onScrollStart');
}
mutableRef.current.scrollStopJob.stop();
}
if (Math.abs(maxLeft - targetX) < epsilon) {
targetX = maxLeft;
}
if (Math.abs(maxTop - targetY) < epsilon) {
targetY = maxTop;
}
if (mutableRef.current.overscrollEnabled && overscrollEffect) {
if (scrollLeft !== targetX && canScrollHorizontally(bounds)) {
checkAndApplyOverscrollEffectOnStart('horizontal', getEdgeFromPosition(targetX, maxLeft), targetX);
}
if (scrollTop !== targetY && canScrollVertically(bounds)) {
checkAndApplyOverscrollEffectOnStart('vertical', getEdgeFromPosition(targetY, maxTop), targetY);
}
}
if (scrollMode === 'translate') {
showScrollbarTrack(bounds);
if (scrollContentHandle.current && scrollContentHandle.current.setScrollPositionTarget) {
scrollContentHandle.current.setScrollPositionTarget(targetX, targetY);
}
if (animate) {
mutableRef.current.animator.animate(scrollAnimation(mutableRef.current.animationInfo));
} else {
scroll(targetX, targetY);
stop();
}
} else {
// scrollMode 'native'
if (animate) {
scrollContentHandle.current.scrollToPosition(targetX, targetY, 'smooth');
} else {
scrollContentHandle.current.scrollToPosition(targetX, targetY, 'instant');
}
if (props.start) {
props.start(animate);
}
}
}
// scrollMode 'translate' [[
function scrollAnimation(animationInfo) {
return function (curTime) {
var sourceX = animationInfo.sourceX,
sourceY = animationInfo.sourceY,
targetX = animationInfo.targetX,
targetY = animationInfo.targetY,
duration = animationInfo.duration,
bounds = getScrollBounds();
if (curTime < duration) {
var toBeContinued = false,
curTargetX = sourceX,
curTargetY = sourceY;
if (canScrollHorizontally(bounds)) {
curTargetX = mutableRef.current.animator.timingFunction(sourceX, targetX, duration, curTime);
if (Math.abs(curTargetX - targetX) < epsilon) {
curTargetX = targetX;
} else {
toBeContinued = true;
}
}
if (canScrollVertically(bounds)) {
curTargetY = mutableRef.current.animator.timingFunction(sourceY, targetY, duration, curTime);
if (Math.abs(curTargetY - targetY) < epsilon) {
curTargetY = targetY;
} else {
toBeContinued = true;
}
}
scroll(curTargetX, curTargetY);
if (!toBeContinued) {
stop();
}
} else {
scroll(targetX, targetY);
stop();
}
};
}
function scroll(left, top) {
if (left !== mutableRef.current.scrollLeft) {
setScrollLeft(left);
}
if (top !== mutableRef.current.scrollTop) {
setScrollTop(top);
}
scrollContentHandle.current.setScrollPosition(mutableRef.current.scrollLeft, mutableRef.current.scrollTop);
forwardScrollEvent('onScroll');
}
// scrollMode 'translate' ]]
function stopForTranslate() {
mutableRef.current.animator.stop();
mutableRef.current.lastInputType = null;
mutableRef.current.isScrollAnimationTargetAccumulated = false;
startHidingScrollbarTrack();
if (mutableRef.current.overscrollEnabled && !mutableRef.current.isDragging) {
// not check overscrollEffectOn && overscrollEffectOn for safety
clearAllOverscrollEffects();
}
if (props.stop) {
props.stop();
}
if (mutableRef.current.scrolling) {
mutableRef.current.scrollStopJob.start();
}
}
function stopForNative() {
scrollContentHandle.current.scrollToPosition(mutableRef.current.scrollLeft + (rtl ? -0.1 : 0.1), mutableRef.current.scrollTop + 0.1, 'instant');
}
function stop() {
if (scrollMode === 'translate') {
stopForTranslate();
} else {
stopForNative();
}
}
// scrollTo API
function getPositionForScrollTo(opt) {
var bounds = getScrollBounds(),
canScrollH = canScrollHorizontally(bounds),
canScrollV = canScrollVertically(bounds);
var itemPos,
left = null,
top = null;
if (opt instanceof Object) {
if (opt.position instanceof Object) {
if (canScrollH) {
// We need '!=' to check if opt.position.x is null or undefined
left = opt.position.x != null ? opt.position.x : mutableRef.current.scrollLeft;
} else {
left = 0;
}
if (canScrollV) {
// We need '!=' to check if opt.position.y is null or undefined
top = opt.position.y != null ? opt.position.y : mutableRef.current.scrollTop;
} else {
top = 0;
}
} else if (typeof opt.align === 'string') {
if (canScrollH) {
if (opt.align.includes('left')) {
left = 0;
} else if (opt.align.includes('right')) {
left = b