UNPKG

@minix-iot/ui

Version:

基于Vue的移动端轻量级UI组件库

259 lines (251 loc) 6.82 kB
import { Namespace } from '../../utils/namespace'; import { Touch } from '../../utils/touch'; import { Inertia } from '../../utils/inertia'; import { DOMExtension } from '../../utils/extensions/dom'; import { MathExtension } from '../../utils/extensions/math'; const namespace = Namespace.Create('picker-column'); export default { name: namespace.name, props: { readonly: { type: Boolean, default: false }, itemHeight: { type: Number, required: true }, visibleItemCount: { type: Number | String, required: true }, defaultIndex: { type: Number, default: 0 }, swipeDuration: { type: Number, required: true }, items: { type: Array, default: () => [] }, suffix: { type: String, default: '' }, formatter: { type: Function, default: null } }, data() { return { selectedIndex: this.defaultIndex, offset: 0, duration: 0, moving: false, startOffset: 0, correctAfterTransition: null, inertia: Inertia.Create(), touch: Touch.Create() }; }, computed: { wrapperCls(){ return namespace.derive('wrapper'); }, itemWrapperCls(){ return namespace.derive('item-wrapper'); }, cls() { return namespace.derive(); }, suffixWrapperCls(){ return namespace.derive('suffix-wrapper') }, suffixCls(){ return namespace.derive('suffix') }, itemCount() { return this.items.length; }, baseOffset() { return (this.itemHeight * (this.visibleItemCount - 1)) / 2; }, wrapperStyle() { return { transform: `translate3d(0, ${this.offset + this.baseOffset}px, 0)`, transitionDuration: `${this.duration}ms`, transitionProperty: this.duration ? 'all' : 'none' }; }, itemStyle() { return { height: `${this.itemHeight}px`, lineHeight: `${this.itemHeight}px` }; }, suffixStyle(){ return { height: `${this.itemHeight}px`, lineHeight: `${this.itemHeight}px` } } }, watch:{ defaultIndex(val){ this.setIndex(val) } }, created() { this.setIndex(this.selectedIndex); }, updated() { if (this.selectedIndex >= this.itemCount) { this.setIndex(this.itemCount - 1); } }, methods: { getTouchInfo(e) { return e.touches[0]; }, getValue() { return this.items[this.selectedIndex]; }, setIndex(index, emitChange) { const nIndex = this.correctIndex(index) || 0; const offset = -nIndex * this.itemHeight; const trigger = () => { if (nIndex !== this.selectedIndex) { this.selectedIndex = nIndex; if (emitChange) { this.emitChange(nIndex); } } }; if (this.moving && offset !== this.offset) { this.correctAfterTransition = trigger; } else { trigger(); } this.offset = offset; }, emitChange(index) { this.$emit('change', index); }, correctIndex(index) { const nIndex = MathExtension.Clamp(index, 0, this.itemCount); for (let i = nIndex; i < this.itemCount; i++) { if (!this.isOptionDisabled(this.items[i])) return i; } for (let i = nIndex - 1; i >= 0; i--) { if (!this.isOptionDisabled(this.items[i])) return i; } }, isOptionDisabled(option) { return typeof option === 'object' && option.disabeld; }, onTouchStart(e) { if (this.readonly) { return; } const touchInfo = this.getTouchInfo(e); this.touch.start(touchInfo); this.duration = 0; this.startOffset = this.offset; this.correctAfterTransition = null; this.inertia.start(this.offset); }, onTouchMove(e) { if (this.readonly) { return; } DOMExtension.PreventDefault(e); const touchInfo = this.getTouchInfo(e); this.touch.move(touchInfo); if (!this.touch.isVertical) { return; } this.moving = true; this.offset = MathExtension.Clamp(this.startOffset + this.touch.deltaY, -this.itemCount * this.itemHeight, this.itemHeight); const now = Date.now(); if (now - this.touchStartTime > Inertia.LIMIT_TIME) { this.inertia.start(this.offset, now); } }, onTouchEnd(e) { if (this.readonly) { return; } this.inertia.end(this.offset); if (this.inertia.shouldActive) { this.startInertia(); } else { const index = this.getIndexByOffset(this.offset); this.duration = 200; this.setIndex(index, true); setTimeout(() => (this.moving = false), 0); } }, onTransitionEnd() { this.stopInertia(); }, getIndexByOffset(offset) { return MathExtension.Clamp(Math.round(-offset / this.itemHeight), 0, this.itemCount - 1); }, startInertia() { const speed = Math.abs(this.inertia.speed); const offset = this.offset + (speed / 0.003) * (this.inertia.distance < 0 ? -1 : 1); const index = this.getIndexByOffset(offset); this.duration = this.swipeDuration; this.setIndex(index, true); }, stopInertia() { this.moving = false; this.duration = 0; if (this.correctAfterTransition) { this.correctAfterTransition(); this.correctAfterTransition = null; } }, onItemClick(e, index) { if (this.moving || this.readonly) { return; } this.correctAfterTransition = null; this.duration = 200; this.setIndex(index, true); } }, render(){ const renderItems = ()=> this.items.map((item,i)=>{ const cls = namespace.derive('item',{selected: i === this.selectedIndex}) return ( <li class={cls} key={i} style={this.itemStyle} vOn:click_stop={e=>this.onItemClick(e,i)}> {this.formatter ? this.formatter(item.text) : item.text} </li> ) }) const renderSuffix = ()=>{ if(this.suffix){ return ( <div class={this.suffixWrapperCls}> <div class={this.suffixCls} style={this.suffixStyle}>{this.suffix}</div> </div> ) } } return ( <div class={this.wrapperCls}> <div class={this.itemWrapperCls} onTouchstart={this.onTouchStart} onTouchmove={this.onTouchMove} onTouchend={this.onTouchEnd} onTouchcancel={this.onTouchEnd}> <ul class={this.cls} style={this.wrapperStyle} onTransitionend={this.onTransitionEnd}> {renderItems()} </ul> </div> {renderSuffix()} </div> ) } }