@minix-iot/ui
Version:
基于Vue的移动端轻量级UI组件库
259 lines (251 loc) • 6.82 kB
JSX
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>
)
}
}