UNPKG

uikit

Version:

UIkit is a lightweight and modular front-end framework for developing fast and powerful web interfaces.

671 lines (658 loc) • 23.8 kB
/*! UIkit 3.21.0 | https://www.getuikit.com | (c) 2014 - 2024 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 callUpdate(instance, e = "update") { if (!instance._connected) { return; } if (!instance._updates.length) { return; } if (!instance._queued) { instance._queued = /* @__PURE__ */ new Set(); uikitUtil.fastdom.read(() => { if (instance._connected) { runUpdates(instance, instance._queued); } delete instance._queued; }); } instance._queued.add(e.type || e); } function runUpdates(instance, types) { for (const { read, write, events = [] } of instance._updates) { if (!types.has("update") && !events.some((type) => types.has(type))) { continue; } let result; if (read) { result = read.call(instance, instance._data, types); if (result && uikitUtil.isPlainObject(result)) { uikitUtil.assign(instance._data, result); } } if (write && result !== false) { uikitUtil.fastdom.write(() => { if (instance._connected) { write.call(instance, instance._data, types); } }); } } } function resize(options) { return observe(uikitUtil.observeResize, options, "resize"); } function mutation(options) { return observe(uikitUtil.observeMutation, options); } function observe(observe2, options, emit) { return { observe: observe2, handler() { callUpdate(this, emit); }, ...options }; } ({ props: { margin: String, firstColumn: Boolean }, data: { margin: "uk-margin-small-top", firstColumn: "uk-first-column" }, observe: [ mutation({ options: { childList: true } }), mutation({ options: { attributes: true, attributeFilter: ["style"] }, target: ({ $el }) => [$el, ...uikitUtil.children($el)] }), resize({ target: ({ $el }) => [$el, ...uikitUtil.children($el)] }) ], update: { read() { return { rows: getRows(uikitUtil.children(this.$el)) }; }, write({ rows }) { for (const row of rows) { for (const el of row) { uikitUtil.toggleClass(el, this.margin, rows[0] !== row); uikitUtil.toggleClass(el, this.firstColumn, row[uikitUtil.isRtl ? row.length - 1 : 0] === el); } } }, events: ["resize"] } }); function getRows(elements) { const sorted = [[]]; const withOffset = elements.some( (el, i) => i && elements[i - 1].offsetParent !== el.offsetParent ); for (const el of elements) { if (!uikitUtil.isVisible(el)) { continue; } const offset = getOffset(el, withOffset); for (let i = sorted.length - 1; i >= 0; i--) { const current = sorted[i]; if (!current[0]) { current.push(el); break; } const offsetCurrent = getOffset(current[0], withOffset); if (offset.top >= offsetCurrent.bottom - 1 && offset.top !== offsetCurrent.top) { sorted.push([el]); break; } if (offset.bottom - 1 > offsetCurrent.top || offset.top === offsetCurrent.top) { let j = current.length - 1; for (; j >= 0; j--) { const offsetCurrent2 = getOffset(current[j], withOffset); if (offset.left >= offsetCurrent2.left) { break; } } current.splice(j + 1, 0, el); break; } if (i === 0) { sorted.unshift([el]); break; } } } return sorted; } function getOffset(element, 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 }; } async function slide(action, target, duration) { await awaitFrame(); let nodes = uikitUtil.children(target); const currentProps = nodes.map((el) => getProps(el, true)); const targetProps = { ...uikitUtil.css(target, ["height", "padding"]), display: "block" }; const targets = nodes.concat(target); await Promise.all(targets.map(uikitUtil.Transition.cancel)); uikitUtil.css(targets, "transitionProperty", "none"); await action(); nodes = nodes.concat(uikitUtil.children(target).filter((el) => !uikitUtil.includes(nodes, el))); await Promise.resolve(); uikitUtil.css(targets, "transitionProperty", ""); const targetStyle = uikitUtil.attr(target, "style"); const targetPropsTo = uikitUtil.css(target, ["height", "padding"]); const [propsTo, propsFrom] = getTransitionProps(target, nodes, currentProps); const attrsTo = nodes.map((el) => ({ style: uikitUtil.attr(el, "style") })); nodes.forEach((el, i) => propsFrom[i] && uikitUtil.css(el, propsFrom[i])); uikitUtil.css(target, targetProps); uikitUtil.trigger(target, "scroll"); await awaitFrame(); 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")); try { await Promise.all(transitions); nodes.forEach((el, i) => { uikitUtil.attr(el, attrsTo[i]); if (uikitUtil.parent(el) === target) { uikitUtil.css(el, "display", propsTo[i].opacity === 0 ? "none" : ""); } }); uikitUtil.attr(target, "style", targetStyle); } catch (e) { uikitUtil.attr(nodes, "style", ""); resetProps(target, targetProps); } } 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 resetProps(el, props) { for (const prop in props) { uikitUtil.css(el, prop, ""); } } function getPositionWithMargin(el) { const { height, width } = uikitUtil.offset(el); return { height, width, transform: "", ...uikitUtil.position(el), ...uikitUtil.css(el, ["marginTop", "marginLeft"]) }; } function awaitFrame() { return new Promise((resolve) => requestAnimationFrame(resolve)); } const clsLeave = "uk-transition-leave"; const clsEnter = "uk-transition-enter"; function fade(action, target, duration, 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(async () => { uikitUtil.addClass(target, clsLeave); await Promise.all( getTransitionNodes(target).map( (child, i) => new Promise( (resolve) => setTimeout( () => uikitUtil.Transition.start(child, propsOut, duration / 2, "ease").then( resolve ), i * stagger ) ) ) ); uikitUtil.removeClass(target, clsLeave); }); const enterFn = wrapIndexFn(async () => { const oldHeight = uikitUtil.height(target); uikitUtil.addClass(target, clsEnter); action(); uikitUtil.css(uikitUtil.children(target), { opacity: 0 }); await awaitFrame(); const nodes = uikitUtil.children(target); const newHeight = uikitUtil.height(target); uikitUtil.css(target, "alignContent", "flex-start"); uikitUtil.height(target, oldHeight); const transitionNodes = getTransitionNodes(target); uikitUtil.css(nodes, propsOut); const transitions = transitionNodes.map(async (child, i) => { await awaitTimeout(i * stagger); await uikitUtil.Transition.start(child, propsIn, duration / 2, "ease"); }); if (oldHeight !== newHeight) { transitions.push( uikitUtil.Transition.start( target, { height: newHeight }, duration / 2 + transitionNodes.length * stagger, "ease" ) ); } await 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; } }); }); 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)).flat().filter(uikitUtil.isVisible); } function awaitTimeout(timeout) { return new Promise((resolve) => setTimeout(resolve, timeout)); } var Animate = { props: { duration: Number, animation: Boolean }, data: { duration: 150, animation: "slide" }, methods: { animate(action, target = this.$el) { const name = this.animation; const animationFn = name === "fade" ? fade : name === "delayed-fade" ? (...args) => fade(...args, 40) : name ? slide : () => { action(); return Promise.resolve(); }; return animationFn(action, target, this.duration).catch(uikitUtil.noop); } } }; var Class = { connected() { uikitUtil.addClass(this.$el, this.$options.id); } }; 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: {} }, events: { name: uikitUtil.pointerDown, passive: false, handler: "init" }, computed: { target: (_, $el) => ($el.tBodies || [$el])[0], items() { return uikitUtil.children(this.target); }, isEmpty() { return !this.items.length; }, handles({ handle }, $el) { return handle ? uikitUtil.$$(handle, $el) : this.items; } }, watch: { isEmpty(empty) { uikitUtil.toggleClass(this.target, this.clsEmpty, empty); }, handles(handles, prev) { uikitUtil.css(prev, { touchAction: "", userSelect: "" }); uikitUtil.css(handles, { touchAction: "none", userSelect: "none" }); } }, 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) => el.contains(target)); if (!placeholder || defaultPrevented || button > 0 || uikitUtil.isInput(target) || target.closest(`.${this.clsNoDrag}`) || this.handle && !target.closest(this.handle)) { return; } e.preventDefault(); this.pos = uikitUtil.getEventPos(e); this.touched = /* @__PURE__ */ 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 } = uikitUtil.dimensions(this.placeholder); 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: throttle(function(e) { uikitUtil.assign(this.pos, uikitUtil.getEventPos(e)); if (!this.drag && (Math.abs(this.pos.x - this.origin.x) > this.threshold || Math.abs(this.pos.y - this.origin.y) > this.threshold)) { this.start(e); } this.$emit("move"); }), 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 sortable2 of this.touched) { uikitUtil.removeClass(sortable2.items, clsPlaceholder, clsItem); } } this.touched = null; uikitUtil.removeClass(document.documentElement, this.clsDragState); }, insert(element, target) { uikitUtil.addClass(this.items, this.clsItem); if (target && target.previousElementSibling !== element) { this.animate(() => uikitUtil.before(target, element)); } else if (!target && this.target.lastElementChild !== element) { this.animate(() => uikitUtil.append(this.target, element)); } }, remove(element) { if (this.target.contains(element)) { 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 += document.scrollingElement.scrollTop; const dist = (Date.now() - last) * 0.3; last = Date.now(); uikitUtil.scrollParents(document.elementFromPoint(x, pos.y)).reverse().some((scrollEl) => { let { scrollTop: scroll, scrollHeight } = scrollEl; const { top, bottom, height: height2 } = 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 - height2) { scrollEl.scrollTop = scroll; return true; } }); }, 15); } function untrackScroll() { clearInterval(trackTimer); } function appendDrag(container, element) { let clone; if (uikitUtil.isTag(element, "li", "tr")) { clone = uikitUtil.$("<div>"); uikitUtil.append(clone, element.cloneNode(true).children); for (const attribute of element.getAttributeNames()) { uikitUtil.attr(clone, attribute, element.getAttribute(attribute)); } } else { clone = element.cloneNode(true); } uikitUtil.append(container, clone); 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, uikitUtil.dimensions(item)))]; } function findInsertTarget(list, target, placeholder, x, y, sameList) { if (!uikitUtil.children(list).length) { return; } const rect = uikitUtil.dimensions(target); if (!sameList) { if (!isHorizontal(list, placeholder)) { return y < rect.top + rect.height / 2 ? target : target.nextElementSibling; } return target; } const placeholderRect = uikitUtil.dimensions(placeholder); const sameRow = linesIntersect( [rect.top, rect.bottom], [placeholderRect.top, placeholderRect.bottom] ); const [pointerPos, lengthProp, startProp, endProp] = sameRow ? [x, "width", "left", "right"] : [y, "height", "top", "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 isHorizontal2 = items.some((el, i) => { const rectA = uikitUtil.dimensions(el); return items.slice(i + 1).some((el2) => { const rectB = uikitUtil.dimensions(el2); return !linesIntersect([rectA.left, rectA.right], [rectB.left, rectB.right]); }); }); if (single) { uikitUtil.remove(placeholder); } return isHorizontal2; } function linesIntersect(lineA, lineB) { return lineA[1] > lineB[0] && lineB[1] > lineA[0]; } function throttle(fn) { let throttled; return function(...args) { if (!throttled) { throttled = true; fn.call(this, ...args); requestAnimationFrame(() => throttled = false); } }; } if (typeof window !== "undefined" && window.UIkit) { window.UIkit.component("sortable", Component); } return Component; }));