uikit
Version:
UIkit is a lightweight and modular front-end framework for developing fast and powerful web interfaces.
786 lines (597 loc) • 22 kB
JavaScript
/*! UIkit 3.14.1 | https://www.getuikit.com | (c) 2014 - 2022 YOOtheme | MIT License */
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('uikit-util')) :
typeof define === 'function' && define.amd ? define('uikitsortable', ['uikit-util'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.UIkitSortable = factory(global.UIkit.util));
})(this, (function (uikitUtil) { 'use strict';
function getRows(items) {
return sortBy(items, 'top', 'bottom');
}
function sortBy(items, startProp, endProp) {
const sorted = [[]];
for (const el of items) {
if (!uikitUtil.isVisible(el)) {
continue;
}
let dim = getOffset(el);
for (let i = sorted.length - 1; i >= 0; i--) {
const current = sorted[i];
if (!current[0]) {
current.push(el);
break;
}
let startDim;
if (current[0].offsetParent === el.offsetParent) {
startDim = getOffset(current[0]);
} else {
dim = getOffset(el, true);
startDim = getOffset(current[0], true);
}
if (dim[startProp] >= startDim[endProp] - 1 && dim[startProp] !== startDim[startProp]) {
sorted.push([el]);
break;
}
if (dim[endProp] - 1 > startDim[startProp] || dim[startProp] === startDim[startProp]) {
current.push(el);
break;
}
if (i === 0) {
sorted.unshift([el]);
break;
}
}
}
return sorted;
}
function getOffset(element, offset) {if (offset === void 0) {offset = false;}
let { offsetTop, offsetLeft, offsetHeight, offsetWidth } = element;
if (offset) {
[offsetTop, offsetLeft] = uikitUtil.offsetPosition(element);
}
return {
top: offsetTop,
left: offsetLeft,
bottom: offsetTop + offsetHeight,
right: offsetLeft + offsetWidth };
}
const clsLeave = 'uk-transition-leave';
const clsEnter = 'uk-transition-enter';
function fade(action, target, duration, stagger) {if (stagger === void 0) {stagger = 0;}
const index = transitionIndex(target, true);
const propsIn = { opacity: 1 };
const propsOut = { opacity: 0 };
const wrapIndexFn = (fn) => () => index === transitionIndex(target) ? fn() : Promise.reject();
const leaveFn = wrapIndexFn(() => {
uikitUtil.addClass(target, clsLeave);
return Promise.all(
getTransitionNodes(target).map(
(child, i) =>
new Promise((resolve) =>
setTimeout(
() =>
uikitUtil.Transition.start(child, propsOut, duration / 2, 'ease').then(
resolve),
i * stagger)))).
then(() => uikitUtil.removeClass(target, clsLeave));
});
const enterFn = wrapIndexFn(() => {
const oldHeight = uikitUtil.height(target);
uikitUtil.addClass(target, clsEnter);
action();
uikitUtil.css(uikitUtil.children(target), { opacity: 0 });
// Ensure UIkit updates have propagated
return new Promise((resolve) =>
requestAnimationFrame(() => {
const nodes = uikitUtil.children(target);
const newHeight = uikitUtil.height(target);
// Ensure Grid cells do not stretch when height is applied
uikitUtil.css(target, 'alignContent', 'flex-start');
uikitUtil.height(target, oldHeight);
const transitionNodes = getTransitionNodes(target);
uikitUtil.css(nodes, propsOut);
const transitions = transitionNodes.map(
(child, i) =>
new Promise((resolve) =>
setTimeout(
() =>
uikitUtil.Transition.start(child, propsIn, duration / 2, 'ease').then(
resolve),
i * stagger)));
if (oldHeight !== newHeight) {
transitions.push(
uikitUtil.Transition.start(
target,
{ height: newHeight },
duration / 2 + transitionNodes.length * stagger,
'ease'));
}
Promise.all(transitions).then(() => {
uikitUtil.removeClass(target, clsEnter);
if (index === transitionIndex(target)) {
uikitUtil.css(target, { height: '', alignContent: '' });
uikitUtil.css(nodes, { opacity: '' });
delete target.dataset.transition;
}
resolve();
});
}));
});
return uikitUtil.hasClass(target, clsLeave) ?
waitTransitionend(target).then(enterFn) :
uikitUtil.hasClass(target, clsEnter) ?
waitTransitionend(target).then(leaveFn).then(enterFn) :
leaveFn().then(enterFn);
}
function transitionIndex(target, next) {
if (next) {
target.dataset.transition = 1 + transitionIndex(target);
}
return uikitUtil.toNumber(target.dataset.transition) || 0;
}
function waitTransitionend(target) {
return Promise.all(
uikitUtil.children(target).
filter(uikitUtil.Transition.inProgress).
map(
(el) =>
new Promise((resolve) => uikitUtil.once(el, 'transitionend transitioncanceled', resolve))));
}
function getTransitionNodes(target) {
return getRows(uikitUtil.children(target)).reduce(
(nodes, row) =>
nodes.concat(
uikitUtil.sortBy(
row.filter((el) => uikitUtil.isInView(el)),
'offsetLeft')),
[]);
}
function slide (action, target, duration) {
return new Promise((resolve) =>
requestAnimationFrame(() => {
let nodes = uikitUtil.children(target);
// Get current state
const currentProps = nodes.map((el) => getProps(el, true));
const targetProps = uikitUtil.css(target, ['height', 'padding']);
// Cancel previous animations
uikitUtil.Transition.cancel(target);
nodes.forEach(uikitUtil.Transition.cancel);
reset(target);
// Adding, sorting, removing nodes
action();
// Find new nodes
nodes = nodes.concat(uikitUtil.children(target).filter((el) => !uikitUtil.includes(nodes, el)));
// Wait for update to propagate
Promise.resolve().then(() => {
// Force update
uikitUtil.fastdom.flush();
// Get new state
const targetPropsTo = uikitUtil.css(target, ['height', 'padding']);
const [propsTo, propsFrom] = getTransitionProps(target, nodes, currentProps);
// Reset to previous state
nodes.forEach((el, i) => propsFrom[i] && uikitUtil.css(el, propsFrom[i]));
uikitUtil.css(target, { display: 'block', ...targetProps });
// Start transitions on next frame
requestAnimationFrame(() => {
const transitions = nodes.
map(
(el, i) =>
uikitUtil.parent(el) === target &&
uikitUtil.Transition.start(el, propsTo[i], duration, 'ease')).
concat(uikitUtil.Transition.start(target, targetPropsTo, duration, 'ease'));
Promise.all(transitions).
then(() => {
nodes.forEach(
(el, i) =>
uikitUtil.parent(el) === target &&
uikitUtil.css(el, 'display', propsTo[i].opacity === 0 ? 'none' : ''));
reset(target);
}, uikitUtil.noop).
then(resolve);
});
});
}));
}
function getProps(el, opacity) {
const zIndex = uikitUtil.css(el, 'zIndex');
return uikitUtil.isVisible(el) ?
{
display: '',
opacity: opacity ? uikitUtil.css(el, 'opacity') : '0',
pointerEvents: 'none',
position: 'absolute',
zIndex: zIndex === 'auto' ? uikitUtil.index(el) : zIndex,
...getPositionWithMargin(el) } :
false;
}
function getTransitionProps(target, nodes, currentProps) {
const propsTo = nodes.map((el, i) =>
uikitUtil.parent(el) && i in currentProps ?
currentProps[i] ?
uikitUtil.isVisible(el) ?
getPositionWithMargin(el) :
{ opacity: 0 } :
{ opacity: uikitUtil.isVisible(el) ? 1 : 0 } :
false);
const propsFrom = propsTo.map((props, i) => {
const from = uikitUtil.parent(nodes[i]) === target && (currentProps[i] || getProps(nodes[i]));
if (!from) {
return false;
}
if (!props) {
delete from.opacity;
} else if (!('opacity' in props)) {
const { opacity } = from;
if (opacity % 1) {
props.opacity = 1;
} else {
delete from.opacity;
}
}
return from;
});
return [propsTo, propsFrom];
}
function reset(el) {
uikitUtil.css(el.children, {
height: '',
left: '',
opacity: '',
pointerEvents: '',
position: '',
top: '',
marginTop: '',
marginLeft: '',
transform: '',
width: '',
zIndex: '' });
uikitUtil.css(el, { height: '', display: '', padding: '' });
}
function getPositionWithMargin(el) {
const { height, width } = uikitUtil.offset(el);
const { top, left } = uikitUtil.position(el);
const { marginLeft, marginTop } = uikitUtil.css(el, ['marginTop', 'marginLeft']);
return { top, left, height, width, marginLeft, marginTop, transform: '' };
}
var Animate = {
props: {
duration: Number,
animation: Boolean },
data: {
duration: 150,
animation: 'slide' },
methods: {
animate(action, target) {if (target === void 0) {target = this.$el;}
const name = this.animation;
const animationFn =
name === 'fade' ?
fade :
name === 'delayed-fade' ?
function () {for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {args[_key] = arguments[_key];}return fade(...args, 40);} :
name ?
slide :
() => {
action();
return Promise.resolve();
};
return animationFn(action, target, this.duration).then(
() => this.$update(target, 'resize'),
uikitUtil.noop);
} } };
var Class = {
connected() {
!uikitUtil.hasClass(this.$el, this.$name) && uikitUtil.addClass(this.$el, this.$name);
} };
var Component = {
mixins: [Class, Animate],
props: {
group: String,
threshold: Number,
clsItem: String,
clsPlaceholder: String,
clsDrag: String,
clsDragState: String,
clsBase: String,
clsNoDrag: String,
clsEmpty: String,
clsCustom: String,
handle: String },
data: {
group: false,
threshold: 5,
clsItem: 'uk-sortable-item',
clsPlaceholder: 'uk-sortable-placeholder',
clsDrag: 'uk-sortable-drag',
clsDragState: 'uk-drag',
clsBase: 'uk-sortable',
clsNoDrag: 'uk-sortable-nodrag',
clsEmpty: 'uk-sortable-empty',
clsCustom: '',
handle: false,
pos: {} },
created() {
for (const key of ['init', 'start', 'move', 'end']) {
const fn = this[key];
this[key] = (e) => {
uikitUtil.assign(this.pos, uikitUtil.getEventPos(e));
fn(e);
};
}
},
events: {
name: uikitUtil.pointerDown,
passive: false,
handler: 'init' },
computed: {
target() {
return (this.$el.tBodies || [this.$el])[0];
},
items() {
return uikitUtil.children(this.target);
},
isEmpty: {
get() {
return uikitUtil.isEmpty(this.items);
},
watch(empty) {
uikitUtil.toggleClass(this.target, this.clsEmpty, empty);
},
immediate: true },
handles: {
get(_ref, el) {let { handle } = _ref;
return handle ? uikitUtil.$$(handle, el) : this.items;
},
watch(handles, prev) {
uikitUtil.css(prev, { touchAction: '', userSelect: '' });
uikitUtil.css(handles, { touchAction: uikitUtil.hasTouch ? 'none' : '', userSelect: 'none' }); // touchAction set to 'none' causes a performance drop in Chrome 80
},
immediate: true } },
update: {
write(data) {
if (!this.drag || !uikitUtil.parent(this.placeholder)) {
return;
}
const {
pos: { x, y },
origin: { offsetTop, offsetLeft },
placeholder } =
this;
uikitUtil.css(this.drag, {
top: y - offsetTop,
left: x - offsetLeft });
const sortable = this.getSortable(document.elementFromPoint(x, y));
if (!sortable) {
return;
}
const { items } = sortable;
if (items.some(uikitUtil.Transition.inProgress)) {
return;
}
const target = findTarget(items, { x, y });
if (items.length && (!target || target === placeholder)) {
return;
}
const previous = this.getSortable(placeholder);
const insertTarget = findInsertTarget(
sortable.target,
target,
placeholder,
x,
y,
sortable === previous && data.moved !== target);
if (insertTarget === false) {
return;
}
if (insertTarget && placeholder === insertTarget) {
return;
}
if (sortable !== previous) {
previous.remove(placeholder);
data.moved = target;
} else {
delete data.moved;
}
sortable.insert(placeholder, insertTarget);
this.touched.add(sortable);
},
events: ['move'] },
methods: {
init(e) {
const { target, button, defaultPrevented } = e;
const [placeholder] = this.items.filter((el) => uikitUtil.within(target, el));
if (
!placeholder ||
defaultPrevented ||
button > 0 ||
uikitUtil.isInput(target) ||
uikitUtil.within(target, "." + this.clsNoDrag) ||
this.handle && !uikitUtil.within(target, this.handle))
{
return;
}
e.preventDefault();
this.touched = new Set([this]);
this.placeholder = placeholder;
this.origin = { target, index: uikitUtil.index(placeholder), ...this.pos };
uikitUtil.on(document, uikitUtil.pointerMove, this.move);
uikitUtil.on(document, uikitUtil.pointerUp, this.end);
if (!this.threshold) {
this.start(e);
}
},
start(e) {
this.drag = appendDrag(this.$container, this.placeholder);
const { left, top } = this.placeholder.getBoundingClientRect();
uikitUtil.assign(this.origin, { offsetLeft: this.pos.x - left, offsetTop: this.pos.y - top });
uikitUtil.addClass(this.drag, this.clsDrag, this.clsCustom);
uikitUtil.addClass(this.placeholder, this.clsPlaceholder);
uikitUtil.addClass(this.items, this.clsItem);
uikitUtil.addClass(document.documentElement, this.clsDragState);
uikitUtil.trigger(this.$el, 'start', [this, this.placeholder]);
trackScroll(this.pos);
this.move(e);
},
move(e) {
if (this.drag) {
this.$emit('move');
} else if (
Math.abs(this.pos.x - this.origin.x) > this.threshold ||
Math.abs(this.pos.y - this.origin.y) > this.threshold)
{
this.start(e);
}
},
end() {
uikitUtil.off(document, uikitUtil.pointerMove, this.move);
uikitUtil.off(document, uikitUtil.pointerUp, this.end);
if (!this.drag) {
return;
}
untrackScroll();
const sortable = this.getSortable(this.placeholder);
if (this === sortable) {
if (this.origin.index !== uikitUtil.index(this.placeholder)) {
uikitUtil.trigger(this.$el, 'moved', [this, this.placeholder]);
}
} else {
uikitUtil.trigger(sortable.$el, 'added', [sortable, this.placeholder]);
uikitUtil.trigger(this.$el, 'removed', [this, this.placeholder]);
}
uikitUtil.trigger(this.$el, 'stop', [this, this.placeholder]);
uikitUtil.remove(this.drag);
this.drag = null;
for (const { clsPlaceholder, clsItem } of this.touched) {
for (const sortable of this.touched) {
uikitUtil.removeClass(sortable.items, clsPlaceholder, clsItem);
}
}
this.touched = null;
uikitUtil.removeClass(document.documentElement, this.clsDragState);
},
insert(element, target) {
uikitUtil.addClass(this.items, this.clsItem);
const insert = () => target ? uikitUtil.before(target, element) : uikitUtil.append(this.target, element);
this.animate(insert);
},
remove(element) {
if (!uikitUtil.within(element, this.target)) {
return;
}
this.animate(() => uikitUtil.remove(element));
},
getSortable(element) {
do {
const sortable = this.$getComponent(element, 'sortable');
if (
sortable && (
sortable === this || this.group !== false && sortable.group === this.group))
{
return sortable;
}
} while (element = uikitUtil.parent(element));
} } };
let trackTimer;
function trackScroll(pos) {
let last = Date.now();
trackTimer = setInterval(() => {
let { x, y } = pos;
y += uikitUtil.scrollTop(window);
const dist = (Date.now() - last) * 0.3;
last = Date.now();
uikitUtil.scrollParents(document.elementFromPoint(x, pos.y), /auto|scroll/).
reverse().
some((scrollEl) => {
let { scrollTop: scroll, scrollHeight } = scrollEl;
const { top, bottom, height } = uikitUtil.offsetViewport(scrollEl);
if (top < y && top + 35 > y) {
scroll -= dist;
} else if (bottom > y && bottom - 35 < y) {
scroll += dist;
} else {
return;
}
if (scroll > 0 && scroll < scrollHeight - height) {
uikitUtil.scrollTop(scrollEl, scroll);
return true;
}
});
}, 15);
}
function untrackScroll() {
clearInterval(trackTimer);
}
function appendDrag(container, element) {
const clone = uikitUtil.append(
container,
element.outerHTML.replace(/(^<)(?:li|tr)|(?:li|tr)(\/>$)/g, '$1div$2'));
uikitUtil.css(clone, 'margin', '0', 'important');
uikitUtil.css(clone, {
boxSizing: 'border-box',
width: element.offsetWidth,
height: element.offsetHeight,
padding: uikitUtil.css(element, 'padding') });
uikitUtil.height(clone.firstElementChild, uikitUtil.height(element.firstElementChild));
return clone;
}
function findTarget(items, point) {
return items[uikitUtil.findIndex(items, (item) => uikitUtil.pointInRect(point, item.getBoundingClientRect()))];
}
function findInsertTarget(list, target, placeholder, x, y, sameList) {
if (!uikitUtil.children(list).length) {
return;
}
const rect = target.getBoundingClientRect();
if (!sameList) {
if (!isHorizontal(list, placeholder)) {
return y < rect.top + rect.height / 2 ? target : target.nextElementSibling;
}
return target;
}
const placeholderRect = placeholder.getBoundingClientRect();
const sameRow = linesIntersect(
[rect.top, rect.bottom],
[placeholderRect.top, placeholderRect.bottom]);
const pointerPos = sameRow ? x : y;
const lengthProp = sameRow ? 'width' : 'height';
const startProp = sameRow ? 'left' : 'top';
const endProp = sameRow ? 'right' : 'bottom';
const diff =
placeholderRect[lengthProp] < rect[lengthProp] ?
rect[lengthProp] - placeholderRect[lengthProp] :
0;
if (placeholderRect[startProp] < rect[startProp]) {
if (diff && pointerPos < rect[startProp] + diff) {
return false;
}
return target.nextElementSibling;
}
if (diff && pointerPos > rect[endProp] - diff) {
return false;
}
return target;
}
function isHorizontal(list, placeholder) {
const single = uikitUtil.children(list).length === 1;
if (single) {
uikitUtil.append(list, placeholder);
}
const items = uikitUtil.children(list);
const isHorizontal = items.some((el, i) => {
const rectA = el.getBoundingClientRect();
return items.slice(i + 1).some((el) => {
const rectB = el.getBoundingClientRect();
return !linesIntersect([rectA.left, rectA.right], [rectB.left, rectB.right]);
});
});
if (single) {
uikitUtil.remove(placeholder);
}
return isHorizontal;
}
function linesIntersect(lineA, lineB) {
return lineA[1] > lineB[0] && lineB[1] > lineA[0];
}
if (typeof window !== 'undefined' && window.UIkit) {
window.UIkit.component('sortable', Component);
}
return Component;
}));