UNPKG

weex-nuke

Version:

基于 Rax 、Weex 的高性能组件体系 ~~

642 lines (520 loc) 18.2 kB
'use strict'; /** @jsx createElement */ Object.defineProperty(exports, "__esModule", { value: true }); var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; var _createClass = 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, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _class, _temp; exports.labelMap = labelMap; exports.valueMap = valueMap; exports.keyMap = keyMap; var _rax = require('rax'); var _nukeView = require('../../View/index.js'); var _nukeView2 = _interopRequireDefault(_nukeView); var _nukeText = require('../../Text/index.js'); var _nukeText2 = _interopRequireDefault(_nukeText); var _item = require('./item.js'); var _item2 = _interopRequireDefault(_item); var _nukeThemeProvider = require('../../ThemeProvider/index.js'); var _nukeThemeProvider2 = _interopRequireDefault(_nukeThemeProvider); var _styles = require('../styles/index.js'); var _styles2 = _interopRequireDefault(_styles); var _animate = require('./animate.js'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } var connectStyle = _nukeThemeProvider2.default.connectStyle; var noop = function noop() {}; var VISIBLE_ITEM_COUNT = 5; var MAX_TOUCH_PATH_LENGTH = 20; var VELOCITY_DETERMINE_PERIOD = 100; var MILLISECONDS = 1000; var MIN_VELOCITY = 50; var VELOCITY_DECREASE_RATE = 0.9; var BOUNCE_RESISTANCE = 0.5; var BOUNCE_RESISTANCE_DISTANCE = 200; // px function getComputedStyleHeight(el) { if (typeof window === 'undefined') { return 0; } return parseFloat(window.getComputedStyle(el).height); } var PickerColumn = (_temp = _class = function (_Component) { _inherits(PickerColumn, _Component); function PickerColumn(props, context) { _classCallCheck(this, PickerColumn); var _this = _possibleConstructorReturn(this, (PickerColumn.__proto__ || Object.getPrototypeOf(PickerColumn)).call(this, props)); var value = void 0; if (context.__picker__) { value = context.selectedValue[props.index]; } else if ('value' in props) { value = props.value; } else { value = props.selectedKey; } _this.state = { value: value }; _this.bound = null; _this.isTracking = false; _this.isDragging = false; _this.currentScrollTop = 0; _this.touchPath = []; // use to calculate move velocity ['onChange', 'swipeStart', 'swipeMove', 'swipeEnd'].forEach(function (m) { _this[m] = _this[m].bind(_this); }); return _this; } _createClass(PickerColumn, [{ key: 'getChildContext', value: function getChildContext() { return { __column__: true, selectedValue: this.state.value }; } }, { key: 'componentWillReceiveProps', value: function componentWillReceiveProps(nextProps, nextContext) { if (nextContext.__picker__) { this.setState({ value: nextContext.selectedValue[nextProps.index] }); } else if ('value' in nextProps) { this.setState({ value: nextProps.value }); } } }, { key: 'componentWillUpdate', value: function componentWillUpdate(nextProps, nextState) { if (nextProps.dataSource !== this.props.dataSource || JSON.stringify(nextProps.dataSource) !== JSON.stringify(this.props.dataSource)) { this.changed = true; } } }, { key: 'componentDidMount', value: function componentDidMount() { this.componentDidUpdate(); } }, { key: 'componentDidUpdate', value: function componentDidUpdate() { // calculate scroll content bound // inMatrix arg for config platform if (!this.bound || this.changed) { this.calculateBound(); } // set scroll offset according to default value this.setScroll(this.state.value); this.changed = false; } }, { key: 'calculateBound', value: function calculateBound(props) { props = props || this.props; var indicator = this.refs.indicator; var _props = props, children = _props.children, dataSource = _props.dataSource; var top = void 0;var bottom = void 0;var itemHeight = void 0;var length = void 0; var halfOfVisibleCount = Math.floor(VISIBLE_ITEM_COUNT / 2); itemHeight = getComputedStyleHeight(indicator); length = dataSource.length; bottom = itemHeight * halfOfVisibleCount; top = -itemHeight * (length - (VISIBLE_ITEM_COUNT - halfOfVisibleCount)); this.bound = { top: top, bottom: bottom, itemHeight: itemHeight }; } }, { key: 'setScroll', value: function setScroll(value) { var _bound = this.bound, itemHeight = _bound.itemHeight, bottom = _bound.bottom; var _props2 = this.props, valueMap = _props2.valueMap, dataSource = _props2.dataSource, children = _props2.children, keyMap = _props2.keyMap; var index = 0; var _children = void 0; var item = void 0; if (typeof value === 'undefined') { value = dataSource[0].key; } for (var i = 0, l = dataSource.length; i < l; i++) { if (keyMap(dataSource[i]).toString() === value.toString()) { index = i; item = dataSource[i]; break; } } if (this.context.__picker__) { this.context.onUpdate(item, this.props.index); } this.updateOffset(bottom - itemHeight * index); } }, { key: 'startAnimate', value: function startAnimate(stepFunc, callback, duration, easeFunc) { this.stopAnimate(); return this.animateId = _animate.Animate.start(stepFunc, callback, duration, easeFunc); } }, { key: 'stopAnimate', value: function stopAnimate() { if (this.animateId) { _animate.Animate.stop(this.animateId); this.animateId = null; } } }, { key: 'isInBound', value: function isInBound(scrollTop) { var _bound2 = this.bound, top = _bound2.top, bottom = _bound2.bottom; if (scrollTop === undefined) { scrollTop = this.currentScrollTop; } return !(scrollTop > bottom || scrollTop < top); } }, { key: 'getBouncedDiff', value: function getBouncedDiff(scrollTop) { var _bound3 = this.bound, top = _bound3.top, bottom = _bound3.bottom; if (!this.isInBound(scrollTop)) { if (scrollTop > bottom) { return scrollTop - bottom; } if (scrollTop < top) { return scrollTop - top; } } return 0; } }, { key: 'setTransformStyle', value: function setTransformStyle(offset) { var content = this.refs.content; if (content) { var style = 'translate(0, ' + offset + 'px)'; // debugger; //bug fix: avoid translate3d, bug in android 4.3 content.style.WebkitTransform = style; content.style.transform = style; } } }, { key: 'setOffset', value: function setOffset(top) { this.currentScrollTop = top; this.setTransformStyle(top); } }, { key: 'updateOffset', value: function updateOffset(top, animation, callback, easeFunc) { var _this2 = this; var lastScrollTop = void 0;var diff = void 0; if (animation) { lastScrollTop = this.currentScrollTop, diff = top - lastScrollTop; this.startAnimate(function (percent) { _this2.setOffset(lastScrollTop + diff * percent); }, function () { _this2.setOffset(top); callback && callback(); }, 200, easeFunc); } else { this.setOffset(top); callback && callback(); } } }, { key: 'restrictScrollBound', value: function restrictScrollBound(scrollTop) { var _bound4 = this.bound, top = _bound4.top, bottom = _bound4.bottom; var max = Math.max, min = Math.min; return min(max(top, scrollTop), bottom); } // when touch move end or animation end, // adjust scrollTop value to stop by nearest picker item }, { key: 'stopBy', value: function stopBy() { var _this3 = this; var abs = Math.abs; // debugger; var current = this.currentScrollTop; var itemHeight = this.bound.itemHeight; var remainder = current % itemHeight; var multiple = parseInt(current / itemHeight, 10); var stopBy = current; if (abs(remainder) >= itemHeight / 2) { // reserve the sign that represent scroll direction stopBy = remainder / abs(remainder) * (abs(multiple) + 1) * itemHeight; } else { stopBy = multiple * itemHeight; } stopBy = this.restrictScrollBound(stopBy); this.updateOffset(stopBy, false, function () { _this3.onChange(); }, _animate.easeInOutQuad); } }, { key: 'swipeStart', value: function swipeStart(e) { e.preventDefault(); var pageY = e.touches ? e.touches[0].pageY : e.clientY; this.isTracking = true; this.isDragging = false; this.touchPageY = pageY; this.lastScrollTop = this.currentScrollTop; this.touchPath = [{ time: Date.now(), pageY: pageY }]; this.stopAnimate(); } }, { key: 'swipeMove', value: function swipeMove(e) { e.preventDefault(); if (!this.isTracking) { return; } this.isDragging = true; var abs = Math.abs, max = Math.max, min = Math.min; var initPageY = this.touchPageY; var currentPageY = e.touches ? e.touches[0].pageY : e.clientY; var diff = currentPageY - initPageY; var currentScrollTop = this.lastScrollTop + diff; // bounce if (!this.isInBound(currentScrollTop)) { var bound = void 0;var bounce = void 0; // bounced diff diff = this.getBouncedDiff(currentScrollTop); bound = currentScrollTop - diff; // apply bounce resistance diff = min(max(-BOUNCE_RESISTANCE_DISTANCE, diff), BOUNCE_RESISTANCE_DISTANCE); bounce = diff * (1 - abs(diff) / (BOUNCE_RESISTANCE_DISTANCE * 2)); currentScrollTop = bound + bounce; } this.updateOffset(currentScrollTop); // record move path this.touchPath.push({ time: Date.now(), pageY: currentPageY }); // restrict length if (this.touchPath.length > MAX_TOUCH_PATH_LENGTH) { this.touchPath = this.touchPath.slice(MAX_TOUCH_PATH_LENGTH / 2); } } }, { key: 'swipeEnd', value: function swipeEnd(e) { var _this4 = this; e.preventDefault(); // Ignore event when tracking is not enabled (event might be outside of element) if (!this.isDragging) { return; } var abs = Math.abs; // velocity calculation var lastTouchTime = Date.now(); var startPos = this.touchPath.length - 1; var endPos = startPos; var timeDiff = 0; var velocity = 0; for (var i = startPos - 1; i >= 0 && timeDiff < VELOCITY_DETERMINE_PERIOD; i--) { var position = this.touchPath[i]; timeDiff = lastTouchTime - position.time; endPos = i; } if (startPos !== endPos) { var firstPageY = this.touchPath[endPos].pageY; var lastPageY = this.touchPath[startPos].pageY; velocity = (lastPageY - firstPageY) / timeDiff * MILLISECONDS; } if (abs(velocity) > MIN_VELOCITY) { var animateId = this.startAnimate(function (percent, timeDiff, timeFrame) { var currentScrollTop = _this4.currentScrollTop; velocity *= VELOCITY_DECREASE_RATE; // bounce if (!_this4.isInBound(currentScrollTop)) { velocity *= BOUNCE_RESISTANCE; } var diff = velocity * timeFrame / MILLISECONDS; _this4.setOffset(currentScrollTop + diff); if (abs(velocity) < MIN_VELOCITY) { _animate.Animate.stop(animateId); _this4.stopBy(); } }); } else { this.stopBy(); } this.isTracking = false; this.isDragging = false; } }, { key: 'setValue', value: function setValue(value) { if (!('value' in this.props)) { this.setState({ value: value }); } } }, { key: 'getValue', value: function getValue() { return this.state.value; } }, { key: 'onChange', value: function onChange(e) { var scrollTop = this.currentScrollTop; var _bound5 = this.bound, bottom = _bound5.bottom, itemHeight = _bound5.itemHeight; var itemIndex = Math.round((bottom - scrollTop) / itemHeight); var _props3 = this.props, index = _props3.index, children = _props3.children, dataSource = _props3.dataSource, valueMap = _props3.valueMap, keyMap = _props3.keyMap; var value = void 0;var item = void 0; if (children) { item = this.props.children[itemIndex]; value = item.props.value; } else { item = dataSource[itemIndex]; value = keyMap(item); } if (value === this.state.value) { return; } if (this.context.__picker__) { this.context.onChange(value, item, index); } else { this.setValue(value); this.props.onChange(value, item); } } }, { key: 'render', value: function render() { var value = this.state.value; var styles = this.props.themeStyle; var _props4 = this.props, dataSource = _props4.dataSource, labelMap = _props4.labelMap, valueMap = _props4.valueMap, className = _props4.className, _props4$style = _props4.style, style = _props4$style === undefined ? {} : _props4$style, _props4$prefix = _props4.prefix, prefix = _props4$prefix === undefined ? this.defaultPrefix : _props4$prefix; var children = this.props.children; if (!children) { children = dataSource.map(function (item, index) { return (0, _rax.createElement)( _item2.default, { key: keyMap(item), value: valueMap(item) }, valueMap(item) ); }); } return (0, _rax.createElement)( 'div', { x: 'column-item', style: [styles['picker-column'], style], onMouseDown: this.swipeStart, onMouseMove: this.swipeMove, onMouseUp: this.swipeEnd, onMouseLeave: this.swipeEnd, onTouchStart: this.swipeStart, onTouchMove: this.swipeMove, onTouchEnd: this.swipeEnd, onTouchCancel: this.swipeEnd }, (0, _rax.createElement)( 'div', { ref: 'content', x: 'column-item-scroll', style: styles['picker-column-scroll'] }, children ), (0, _rax.createElement)('div', { x: 'column-item-mask', style: styles['picker-column-mask'] }), (0, _rax.createElement)('div', { x: 'column-item-indicator', ref: 'indicator', style: styles['picker-column-indicator'] }) ); } }]); return PickerColumn; }(_rax.Component), _class.propTypes = { value: _rax.PropTypes.any, dataSource: _rax.PropTypes.array, onChange: _rax.PropTypes.func, labelMap: _rax.PropTypes.func, valueMap: _rax.PropTypes.func }, _class.defaultProps = { selectedKey: null, dataSource: [], onChange: noop, labelMap: labelMap, valueMap: valueMap }, _class.contextTypes = { onUpdate: _rax.PropTypes.func, onChange: _rax.PropTypes.func, __picker__: _rax.PropTypes.bool, selectedValue: _rax.PropTypes.any }, _class.childContextTypes = { __column__: _rax.PropTypes.bool, selectedValue: _rax.PropTypes.any }, _temp); PickerColumn.displayName = 'Picker'; var StyledPickerColumn = connectStyle(_styles2.default)(PickerColumn); exports.default = StyledPickerColumn; // for label map function labelMap(item) { if ((typeof item === 'undefined' ? 'undefined' : _typeof(item)) === 'object') { return item.label; } return item; } // for value map function valueMap(item) { if ((typeof item === 'undefined' ? 'undefined' : _typeof(item)) === 'object') { return item.value; } return item; } // for key map function keyMap(item) { if ((typeof item === 'undefined' ? 'undefined' : _typeof(item)) === 'object') { return item.key; } return item; }