@shopify/polaris
Version:
Shopify’s product component library
233 lines (200 loc) • 6.18 kB
JavaScript
import { objectWithoutProperties as _objectWithoutProperties } from '../../_virtual/_rollupPluginBabelHelpers.js';
import React$1, { Component } from 'react';
import debounce$1 from 'lodash/debounce';
import { StickyManagerContext } from '../../utilities/sticky-manager/context.js';
import { scrollable } from '../shared.js';
import { StickyManager } from '../../utilities/sticky-manager/sticky-manager.js';
import { classNames } from '../../utilities/css.js';
import { ScrollableContext } from './context.js';
import { ScrollTo as ScrollTo$1 } from './components/ScrollTo/ScrollTo.js';
import styles from './Scrollable.scss.js';
var MAX_SCROLL_DISTANCE = 100;
var DELTA_THRESHOLD = 0.2;
var DELTA_PERCENTAGE = 0.2;
var EVENTS_TO_LOCK = ['scroll', 'touchmove', 'wheel'];
var PREFERS_REDUCED_MOTION = prefersReducedMotion();
class Scrollable extends Component {
constructor(...args) {
super(...args);
this.state = {
topShadow: false,
bottomShadow: false,
scrollPosition: 0,
canScroll: false
};
this.stickyManager = new StickyManager();
this.scrollArea = null;
this.handleResize = debounce$1(() => {
this.handleScroll();
}, 50, {
trailing: true
});
this.setScrollArea = scrollArea => {
this.scrollArea = scrollArea;
};
this.handleScroll = () => {
var {
scrollArea
} = this;
var {
shadow,
onScrolledToBottom
} = this.props;
if (scrollArea == null) {
return;
}
var {
scrollTop,
clientHeight,
scrollHeight
} = scrollArea;
var shouldBottomShadow = Boolean(shadow && !(scrollTop + clientHeight >= scrollHeight));
var shouldTopShadow = Boolean(shadow && scrollTop > 0);
var canScroll = scrollHeight > clientHeight;
var hasScrolledToBottom = scrollHeight - scrollTop === clientHeight;
if (canScroll && hasScrolledToBottom && onScrolledToBottom) {
onScrolledToBottom();
}
this.setState({
topShadow: shouldTopShadow,
bottomShadow: shouldBottomShadow,
scrollPosition: scrollTop,
canScroll
});
};
this.scrollHint = () => {
var {
scrollArea
} = this;
if (scrollArea == null) {
return;
}
var {
clientHeight,
scrollHeight
} = scrollArea;
if (PREFERS_REDUCED_MOTION || this.state.scrollPosition > 0 || scrollHeight <= clientHeight) {
return;
}
var 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
}) => {
var 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) {
var closestElement = node.closest(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() {
var {
scrollPosition
} = this.state;
if (scrollPosition && this.scrollArea && scrollPosition > 0) {
this.scrollArea.scrollTop = scrollPosition;
}
}
render() {
var {
topShadow,
bottomShadow,
canScroll
} = this.state;
var _this$props = this.props,
{
children,
className,
horizontal,
vertical = true,
shadow,
hint,
onScrolledToBottom
} = _this$props,
rest = _objectWithoutProperties(_this$props, ["children", "className", "horizontal", "vertical", "shadow", "hint", "onScrolledToBottom"]);
var finalClassName = classNames(className, styles.Scrollable, vertical && styles.vertical, horizontal && styles.horizontal, topShadow && styles.hasTopShadow, bottomShadow && styles.hasBottomShadow, vertical && canScroll && styles.verticalHasScrolling);
return /*#__PURE__*/React$1.createElement(ScrollableContext.Provider, {
value: this.scrollToPosition
}, /*#__PURE__*/React$1.createElement(StickyManagerContext.Provider, {
value: this.stickyManager
}, /*#__PURE__*/React$1.createElement("div", Object.assign({
className: finalClassName
}, scrollable.props, rest, {
ref: this.setScrollArea
}), children)));
}
toggleLock(shouldLock = true) {
var {
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$1;
function prevent(evt) {
evt.preventDefault();
}
function prefersReducedMotion() {
try {
return window.matchMedia('(prefers-reduced-motion: reduce)').matches;
} catch (err) {
return false;
}
}
export { Scrollable };