|
| 1 | +import React, {Component} from 'react'; |
| 2 | +import PropTypes from 'prop-types'; |
| 3 | +import classnames from 'classnames'; |
| 4 | + |
| 5 | +import {MDCTabScrollerFoundation, util} from '@material/tab-scroller/dist/mdc.tabScroller'; |
| 6 | + |
| 7 | +const convertDashToCamelCase = (propName) => propName.replace(/-(\w)/g, (_, v) => v.toUpperCase()); |
| 8 | + |
| 9 | +export default class TabScroller extends Component { |
| 10 | + areaElement_ = React.createRef(); |
| 11 | + contentElement_ = React.createRef(); |
| 12 | + state = { |
| 13 | + classList: new Set(), |
| 14 | + areaClassList: new Set(), |
| 15 | + scrollAreaStyleProperty: {}, |
| 16 | + scrollContentStyleProperty: {}, |
| 17 | + }; |
| 18 | + |
| 19 | + componentDidMount() { |
| 20 | + this.foundation_ = new MDCTabScrollerFoundation(this.adapter); |
| 21 | + this.foundation_.init(); |
| 22 | + } |
| 23 | + |
| 24 | + componentWillUnmount() { |
| 25 | + this.foundation_.destroy(); |
| 26 | + } |
| 27 | + |
| 28 | + get classes() { |
| 29 | + const {alignStart, alignEnd, alignCenter, className} = this.props; |
| 30 | + const {classList} = this.state; |
| 31 | + return classnames('mdc-tab-scroller', Array.from(classList), className, { |
| 32 | + 'mdc-tab-scroller--align-start': alignStart, |
| 33 | + 'mdc-tab-scroller--align-end': alignEnd, |
| 34 | + 'mdc-tab-scroller--align-center': alignCenter, |
| 35 | + }); |
| 36 | + } |
| 37 | + |
| 38 | + setStyleToElement = (prop, value, elementStyleProperty) => { |
| 39 | + const styleName = convertDashToCamelCase(prop); |
| 40 | + const updateElementStyleProperty = Object.assign( |
| 41 | + this.state[elementStyleProperty], |
| 42 | + {[styleName]: value} |
| 43 | + ); |
| 44 | + this.setState({[elementStyleProperty]: updateElementStyleProperty}); |
| 45 | + } |
| 46 | + |
| 47 | + get adapter() { |
| 48 | + return { |
| 49 | + eventTargetMatchesSelector: (evtTarget, selector) => { |
| 50 | + const MATCHES = util.getMatchesProperty(HTMLElement.prototype); |
| 51 | + return evtTarget[MATCHES](selector); |
| 52 | + }, |
| 53 | + addClass: (className) => { |
| 54 | + const classList = new Set(this.state.classList); |
| 55 | + classList.add(className); |
| 56 | + this.setState({classList}); |
| 57 | + }, |
| 58 | + removeClass: (className) => { |
| 59 | + const classList = new Set(this.state.classList); |
| 60 | + classList.delete(className); |
| 61 | + this.setState({classList}); |
| 62 | + }, |
| 63 | + addScrollAreaClass: (className) => { |
| 64 | + const areaClassList = new Set(this.state.areaClassList); |
| 65 | + areaClassList.add(className); |
| 66 | + this.setState({areaClassList}); |
| 67 | + }, |
| 68 | + setScrollAreaStyleProperty: (prop, value) => this.setStyleToElement(prop, value, 'scrollAreaStyleProperty'), |
| 69 | + setScrollContentStyleProperty: (prop, value) => this.setStyleToElement(prop, value, 'scrollContentStyleProperty'), |
| 70 | + getScrollContentStyleValue: (propName) => this.contentElement_.current |
| 71 | + && window.getComputedStyle(this.contentElement_.current).getPropertyValue(propName), |
| 72 | + setScrollAreaScrollLeft: (scrollX) => { |
| 73 | + if (!this.areaElement_.current) return; |
| 74 | + this.areaElement_.current.scrollLeft = scrollX; |
| 75 | + }, |
| 76 | + getScrollAreaScrollLeft: () => this.areaElement_.current && this.areaElement_.current.scrollLeft, |
| 77 | + getScrollContentOffsetWidth: this.getScrollContentWidth, |
| 78 | + getScrollAreaOffsetWidth: () => this.areaElement_.current && this.areaElement_.current.offsetWidth, |
| 79 | + computeScrollAreaClientRect: () => this.areaElement_.current && this.areaElement_.current.getBoundingClientRect(), |
| 80 | + computeScrollContentClientRect: () => this.contentElement_.current |
| 81 | + && this.contentElement_.current.getBoundingClientRect(), |
| 82 | + computeHorizontalScrollbarHeight: () => util.computeHorizontalScrollbarHeight(document), |
| 83 | + }; |
| 84 | + } |
| 85 | + |
| 86 | + getScrollPosition = () => { |
| 87 | + return this.foundation_.getScrollPosition(); |
| 88 | + } |
| 89 | + |
| 90 | + // needs to be public class method for react tab-bar |
| 91 | + getScrollContentWidth = () => { |
| 92 | + return this.contentElement_.current && this.contentElement_.current.offsetWidth; |
| 93 | + } |
| 94 | + |
| 95 | + incrementScroll = (scrollXIncrement) => { |
| 96 | + this.foundation_.incrementScroll(scrollXIncrement); |
| 97 | + } |
| 98 | + |
| 99 | + scrollTo = (scrollX) => { |
| 100 | + this.foundation_.scrollTo(scrollX); |
| 101 | + } |
| 102 | + |
| 103 | + handleWheel_ = (evt) => { |
| 104 | + this.props.onWheel(evt); |
| 105 | + this.foundation_.handleInteraction(evt); |
| 106 | + } |
| 107 | + |
| 108 | + handleTouchStart_ = (evt) => { |
| 109 | + this.props.onTouchStart(evt); |
| 110 | + this.foundation_.handleInteraction(evt); |
| 111 | + } |
| 112 | + |
| 113 | + handlePointerDown_ = (evt) => { |
| 114 | + this.props.onPointerDown(evt); |
| 115 | + this.foundation_.handleInteraction(evt); |
| 116 | + } |
| 117 | + |
| 118 | + handleMouseDown_ = (evt) => { |
| 119 | + this.props.onMouseDown(evt); |
| 120 | + this.foundation_.handleInteraction(evt); |
| 121 | + } |
| 122 | + |
| 123 | + handleKeyDown_ = (evt) => { |
| 124 | + this.props.onKeyDown(evt); |
| 125 | + this.foundation_.handleInteraction(evt); |
| 126 | + } |
| 127 | + |
| 128 | + handleTransitionEnd_ = (evt) => { |
| 129 | + this.props.onTransitionEnd(evt); |
| 130 | + this.foundation_.handleTransitionEnd(evt); |
| 131 | + } |
| 132 | + |
| 133 | + render() { |
| 134 | + const {areaClassList} = this.state; |
| 135 | + const { |
| 136 | + children, |
| 137 | + /* eslint-disable */ |
| 138 | + alignStart, |
| 139 | + alignEnd, |
| 140 | + alignCenter, |
| 141 | + className, |
| 142 | + onWheel, |
| 143 | + onTouchStart, |
| 144 | + onPointerDown, |
| 145 | + onMouseDown, |
| 146 | + onKeyDown, |
| 147 | + onTransitionEnd, |
| 148 | + /* eslint-enable */ |
| 149 | + ...otherProps |
| 150 | + } = this.props; |
| 151 | + const areaClasses = classnames('mdc-tab-scroller__scroll-area', Array.from(areaClassList)); |
| 152 | + |
| 153 | + return ( |
| 154 | + <div |
| 155 | + className={this.classes} |
| 156 | + onWheel={this.handleWheel_} |
| 157 | + onTouchStart={this.handleTouchStart_} |
| 158 | + onPointerDown={this.handlePointerDown_} |
| 159 | + onMouseDown={this.handleMouseDown_} |
| 160 | + onKeyDown={this.handleKeyDown_} |
| 161 | + onTransitionEnd={this.handleTransitionEnd_} |
| 162 | + {...otherProps} |
| 163 | + > |
| 164 | + <div |
| 165 | + className={areaClasses} |
| 166 | + ref={this.areaElement_} |
| 167 | + > |
| 168 | + <div |
| 169 | + className='mdc-tab-scroller__scroll-content' |
| 170 | + ref={this.contentElement_} |
| 171 | + > |
| 172 | + {children} |
| 173 | + </div> |
| 174 | + </div> |
| 175 | + </div> |
| 176 | + ); |
| 177 | + } |
| 178 | +} |
| 179 | + |
| 180 | +TabScroller.propTypes = { |
| 181 | + alignStart: PropTypes.bool, |
| 182 | + alignEnd: PropTypes.bool, |
| 183 | + alignCenter: PropTypes.bool, |
| 184 | + children: PropTypes.node, |
| 185 | + className: PropTypes.string, |
| 186 | + onWheel: PropTypes.func, |
| 187 | + onTouchStart: PropTypes.func, |
| 188 | + onPointerDown: PropTypes.func, |
| 189 | + onMouseDown: PropTypes.func, |
| 190 | + onKeyDown: PropTypes.func, |
| 191 | + onTransitionEnd: PropTypes.func, |
| 192 | +}; |
| 193 | + |
| 194 | +TabScroller.defaultProps = { |
| 195 | + alignStart: false, |
| 196 | + alignEnd: false, |
| 197 | + alignCenter: false, |
| 198 | + className: '', |
| 199 | + children: null, |
| 200 | + onWheel: () => {}, |
| 201 | + onTouchStart: () => {}, |
| 202 | + onPointerDown: () => {}, |
| 203 | + onMouseDown: () => {}, |
| 204 | + onKeyDown: () => {}, |
| 205 | + onTransitionEnd: () => {}, |
| 206 | +}; |
0 commit comments