UNPKG

@shopify/polaris

Version:

Shopify’s admin product component library

244 lines (209 loc) • 6.65 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var React = require('react'); var debounce = require('../../utilities/debounce.js'); var css = require('../../utilities/css.js'); var shared = require('../shared.js'); var context = require('./context.js'); var Scrollable$1 = require('./Scrollable.scss.js'); var ScrollTo = require('./components/ScrollTo/ScrollTo.js'); var stickyManager = require('../../utilities/sticky-manager/sticky-manager.js'); var context$1 = require('../../utilities/sticky-manager/context.js'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var React__default = /*#__PURE__*/_interopDefaultLegacy(React); const MAX_SCROLL_DISTANCE = 100; const DELTA_THRESHOLD = 0.2; const DELTA_PERCENTAGE = 0.2; const EVENTS_TO_LOCK = ['scroll', 'touchmove', 'wheel']; const PREFERS_REDUCED_MOTION = prefersReducedMotion(); const LOW_RES_BUFFER = 2; class Scrollable extends React.Component { constructor(...args) { super(...args); this.state = { topShadow: false, bottomShadow: false, scrollPosition: 0, canScroll: false }; this.stickyManager = new stickyManager.StickyManager(); this.scrollArea = null; this.handleResize = debounce.debounce(() => { this.handleScroll(); }, 50, { trailing: true }); this.setScrollArea = scrollArea => { this.scrollArea = scrollArea; }; this.handleScroll = () => { const { scrollArea } = this; const { scrollPosition } = this.state; const { shadow, onScrolledToBottom } = this.props; if (scrollArea == null) { return; } const { scrollTop, clientHeight, scrollHeight } = scrollArea; const shouldBottomShadow = Boolean(shadow && !(scrollTop + clientHeight >= scrollHeight)); const shouldTopShadow = Boolean(shadow && scrollTop > 0 && scrollPosition > 0); const canScroll = scrollHeight > clientHeight; const hasScrolledToBottom = scrollHeight - scrollTop <= clientHeight + LOW_RES_BUFFER; if (canScroll && hasScrolledToBottom && onScrolledToBottom) { onScrolledToBottom(); } this.setState({ topShadow: shouldTopShadow, bottomShadow: shouldBottomShadow, scrollPosition: scrollTop, canScroll }); }; this.scrollHint = () => { const { scrollArea } = this; if (scrollArea == null) { return; } const { clientHeight, scrollHeight } = scrollArea; if (PREFERS_REDUCED_MOTION || this.state.scrollPosition > 0 || scrollHeight <= clientHeight) { return; } const scrollDistance = scrollHeight - clientHeight; this.toggleLock(); this.setState({ scrollPosition: scrollDistance > MAX_SCROLL_DISTANCE ? MAX_SCROLL_DISTANCE : scrollDistance }, () => { window.requestAnimationFrame(this.scrollStep); }); }; this.scrollStep = () => { this.setState(({ scrollPosition }) => { const delta = scrollPosition * DELTA_PERCENTAGE; return { scrollPosition: delta < DELTA_THRESHOLD ? 0 : scrollPosition - delta }; }, () => { if (this.state.scrollPosition > 0) { window.requestAnimationFrame(this.scrollStep); } else { this.toggleLock(false); } }); }; this.scrollToPosition = scrollY => { this.setState({ scrollPosition: scrollY }); }; } static forNode(node) { const closestElement = node.closest(shared.scrollable.selector); return closestElement instanceof HTMLElement ? closestElement : document; } componentDidMount() { if (this.scrollArea == null) { return; } this.stickyManager.setContainer(this.scrollArea); this.scrollArea.addEventListener('scroll', () => { window.requestAnimationFrame(this.handleScroll); }); window.addEventListener('resize', this.handleResize); window.requestAnimationFrame(() => { this.handleScroll(); if (this.props.hint) { this.scrollHint(); } }); } componentWillUnmount() { if (this.scrollArea == null) { return; } this.scrollArea.removeEventListener('scroll', this.handleScroll); window.removeEventListener('resize', this.handleResize); this.stickyManager.removeScrollListener(); } componentDidUpdate() { const { scrollPosition } = this.state; if (scrollPosition && this.scrollArea && scrollPosition > 0) { this.scrollArea.scrollTop = scrollPosition; } } render() { const { topShadow, bottomShadow, canScroll } = this.state; const { children, className, horizontal, vertical = true, shadow, hint, focusable, onScrolledToBottom, ...rest } = this.props; const finalClassName = css.classNames(className, Scrollable$1["default"].Scrollable, vertical && Scrollable$1["default"].vertical, horizontal && Scrollable$1["default"].horizontal, topShadow && Scrollable$1["default"].hasTopShadow, bottomShadow && Scrollable$1["default"].hasBottomShadow, vertical && canScroll && Scrollable$1["default"].verticalHasScrolling); return /*#__PURE__*/React__default["default"].createElement(context.ScrollableContext.Provider, { value: this.scrollToPosition }, /*#__PURE__*/React__default["default"].createElement(context$1.StickyManagerContext.Provider, { value: this.stickyManager }, /*#__PURE__*/React__default["default"].createElement("div", Object.assign({ className: finalClassName }, shared.scrollable.props, rest, { ref: this.setScrollArea // eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex , tabIndex: focusable ? 0 : undefined }), children))); } toggleLock(shouldLock = true) { const { scrollArea } = this; if (scrollArea == null) { return; } EVENTS_TO_LOCK.forEach(eventName => { if (shouldLock) { scrollArea.addEventListener(eventName, prevent); } else { scrollArea.removeEventListener(eventName, prevent); } }); } } Scrollable.ScrollTo = ScrollTo.ScrollTo; function prevent(evt) { evt.preventDefault(); } function prefersReducedMotion() { try { return window.matchMedia('(prefers-reduced-motion: reduce)').matches; } catch (err) { return false; } } exports.Scrollable = Scrollable;