UNPKG

@enact/ui

Version:

A collection of simplified unstyled cross-platform UI components for Enact

1,211 lines (1,158 loc) 66.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = exports.constants = exports.assignPropertiesOf = void 0; exports.roundTarget = roundTarget; exports.useScrollBase = exports.useScroll = 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, minAnimationDuration: 100, 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, minAnimationDuration = constants.minAnimationDuration, 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); }; /* * Rounds the target positions based on scroll direction, if Chrome version is > 120 * Chrome 121+ introduced a new way to calculate target pixel position, based on the subunit value of target position * * @function * @param {Object} currentPosition current scroll position * @param {Number} targetX target X axis position * @param {Number} targetY target Y axis position * * @returns {Object} * @memberof ui/useScroll * @private */ function roundTarget(currentPosition, targetX, targetY) { var roundedTargetX, roundedTargetY; if (currentPosition !== null && currentPosition !== void 0 && currentPosition.scrollPos && _platform.platform.chrome > 120) { var _currentPosition$scro, _currentPosition$scro2; roundedTargetX = (currentPosition === null || currentPosition === void 0 || (_currentPosition$scro = currentPosition.scrollPos) === null || _currentPosition$scro === void 0 ? void 0 : _currentPosition$scro.left) < targetX ? Math.ceil(targetX) : Math.floor(targetX); roundedTargetY = (currentPosition === null || currentPosition === void 0 || (_currentPosition$scro2 = currentPosition.scrollPos) === null || _currentPosition$scro2 === void 0 ? void 0 : _currentPosition$scro2.top) < targetY ? Math.ceil(targetY) : Math.floor(targetY); } else { roundedTargetX = targetX; roundedTargetY = targetY; } return { roundedTargetX: roundedTargetX, roundedTargetY: roundedTargetY }; } /** * 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) { mutableRef.current.keyPressed = ev.repeat; 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 onKeyUp(ev) { mutableRef.current.keyPressed = false; (0, _handle.forward)('onKeyUp', 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' var _roundTarget = roundTarget(scrollContentHandle.current, targetX, targetY), roundedTargetX = _roundTarget.roundedTargetX, roundedTargetY = _roundTarget.roundedTargetY; if (animate) { scrollContentHandle.current.scrollToPosition(roundedTargetX, roundedTargetY, 'smooth'); } else { scrollContentHandle.current.scrollToPosition(roundedTargetX, roundedTargetY, '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(), scrollAnimationDuration = mutableRef.current.keyPressed ? minAnimationDuration : duration; var dynamicScrollDuration = function dynamicScrollDuration(target, source) { var customDuration = Math.max(minAnimationDuration, Math.abs(target - source)); if (mutableRef.current.keyPressed) return minAnimationDuration; return animationDuration !== duration ? duration : Math.min(customDuration, duration); }; if (curTime < scrollAnimationDuration) { var toBeContinued = false, curTargetX = sourceX, curTargetY = sourceY; if (canScrollHorizontally(bounds)) { var scrollDuration = dynamicScrollDuration(sourceX, targetX); curTargetX = mutableRef.current.animator.timingFunction(sourceX, targetX, scrollDuration, curTime, mutableRef.current.keyPressed); if (Math.abs(curTargetX - targetX) < epsilon) { curTargetX = targetX; } else { toBeContinued = true; } } if (canScrollVertically(bounds)) { var _scrollDuration = dynamicScrollDuration(sourceY, targetY); curTargetY = mutableRef.current.animator.timingFunction(sourceY, targetY, _scrollDuration, curTime