uikit
Version:
UIkit is a lightweight and modular front-end framework for developing fast and powerful web interfaces.
216 lines (142 loc) • 5.71 kB
JavaScript
import {closest, css, getEventPos, includes, isRtl, isTouch, off, on, pointerCancel, pointerDown, pointerMove, pointerUp, selInput, trigger} from 'uikit-util';
export default {
props: {
draggable: Boolean
},
data: {
draggable: true,
threshold: 10
},
created() {
['start', 'move', 'end'].forEach(key => {
const fn = this[key];
this[key] = e => {
const pos = getEventPos(e).x * (isRtl ? -1 : 1);
this.prevPos = pos !== this.pos ? this.pos : this.prevPos;
this.pos = pos;
fn(e);
};
});
},
events: [
{
name: pointerDown,
delegate() {
return this.selSlides;
},
handler(e) {
if (!this.draggable
|| !isTouch(e) && hasTextNodesOnly(e.target)
|| closest(e.target, selInput)
|| e.button > 0
|| this.length < 2
) {
return;
}
this.start(e);
}
},
{
name: 'dragstart',
handler(e) {
e.preventDefault();
}
}
],
methods: {
start() {
this.drag = this.pos;
if (this._transitioner) {
this.percent = this._transitioner.percent();
this.drag += this._transitioner.getDistance() * this.percent * this.dir;
this._transitioner.cancel();
this._transitioner.translate(this.percent);
this.dragging = true;
this.stack = [];
} else {
this.prevIndex = this.index;
}
on(document, pointerMove, this.move, {passive: false});
// 'input' event is triggered by video controls
on(document, `${pointerUp} ${pointerCancel} input`, this.end, true);
css(this.list, 'userSelect', 'none');
},
move(e) {
const distance = this.pos - this.drag;
if (distance === 0 || this.prevPos === this.pos || !this.dragging && Math.abs(distance) < this.threshold) {
return;
}
// prevent click event
css(this.list, 'pointerEvents', 'none');
e.cancelable && e.preventDefault();
this.dragging = true;
this.dir = (distance < 0 ? 1 : -1);
const {slides} = this;
let {prevIndex} = this;
let dis = Math.abs(distance);
let nextIndex = this.getIndex(prevIndex + this.dir, prevIndex);
let width = this._getDistance(prevIndex, nextIndex) || slides[prevIndex].offsetWidth;
while (nextIndex !== prevIndex && dis > width) {
this.drag -= width * this.dir;
prevIndex = nextIndex;
dis -= width;
nextIndex = this.getIndex(prevIndex + this.dir, prevIndex);
width = this._getDistance(prevIndex, nextIndex) || slides[prevIndex].offsetWidth;
}
this.percent = dis / width;
const prev = slides[prevIndex];
const next = slides[nextIndex];
const changed = this.index !== nextIndex;
const edge = prevIndex === nextIndex;
let itemShown;
[this.index, this.prevIndex].filter(i => !includes([nextIndex, prevIndex], i)).forEach(i => {
trigger(slides[i], 'itemhidden', [this]);
if (edge) {
itemShown = true;
this.prevIndex = prevIndex;
}
});
if (this.index === prevIndex && this.prevIndex !== prevIndex || itemShown) {
trigger(slides[this.index], 'itemshown', [this]);
}
if (changed) {
this.prevIndex = prevIndex;
this.index = nextIndex;
!edge && trigger(prev, 'beforeitemhide', [this]);
trigger(next, 'beforeitemshow', [this]);
}
this._transitioner = this._translate(Math.abs(this.percent), prev, !edge && next);
if (changed) {
!edge && trigger(prev, 'itemhide', [this]);
trigger(next, 'itemshow', [this]);
}
},
end() {
off(document, pointerMove, this.move, {passive: false});
off(document, `${pointerUp} ${pointerCancel} input`, this.end, true);
if (this.dragging) {
this.dragging = null;
if (this.index === this.prevIndex) {
this.percent = 1 - this.percent;
this.dir *= -1;
this._show(false, this.index, true);
this._transitioner = null;
} else {
const dirChange = (isRtl ? this.dir * (isRtl ? 1 : -1) : this.dir) < 0 === this.prevPos > this.pos;
this.index = dirChange ? this.index : this.prevIndex;
if (dirChange) {
this.percent = 1 - this.percent;
}
this.show(this.dir > 0 && !dirChange || this.dir < 0 && dirChange ? 'next' : 'previous', true);
}
}
css(this.list, {userSelect: '', pointerEvents: ''});
this.drag
= this.percent
= null;
}
}
};
function hasTextNodesOnly(el) {
return !el.children.length && el.childNodes.length;
}