UNPKG

@enact/moonstone

Version:

Large-screen/TV support library for Enact, containing a variety of UI components.

1,107 lines (1,088 loc) 59.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = exports.constants = exports.ScrollableNative = exports.ScrollableBaseNative = void 0; var _classnames = _interopRequireDefault(require("classnames")); var _handle = _interopRequireWildcard(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 _Resizable = require("@enact/ui/Resizable"); var _resolution = _interopRequireDefault(require("@enact/ui/resolution")); var _Touchable = _interopRequireDefault(require("@enact/ui/Touchable")); var _propTypes = _interopRequireDefault(require("prop-types")); var _clamp = _interopRequireDefault(require("ramda/src/clamp")); var _react = require("react"); var _UiScrollAnimator = _interopRequireDefault(require("./UiScrollAnimator")); var _UiScrollbar = _interopRequireDefault(require("./UiScrollbar")); var _UiScrollableModule = _interopRequireDefault(require("./UiScrollable.module.css")); var _jsxRuntime = require("react/jsx-runtime"); var _excluded = ["className", "containerRenderer", "noScrollByDrag", "rtl", "style"], _excluded2 = ["childRenderer"]; function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && Object.prototype.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; } function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; } 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(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : String(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 _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf(Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf(this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn(this, result); }; } function _possibleConstructorReturn(self, call) { if (call && (typeof call === "object" || typeof call === "function")) { return call; } else if (call !== void 0) { throw new TypeError("Derived constructors may only return object or undefined"); } return _assertThisInitialized(self); } function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; } function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); } var constants = exports.constants = { epsilon: 1, flickConfig: { maxDuration: null }, isPageDown: (0, _keymap.is)('pageDown'), isPageUp: (0, _keymap.is)('pageUp'), nop: function nop() {}, overscrollTypeNone: 0, overscrollTypeHold: 1, overscrollTypeOnce: 2, overscrollTypeDone: 9, overscrollVelocityFactor: 300, scrollStopWaiting: 200, scrollWheelPageMultiplierForMaxPixel: 0.2 // The ratio of the maximum distance scrolled by wheel to the size of the viewport. }, epsilon = constants.epsilon, flickConfig = constants.flickConfig, nop = constants.nop, overscrollTypeDone = constants.overscrollTypeDone, overscrollTypeHold = constants.overscrollTypeHold, overscrollTypeNone = constants.overscrollTypeNone, overscrollTypeOnce = constants.overscrollTypeOnce, overscrollVelocityFactor = constants.overscrollVelocityFactor, scrollStopWaiting = constants.scrollStopWaiting, scrollWheelPageMultiplierForMaxPixel = constants.scrollWheelPageMultiplierForMaxPixel; var TouchableDiv = (0, _Touchable["default"])('div'); /** * An unstyled native component that passes scrollable behavior information as its render prop's arguments. * * @class ScrollableBaseNative * @memberof moonstone/UiScrollableNative * @ui * @private */ var ScrollableBaseNative = exports.ScrollableBaseNative = /*#__PURE__*/function (_Component) { _inherits(ScrollableBaseNative, _Component); var _super = _createSuper(ScrollableBaseNative); function ScrollableBaseNative(props) { var _this; _classCallCheck(this, ScrollableBaseNative); _this = _super.call(this, props); _this.handleResizeWindow = function () { // `handleSize` in `ui/resolution.ResolutionDecorator` should be executed first. setTimeout(function () { var handleResizeWindow = _this.props.handleResizeWindow; if (handleResizeWindow) { handleResizeWindow(); } _this.childRefCurrent.containerRef.current.style.scrollBehavior = null; _this.childRefCurrent.scrollToPosition(0, 0); _this.childRefCurrent.containerRef.current.style.scrollBehavior = 'smooth'; _this.enqueueForceUpdate(); }); }; // constants _this.pixelPerLine = 39; _this.scrollWheelMultiplierForDeltaPixel = 1.5; // The ratio of wheel 'delta' units to pixels scrolled. // status _this.deferScrollTo = true; _this.isScrollAnimationTargetAccumulated = false; _this.isUpdatedScrollThumb = false; // overscroll _this.lastInputType = null; _this.overscrollEnabled = false; _this.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 _this.bounds = { clientWidth: 0, clientHeight: 0, scrollWidth: 0, scrollHeight: 0, maxTop: 0, maxLeft: 0 }; // wheel/drag/flick info _this.wheelDirection = 0; _this.isDragging = false; _this.isTouching = false; // scroll info _this.scrolling = false; _this.scrollLeft = 0; _this.scrollTop = 0; _this.scrollToInfo = null; // component info _this.childRefCurrent = null; // scroll animator _this.animator = new _UiScrollAnimator["default"](); // event handler for browser native scroll _this.getRtlX = function (x) { return _this.props.rtl ? -x : x; }; _this.onMouseDown = (0, _handle["default"])((0, _handle.forwardWithPrevent)('onMouseDown'), _this.stop).bindAs(_assertThisInitialized(_this), 'onMouseDown'); _this.onTouchStart = function () { _this.isTouching = true; }; _this.onDragStart = function (ev) { if (!_this.isTouching) { _this.stop(); _this.isDragging = true; } // these values are used also for touch inputs _this.dragStartX = _this.scrollLeft + _this.getRtlX(ev.x); _this.dragStartY = _this.scrollTop + ev.y; }; _this.onDrag = function (ev) { var _this$props = _this.props, direction = _this$props.direction, overscrollEffectOn = _this$props.overscrollEffectOn, targetX = direction === 'vertical' ? 0 : _this.dragStartX - _this.getRtlX(ev.x), targetY = direction === 'horizontal' ? 0 : _this.dragStartY - ev.y; // 'vertical' or 'both' _this.lastInputType = 'drag'; if (!_this.isTouching) { _this.start({ targetX: targetX, targetY: targetY, animate: false, overscrollEffect: overscrollEffectOn.drag }); } else if (_this.overscrollEnabled && overscrollEffectOn.drag) { _this.checkAndApplyOverscrollEffectOnDrag(targetX, targetY, overscrollTypeHold); } }; _this.onDragEnd = function () { _this.isDragging = false; _this.lastInputType = 'drag'; if (_this.flickTarget) { var overscrollEffectOn = _this.props.overscrollEffectOn, _this$flickTarget = _this.flickTarget, targetX = _this$flickTarget.targetX, targetY = _this$flickTarget.targetY; if (!_this.isTouching) { _this.isScrollAnimationTargetAccumulated = false; _this.start({ targetX: targetX, targetY: targetY, overscrollEffect: overscrollEffectOn.drag }); } else if (_this.overscrollEnabled && overscrollEffectOn.drag) { _this.checkAndApplyOverscrollEffectOnDrag(targetX, targetY, overscrollTypeOnce); } } else if (!_this.isTouching) { _this.stop(); } if (_this.overscrollEnabled) { // not check this.props.overscrollEffectOn.drag for safety _this.clearAllOverscrollEffects(); } _this.isTouching = false; _this.flickTarget = null; }; _this.onFlick = function (ev) { var direction = _this.props.direction; if (!_this.isTouching) { _this.flickTarget = _this.animator.simulate(_this.scrollLeft, _this.scrollTop, direction !== 'vertical' ? _this.getRtlX(-ev.velocityX) : 0, direction !== 'horizontal' ? -ev.velocityY : 0); } else if (_this.overscrollEnabled && _this.props.overscrollEffectOn.drag) { _this.flickTarget = { targetX: _this.scrollLeft + _this.getRtlX(-ev.velocityX) * overscrollVelocityFactor, // 'horizontal' or 'both' targetY: _this.scrollTop + -ev.velocityY * overscrollVelocityFactor // 'vertical' or 'both' }; } if (_this.props.onFlick) { (0, _handle.forward)('onFlick', ev, _this.props); } }; /* * 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 */ _this.onWheel = function (ev) { if (_this.isDragging) { ev.preventDefault(); ev.stopPropagation(); } else { var overscrollEffectOn = _this.props.overscrollEffectOn, overscrollEffectRequired = _this.overscrollEnabled && overscrollEffectOn.wheel, bounds = _this.getScrollBounds(), canScrollHorizontally = _this.canScrollHorizontally(bounds), canScrollVertically = _this.canScrollVertically(bounds), eventDeltaMode = ev.deltaMode, eventDelta = -ev.wheelDeltaY || ev.deltaY; var delta = 0, needToHideThumb = false; _this.lastInputType = 'wheel'; if (_this.props.noScrollByWheel) { if (canScrollVertically) { ev.preventDefault(); } return; } if (_this.props.onWheel) { (0, _handle.forward)('onWheel', ev, _this.props); return; } _this.showThumb(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 (canScrollVertically) { // This routine handles wheel events on scrollbars for vertical scroll. if (eventDelta < 0 && _this.scrollTop > 0 || eventDelta > 0 && _this.scrollTop < bounds.maxTop) { var _assertThisInitialize = _assertThisInitialized(_this), horizontalScrollbarRef = _assertThisInitialize.horizontalScrollbarRef, verticalScrollbarRef = _assertThisInitialize.verticalScrollbarRef; // Not to check if ev.target is a descendant of a wrapped component which may have a lot of nodes in it. if (horizontalScrollbarRef.current && horizontalScrollbarRef.current.getContainerRef().current.contains(ev.target) || verticalScrollbarRef.current && verticalScrollbarRef.current.getContainerRef().current.contains(ev.target)) { delta = _this.calculateDistanceByWheel(eventDeltaMode, eventDelta, bounds.clientHeight * scrollWheelPageMultiplierForMaxPixel); needToHideThumb = !delta; ev.preventDefault(); } else if (overscrollEffectRequired) { _this.checkAndApplyOverscrollEffect('vertical', eventDelta > 0 ? 'after' : 'before', overscrollTypeOnce); } ev.stopPropagation(); } else { if (overscrollEffectRequired && (eventDelta < 0 && _this.scrollTop <= 0 || eventDelta > 0 && _this.scrollTop >= bounds.maxTop)) { _this.applyOverscrollEffect('vertical', eventDelta > 0 ? 'after' : 'before', overscrollTypeOnce, 1); } needToHideThumb = true; } } else if (canScrollHorizontally) { // this routine handles wheel events on any children for horizontal scroll. if (eventDelta < 0 && _this.scrollLeft > 0 || eventDelta > 0 && _this.scrollLeft < bounds.maxLeft) { delta = _this.calculateDistanceByWheel(eventDeltaMode, eventDelta, bounds.clientWidth * scrollWheelPageMultiplierForMaxPixel); needToHideThumb = !delta; ev.preventDefault(); ev.stopPropagation(); } else { if (overscrollEffectRequired && (eventDelta < 0 && _this.scrollLeft <= 0 || eventDelta > 0 && _this.scrollLeft >= bounds.maxLeft)) { _this.applyOverscrollEffect('horizontal', eventDelta > 0 ? 'after' : 'before', overscrollTypeOnce, 1); } needToHideThumb = true; } } if (delta !== 0) { var direction = Math.sign(delta); // Not to accumulate scroll position if wheel direction is different from hold direction if (direction !== _this.wheelDirection) { _this.isScrollAnimationTargetAccumulated = false; _this.wheelDirection = direction; } _this.scrollToAccumulatedTarget(delta, canScrollVertically, overscrollEffectOn.wheel); } if (needToHideThumb) { _this.startHidingThumb(); } } }; _this.onScroll = function (ev) { var _ev$target = ev.target, scrollLeft = _ev$target.scrollLeft, scrollTop = _ev$target.scrollTop; var bounds = _this.getScrollBounds(), canScrollHorizontally = _this.canScrollHorizontally(bounds); if (!_this.scrolling) { _this.scrollStartOnScroll(); } if (_this.props.rtl && canScrollHorizontally) { scrollLeft = _platform.platform.ios || _platform.platform.safari || _platform.platform.chrome >= 85 || _platform.platform.androidChrome >= 85 ? -scrollLeft : bounds.maxLeft - scrollLeft; } if (scrollLeft !== _this.scrollLeft) { _this.setScrollLeft(scrollLeft); } if (scrollTop !== _this.scrollTop) { _this.setScrollTop(scrollTop); } if (_this.childRefCurrent.didScroll) { _this.childRefCurrent.didScroll(_this.scrollLeft, _this.scrollTop); } _this.forwardScrollEvent('onScroll'); _this.scrollStopJob.start(); }; _this.onKeyDown = function (ev) { (0, _handle.forward)('onKeyDown', ev, _this.props); }; _this.scrollToAccumulatedTarget = function (delta, vertical, overscrollEffect) { if (!_this.isScrollAnimationTargetAccumulated) { _this.accumulatedTargetX = _this.scrollLeft; _this.accumulatedTargetY = _this.scrollTop; _this.isScrollAnimationTargetAccumulated = true; } if (vertical) { _this.accumulatedTargetY += delta; } else { _this.accumulatedTargetX += delta; } _this.start({ targetX: _this.accumulatedTargetX, targetY: _this.accumulatedTargetY, overscrollEffect: overscrollEffect }); }; // overscroll effect _this.getEdgeFromPosition = function (position, maxPosition) { if (position <= 0) { return 'before'; /* If a scroll size or a client size is not integer, browser's max scroll position could be smaller than maxPos by 1 pixel.*/ } else if (position >= maxPosition - 1) { return 'after'; } else { return null; } }; _this.setOverscrollStatus = function (orientation, edge, type, ratio) { var status = _this.overscrollStatus[orientation][edge]; status.type = type; status.ratio = ratio; }; _this.getOverscrollStatus = function (orientation, edge) { return _this.overscrollStatus[orientation][edge]; }; _this.calculateOverscrollRatio = function (orientation, position) { var bounds = _this.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); }; _this.applyOverscrollEffect = function (orientation, edge, type, ratio) { _this.props.applyOverscrollEffect(orientation, edge, type, ratio); _this.setOverscrollStatus(orientation, edge, type === overscrollTypeOnce ? overscrollTypeDone : type, ratio); }; _this.checkAndApplyOverscrollEffect = function (orientation, edge, type) { var ratio = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 1; var isVertical = orientation === 'vertical', curPos = isVertical ? _this.scrollTop : _this.scrollLeft, maxPos = _this.getScrollBounds()[isVertical ? 'maxTop' : 'maxLeft']; /* If a scroll size or a client size is not integer, browser's max scroll position could be smaller than maxPos by 1 pixel.*/ if (edge === 'before' && curPos <= 0 || edge === 'after' && curPos >= maxPos - 1) { // Already on the edge _this.applyOverscrollEffect(orientation, edge, type, ratio); } else { _this.setOverscrollStatus(orientation, edge, type, ratio); } }; _this.clearOverscrollEffect = function (orientation, edge) { if (_this.getOverscrollStatus(orientation, edge).type !== overscrollTypeNone) { if (_this.props.clearOverscrollEffect) { _this.props.clearOverscrollEffect(orientation, edge); } else { _this.applyOverscrollEffect(orientation, edge, overscrollTypeNone, 0); } } }; _this.clearAllOverscrollEffects = function () { ['horizontal', 'vertical'].forEach(function (orientation) { ['before', 'after'].forEach(function (edge) { _this.clearOverscrollEffect(orientation, edge); }); }); }; _this.applyOverscrollEffectOnDrag = function (orientation, edge, targetPosition, type) { if (edge) { var oppositeEdge = edge === 'before' ? 'after' : 'before', ratio = _this.calculateOverscrollRatio(orientation, targetPosition); _this.applyOverscrollEffect(orientation, edge, type, ratio); _this.clearOverscrollEffect(orientation, oppositeEdge); } else { _this.clearOverscrollEffect(orientation, 'before'); _this.clearOverscrollEffect(orientation, 'after'); } }; _this.checkAndApplyOverscrollEffectOnDrag = function (targetX, targetY, type) { var bounds = _this.getScrollBounds(); if (_this.canScrollHorizontally(bounds)) { _this.applyOverscrollEffectOnDrag('horizontal', _this.getEdgeFromPosition(targetX, bounds.maxLeft), targetX, type); } if (_this.canScrollVertically(bounds)) { _this.applyOverscrollEffectOnDrag('vertical', _this.getEdgeFromPosition(targetY, bounds.maxTop), targetY, type); } }; _this.checkAndApplyOverscrollEffectOnScroll = function (orientation) { ['before', 'after'].forEach(function (edge) { var _this$getOverscrollSt = _this.getOverscrollStatus(orientation, edge), ratio = _this$getOverscrollSt.ratio, type = _this$getOverscrollSt.type; if (type === overscrollTypeOnce) { _this.checkAndApplyOverscrollEffect(orientation, edge, type, ratio); } }); }; _this.checkAndApplyOverscrollEffectOnStart = function (orientation, edge, targetPosition) { if (_this.isDragging) { _this.applyOverscrollEffectOnDrag(orientation, edge, targetPosition, overscrollTypeHold); } else if (edge) { _this.checkAndApplyOverscrollEffect(orientation, edge, overscrollTypeOnce); } }; // call scroll callbacks and update scrollbars for native scroll _this.scrollStartOnScroll = function () { _this.scrolling = true; _this.showThumb(_this.getScrollBounds()); _this.forwardScrollEvent('onScrollStart'); }; _this.scrollStopOnScroll = function () { if (_this.props.scrollStopOnScroll) { _this.props.scrollStopOnScroll(); } if (_this.overscrollEnabled && !_this.isDragging) { // not check this.props.overscrollEffectOn for safety _this.clearAllOverscrollEffects(); } _this.lastInputType = null; _this.isScrollAnimationTargetAccumulated = false; _this.scrolling = false; _this.forwardScrollEvent('onScrollStop', _this.getReachedEdgeInfo()); _this.startHidingThumb(); }; _this.scrollStopJob = new _util.Job(_this.scrollStopOnScroll, scrollStopWaiting); _this.getReachedEdgeInfo = function () { var bounds = _this.getScrollBounds(), reachedEdgeInfo = { bottom: false, left: false, right: false, top: false }; if (_this.canScrollHorizontally(bounds)) { var rtl = _this.props.rtl, edge = _this.getEdgeFromPosition(_this.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 (_this.canScrollVertically(bounds)) { var _edge = _this.getEdgeFromPosition(_this.scrollTop, bounds.maxTop); if (_edge === 'before') { reachedEdgeInfo.top = true; } else if (_edge === 'after') { reachedEdgeInfo.bottom = true; } } return reachedEdgeInfo; }; // scrollTo API _this.getPositionForScrollTo = function (opt) { var bounds = _this.getScrollBounds(), canScrollHorizontally = _this.canScrollHorizontally(bounds), canScrollVertically = _this.canScrollVertically(bounds); var itemPos, left = null, top = null; if (opt instanceof Object) { if (opt.position instanceof Object) { if (canScrollHorizontally) { // We need '!=' to check if opt.position.x is null or undefined left = opt.position.x != null ? opt.position.x : _this.scrollLeft; } else { left = 0; } if (canScrollVertically) { // We need '!=' to check if opt.position.y is null or undefined top = opt.position.y != null ? opt.position.y : _this.scrollTop; } else { top = 0; } } else if (typeof opt.align === 'string') { if (canScrollHorizontally) { if (opt.align.includes('left')) { left = 0; } else if (opt.align.includes('right')) { left = bounds.maxLeft; } } if (canScrollVertically) { if (opt.align.includes('top')) { top = 0; } else if (opt.align.includes('bottom')) { top = bounds.maxTop; } } } else { if (typeof opt.index === 'number' && typeof _this.childRefCurrent.getItemPosition === 'function') { itemPos = _this.childRefCurrent.getItemPosition(opt.index, opt.stickTo); } else if (opt.node instanceof Object) { if (opt.node.nodeType === 1 && typeof _this.childRefCurrent.getNodePosition === 'function') { itemPos = _this.childRefCurrent.getNodePosition(opt.node); } } if (itemPos) { if (canScrollHorizontally) { left = itemPos.left; } if (canScrollVertically) { top = itemPos.top; } } } } return { left: left, top: top }; }; _this.scrollTo = function (opt) { if (!_this.deferScrollTo) { var _this$getPositionForS = _this.getPositionForScrollTo(opt), left = _this$getPositionForS.left, top = _this$getPositionForS.top; if (_this.props.scrollTo) { _this.props.scrollTo(opt); } _this.scrollToInfo = null; _this.start({ targetX: left !== null ? left : _this.scrollLeft, targetY: top !== null ? top : _this.scrollTop, animate: opt.animate }); } else { _this.scrollToInfo = opt; } }; _this.canScrollHorizontally = function (bounds) { var direction = _this.props.direction; return (direction === 'horizontal' || direction === 'both') && bounds.scrollWidth > bounds.clientWidth && !isNaN(bounds.scrollWidth); }; _this.canScrollVertically = function (bounds) { var direction = _this.props.direction; return (direction === 'vertical' || direction === 'both') && bounds.scrollHeight > bounds.clientHeight && !isNaN(bounds.scrollHeight); }; _this.startHidingThumb = function () { if (_this.state.isHorizontalScrollbarVisible && _this.horizontalScrollbarRef.current) { _this.horizontalScrollbarRef.current.startHidingThumb(); } if (_this.state.isVerticalScrollbarVisible && _this.verticalScrollbarRef.current) { _this.verticalScrollbarRef.current.startHidingThumb(); } }; _this.updateScrollbars = function () { var _this$props2 = _this.props, horizontalScrollbar = _this$props2.horizontalScrollbar, verticalScrollbar = _this$props2.verticalScrollbar, _this$state = _this.state, isHorizontalScrollbarVisible = _this$state.isHorizontalScrollbarVisible, isVerticalScrollbarVisible = _this$state.isVerticalScrollbarVisible, bounds = _this.getScrollBounds(), canScrollHorizontally = _this.canScrollHorizontally(bounds), canScrollVertically = _this.canScrollVertically(bounds), curHorizontalScrollbarVisible = horizontalScrollbar === 'auto' ? canScrollHorizontally : horizontalScrollbar === 'visible', curVerticalScrollbarVisible = verticalScrollbar === 'auto' ? canScrollVertically : verticalScrollbar === 'visible'; // determine if we should hide or show any scrollbars var isVisibilityChanged = isHorizontalScrollbarVisible !== curHorizontalScrollbarVisible || isVerticalScrollbarVisible !== curVerticalScrollbarVisible; if (isVisibilityChanged) { // one or both scrollbars have changed visibility _this.setState({ isHorizontalScrollbarVisible: curHorizontalScrollbarVisible, isVerticalScrollbarVisible: curVerticalScrollbarVisible }); } else { _this.deferScrollTo = false; _this.isUpdatedScrollThumb = _this.updateScrollThumbSize(); } }; _this.updateScrollThumbSize = function () { var _this$props3 = _this.props, horizontalScrollbar = _this$props3.horizontalScrollbar, verticalScrollbar = _this$props3.verticalScrollbar, bounds = _this.getScrollBounds(), canScrollHorizontally = _this.canScrollHorizontally(bounds), canScrollVertically = _this.canScrollVertically(bounds), curHorizontalScrollbarVisible = horizontalScrollbar === 'auto' ? canScrollHorizontally : horizontalScrollbar === 'visible', curVerticalScrollbarVisible = verticalScrollbar === 'auto' ? canScrollVertically : verticalScrollbar === 'visible'; if (curHorizontalScrollbarVisible || curVerticalScrollbarVisible) { // no visibility change but need to notify whichever scrollbars are visible of the // updated bounds and scroll position var updatedBounds = _objectSpread(_objectSpread({}, bounds), {}, { scrollLeft: _this.scrollLeft, scrollTop: _this.scrollTop }); if (curHorizontalScrollbarVisible && _this.horizontalScrollbarRef.current) { _this.horizontalScrollbarRef.current.update(updatedBounds); } if (curVerticalScrollbarVisible && _this.verticalScrollbarRef.current) { _this.verticalScrollbarRef.current.update(updatedBounds); } return true; } return false; }; // render _this.initChildRef = function (ref) { if (ref) { _this.childRefCurrent = ref.current || ref; } }; _this.state = { remeasure: false, isHorizontalScrollbarVisible: props.horizontalScrollbar === 'visible', isVerticalScrollbarVisible: props.verticalScrollbar === 'visible' }; _this.containerRef = /*#__PURE__*/(0, _react.createRef)(); _this.horizontalScrollbarRef = /*#__PURE__*/(0, _react.createRef)(); _this.verticalScrollbarRef = /*#__PURE__*/(0, _react.createRef)(); _this.horizontalScrollbarProps = { ref: _this.horizontalScrollbarRef, vertical: false, clientSize: props.clientSize }; _this.verticalScrollbarProps = { ref: _this.verticalScrollbarRef, vertical: true, clientSize: props.clientSize }; _this.overscrollEnabled = !!props.applyOverscrollEffect; // Enable the early bail out of repeated scrolling to the same position _this.animationInfo = null; _this.resizeRegistry = _Registry["default"].create(_this.handleResize.bind(_assertThisInitialized(_this))); props.cbScrollTo(_this.scrollTo); return _this; } _createClass(ScrollableBaseNative, [{ key: "componentDidMount", value: function componentDidMount() { this.resizeRegistry.parent = this.context; this.addEventListeners(); this.updateScrollbars(); } }, { key: "componentDidUpdate", value: function componentDidUpdate(prevProps, prevState) { var _this$state2 = this.state, isHorizontalScrollbarVisible = _this$state2.isHorizontalScrollbarVisible, isVerticalScrollbarVisible = _this$state2.isVerticalScrollbarVisible, hasDataSizeChanged = this.childRefCurrent.hasDataSizeChanged; // Need to sync calculated client size if it is different from the real size if (this.childRefCurrent.syncClientSize) { // If we actually synced, we need to reset scroll position. if (this.childRefCurrent.syncClientSize()) { this.setScrollLeft(0); this.setScrollTop(0); } } this.addEventListeners(); if (hasDataSizeChanged === false && (isHorizontalScrollbarVisible && !prevState.isHorizontalScrollbarVisible || isVerticalScrollbarVisible && !prevState.isVerticalScrollbarVisible)) { this.deferScrollTo = false; this.isUpdatedScrollThumb = this.updateScrollThumbSize(); } else { this.updateScrollbars(); } if (this.scrollToInfo !== null) { if (!this.deferScrollTo) { this.scrollTo(this.scrollToInfo); } } // publish container resize changes var horizontal = isHorizontalScrollbarVisible !== prevState.isHorizontalScrollbarVisible; var vertical = isVerticalScrollbarVisible !== prevState.isVerticalScrollbarVisible; if (horizontal || vertical) { this.resizeRegistry.notify({}); } } }, { key: "componentWillUnmount", value: function componentWillUnmount() { this.resizeRegistry.parent = null; // Before call cancelAnimationFrame, you must send scrollStop Event. if (this.scrolling) { this.forwardScrollEvent('onScrollStop', this.getReachedEdgeInfo()); } this.scrollStopJob.stop(); this.removeEventListeners(); } }, { key: "handleResize", value: function handleResize(ev) { if (ev.action === 'invalidateBounds') { this.enqueueForceUpdate(); } } }, { key: "enqueueForceUpdate", value: // TODO: consider replacing forceUpdate() by storing bounds in state rather than a non- // state member. function enqueueForceUpdate() { this.childRefCurrent.calculateMetrics(this.childRefCurrent.props); this.forceUpdate(); } }, { key: "calculateDistanceByWheel", value: function calculateDistanceByWheel(deltaMode, delta, maxPixel) { if (deltaMode === 0) { delta = (0, _clamp["default"])(-maxPixel, maxPixel, _resolution["default"].scale(delta * this.scrollWheelMultiplierForDeltaPixel)); } else if (deltaMode === 1) { // line; firefox delta = (0, _clamp["default"])(-maxPixel, maxPixel, _resolution["default"].scale(delta * this.pixelPerLine * this.scrollWheelMultiplierForDeltaPixel)); } else if (deltaMode === 2) { // page delta = delta < 0 ? -maxPixel : maxPixel; } return delta; } }, { key: "forwardScrollEvent", value: // call scroll callbacks function forwardScrollEvent(type, reachedEdgeInfo) { (0, _handle.forward)(type, { type: type, scrollLeft: this.scrollLeft, scrollTop: this.scrollTop, moreInfo: this.getMoreInfo(), reachedEdgeInfo: reachedEdgeInfo }, this.props); } }, { key: "setScrollLeft", value: // update scroll position function setScrollLeft(value) { var bounds = this.getScrollBounds(); this.scrollLeft = (0, _clamp["default"])(0, bounds.maxLeft, value); if (this.overscrollEnabled && this.props.overscrollEffectOn[this.lastInputType]) { this.checkAndApplyOverscrollEffectOnScroll('horizontal'); } if (this.state.isHorizontalScrollbarVisible) { this.updateThumb(this.horizontalScrollbarRef, bounds); } } }, { key: "setScrollTop", value: function setScrollTop(value) { var bounds = this.getScrollBounds(); this.scrollTop = (0, _clamp["default"])(0, bounds.maxTop, value); if (this.overscrollEnabled && this.props.overscrollEffectOn[this.lastInputType]) { this.checkAndApplyOverscrollEffectOnScroll('vertical'); } if (this.state.isVerticalScrollbarVisible) { this.updateThumb(this.verticalScrollbarRef, bounds); } } }, { key: "start", value: // scroll start function start(_ref) { var targetX = _ref.targetX, targetY = _ref.targetY, _ref$animate = _ref.animate, animate = _ref$animate === void 0 ? true : _ref$animate, _ref$overscrollEffect = _ref.overscrollEffect, overscrollEffect = _ref$overscrollEffect === void 0 ? false : _ref$overscrollEffect; var scrollLeft = this.scrollLeft, scrollTop = this.scrollTop, childRefCurrent = this.childRefCurrent, childContainerRef = childRefCurrent.containerRef, bounds = this.getScrollBounds(), maxLeft = bounds.maxLeft, maxTop = bounds.maxTop; var updatedAnimationInfo = { targetX: targetX, targetY: targetY }; // bail early when scrolling to the same position if (this.scrolling && this.animationInfo && this.animationInfo.targetX === targetX && this.animationInfo.targetY === targetY) { return; } this.animationInfo = updatedAnimationInfo; if (Math.abs(maxLeft - targetX) < epsilon) { targetX = maxLeft; } if (Math.abs(maxTop - targetY) < epsilon) { targetY = maxTop; } if (this.overscrollEnabled && overscrollEffect) { if (scrollLeft !== targetX && this.canScrollHorizontally(bounds)) { this.checkAndApplyOverscrollEffectOnStart('horizontal', this.getEdgeFromPosition(targetX, maxLeft), targetX); } if (scrollTop !== targetY && this.canScrollVertically(bounds)) { this.checkAndApplyOverscrollEffectOnStart('vertical', this.getEdgeFromPosition(targetY, maxTop), targetY); } } if (animate) { childRefCurrent.scrollToPosition(targetX, targetY); } else { childContainerRef.current.style.scrollBehavior = null; childRefCurrent.scrollToPosition(targetX, targetY); childContainerRef.current.style.scrollBehavior = 'smooth'; } this.scrollStopJob.start(); if (this.props.start) { this.props.start(animate); } } }, { key: "stop", value: function stop() { var childRefCurrent = this.childRefCurrent, childContainerRef = childRefCurrent.containerRef; childContainerRef.current.style.scrollBehavior = null; childRefCurrent.scrollToPosition(this.scrollLeft + 0.1, this.scrollTop + 0.1); childContainerRef.current.style.scrollBehavior = 'smooth'; } }, { key: "showThumb", value: // scroll bar function showThumb(bounds) { if (this.state.isHorizontalScrollbarVisible && this.canScrollHorizontally(bounds) && this.horizontalScrollbarRef.current) { this.horizontalScrollbarRef.current.showThumb(); } if (this.state.isVerticalScrollbarVisible && this.canScrollVertically(bounds) && this.verticalScrollbarRef.current) { this.verticalScrollbarRef.current.showThumb(); } } }, { key: "updateThumb", value: function updateThumb(scrollbarRef, bounds) { scrollbarRef.current.update(_objectSpread(_objectSpread({}, bounds), {}, { scrollLeft: this.scrollLeft, scrollTop: this.scrollTop })); } }, { key: "getScrollBounds", value: // ref function getScrollBounds() { if (this.childRefCurrent && typeof this.childRefCurrent.getScrollBounds === 'function') { return this.childRefCurrent.getScrollBounds(); } } }, { key: "getMoreInfo", value: function getMoreInfo() { if (this.childRefCurrent && typeof this.childRefCurrent.getMoreInfo === 'function') { return this.childRefCurrent.getMoreInfo(); } } // FIXME setting event handlers directly to work on the V8 snapshot. }, { key: "addEventListeners", value: function addEventListeners() { var childRefCurrent = this.childRefCurrent, containerRef = this.containerRef; if (containerRef.current && containerRef.current.addEventListener) { containerRef.current.addEventListener('wheel', this.onWheel); containerRef.current.addEventListener('keydown', this.onKeyDown); containerRef.current.addEventListener('mousedown', this.onMouseDown); } if (childRefCurrent.containerRef.current) { if (childRefCurrent.containerRef.current.addEventListener) { childRefCurrent.containerRef.current.addEventListener('scroll', this.onScroll, { capture: true, passive: true }); } this.childRefCurrent.containerRef.current.style.scrollBehavior = 'smooth'; } if (this.props.addEventListeners) { this.props.addEventListeners(childRefCurrent.containerRef); } if (window) { window.addEventListener('resize', this.handleResizeWindow); } } // FIXME setting event handlers directly to work on the V8 snapshot. }, { key: "removeEventListeners", value: function removeEventListeners() { var childRefCurrent = this.childRefCurrent, containerRef = this.containerRef; if (containerRef.current && containerRef.current.removeEventListener) { containerRef.current.removeEventListener('wheel', this.onWheel); containerRef.current.removeEventListener('keydown', this.onKeyDown); containerRef.current.removeEventListener('mousedown', this.onMouseDown); } if (childRefCurrent.containerRef.current && childRefCurrent.containerRef.current.removeEventListener) { childRefCurrent.containerRef.current.removeEventListener('scroll', this.onScroll, { capture: true, passive: true }); } if (this.props.removeEventListeners) { this.props.removeEventListeners(childRefCurrent.containerRef); } if (window) { window.removeEventListener('resize', this.handleResizeWindow); } } }, { key: "render", value: function render() { var _this$props4 = this.props, className = _this$props4.className, containerRenderer = _this$props4.containerRenderer, noScrollByDrag = _this$props4.noScrollByDrag, rtl = _this$props4.rtl, style = _this$props4.style, rest = _objectWithoutProperties(_this$props4, _excluded), _this$state3 = this.state, isHorizontalScrollbarVisible = _this$state3.isHorizontalScrollbarVisible, isVerticalScrollbarVisible = _this$state3.isVerticalScrollbarVisible, scrollableClasses = (0, _classnames["default"])(_UiScrollableModule["default"].scrollable, className), contentClasses = (0, _classnames["default"])(_UiScrollableModule["default"].content, _UiScrollableModule["default"].contentNative), childWrapper = noScrollByDrag ? 'div' : TouchableDiv, childWrapperProps = _objectSpread({ className: contentClasses }, !noScrollByDrag && { flickConfig: flickConfig, onDrag: this.onDrag, onDragEnd: this.onDragEnd, onDragStart: this.onDragStart, onFlick: this.onFlick, onTouchStart: this.onTouchStart }); delete rest.addEventListeners; delete rest.applyOverscrollEffect; delete rest.cbScrollTo; delete rest.clearOverscrollEffect; delete rest.handleResizeWindow; delete rest.horizontalScrollbar; delete rest.noScrollByWheel; delete rest.onFlick; delete rest.onKeyDown; delete rest.onMouseDown; delete rest.onScroll; delete rest.onScrollStart; delete rest.onScrollStop; delete rest.onWheel; delete rest.overscrollEffectOn; delete rest.removeEventListeners; delete rest.scrollStopOnScroll; delete rest.scrollTo; delete rest.start; delete rest.verticalScrollbar; this.deferScrollTo = true; return /*#__PURE__*/(0, _jsxRuntime.jsx)(_Resizable.ResizeContext.Provider, { value: this.resizeRegistry.register, children: typeof containerRenderer === 'function' ? containerRenderer({ childComponentProps: rest, childWrapper: childWrapper, childWrapperProps: childWrapperProps, className: scrollableClasses, componentCss: _UiScrollableModule["default"], containerRef: this.containerRef, horizontalScrollbarProps: this.horizontalScrollbarProps, initChildRef: this.initChildRef, isHorizontalScrollbarVisible: isHorizontalScrollbarVisible, isVerticalScrollbarVisible: isVerticalScrollbarVisible, rtl: rtl, scrollTo: this.scrollTo, style: style, verticalScrollbarProps: this.verticalScrollbarProps }) : null }); } }]); return ScrollableBaseNative; }(_react.Component); /** * An unstyled native component that provides horizontal and vertical scrollbars and makes a render prop element scrollable. * * @class ScrollableNative * @memberof moonstone/UiScrollableNative * @extends moonstone/UiScrollable.ScrollableBaseNative * @ui * @private */ ScrollableBaseNative.displayName = 'ui:ScrollableBaseNative'; ScrollableBaseNative.propTypes = /** @lends moonstone/UiScrollableNative.ScrollableNative.prototype */{ /** * Render function. * * @type {Function} * @required * @private */ containerRenderer: _propTypes["default"].func.isRequired, /** * Called when adding additional event listeners in a themed component. * * @type {Function} * @private */ addEventListeners: _propTypes["default"].func, /** * Called to execute additional logic in a themed component to show overscroll effect. * * @type {Function} * @private */ applyOverscrollEffect: _propTypes["default"].func, /** * A callback function that receives a reference to the `scrollTo` feature. * * Once received, the `scrollTo` method can be called as an imperative interface. * * The `scrollTo` function accepts the following parameters: * - {position: {x, y}} - Pixel value for x and/or y position * - {align} - Where the scroll area should be aligned. Values are: * `'left'`, `'right'`, `'top'`, `'bottom'`, * `'topleft'`, `'topright'`, `'bottomleft'`, and `'bottomright'`. * - {index} - Index of specific item. (`0` or positive integer) * This option is available for only `VirtualList` kind. * - {node} - Node to scroll into view * - {animate} - When `true`, scroll occurs with animation. When `false`, no * animation occurs. * - {focus} - When `true`, attempts to focus item after scroll. Only valid when scrolling * by `index` or `node`. * > Note: Only specify one of: `position`, `align`, `index` or `node` * * Example: * ``` * // If you set cbScrollTo prop like below; * cbScrollTo: (fn) => {this.scrollTo = fn;} * // You can simply call like below; * this.scrollT