aboutsummaryrefslogtreecommitdiffstats
path: root/library/Sortable/src/utils.js
diff options
context:
space:
mode:
Diffstat (limited to 'library/Sortable/src/utils.js')
-rw-r--r--library/Sortable/src/utils.js556
1 files changed, 556 insertions, 0 deletions
diff --git a/library/Sortable/src/utils.js b/library/Sortable/src/utils.js
new file mode 100644
index 000000000..cdb8cc774
--- /dev/null
+++ b/library/Sortable/src/utils.js
@@ -0,0 +1,556 @@
+import { IE11OrLess } from './BrowserInfo.js';
+import Sortable from './Sortable.js';
+
+const captureMode = {
+ capture: false,
+ passive: false
+};
+
+function on(el, event, fn) {
+ el.addEventListener(event, fn, !IE11OrLess && captureMode);
+}
+
+
+function off(el, event, fn) {
+ el.removeEventListener(event, fn, !IE11OrLess && captureMode);
+}
+
+function matches(/**HTMLElement*/el, /**String*/selector) {
+ if (!selector) return;
+
+ selector[0] === '>' && (selector = selector.substring(1));
+
+ if (el) {
+ try {
+ if (el.matches) {
+ return el.matches(selector);
+ } else if (el.msMatchesSelector) {
+ return el.msMatchesSelector(selector);
+ } else if (el.webkitMatchesSelector) {
+ return el.webkitMatchesSelector(selector);
+ }
+ } catch(_) {
+ return false;
+ }
+ }
+
+ return false;
+}
+
+function getParentOrHost(el) {
+ return (el.host && el !== document && el.host.nodeType)
+ ? el.host
+ : el.parentNode;
+}
+
+function closest(/**HTMLElement*/el, /**String*/selector, /**HTMLElement*/ctx, includeCTX) {
+ if (el) {
+ ctx = ctx || document;
+
+ do {
+ if (
+ selector != null &&
+ (
+ selector[0] === '>' ?
+ el.parentNode === ctx && matches(el, selector) :
+ matches(el, selector)
+ ) ||
+ includeCTX && el === ctx
+ ) {
+ return el;
+ }
+
+ if (el === ctx) break;
+ /* jshint boss:true */
+ } while (el = getParentOrHost(el));
+ }
+
+ return null;
+}
+
+const R_SPACE = /\s+/g;
+
+function toggleClass(el, name, state) {
+ if (el && name) {
+ if (el.classList) {
+ el.classList[state ? 'add' : 'remove'](name);
+ }
+ else {
+ let className = (' ' + el.className + ' ').replace(R_SPACE, ' ').replace(' ' + name + ' ', ' ');
+ el.className = (className + (state ? ' ' + name : '')).replace(R_SPACE, ' ');
+ }
+ }
+}
+
+
+function css(el, prop, val) {
+ let style = el && el.style;
+
+ if (style) {
+ if (val === void 0) {
+ if (document.defaultView && document.defaultView.getComputedStyle) {
+ val = document.defaultView.getComputedStyle(el, '');
+ }
+ else if (el.currentStyle) {
+ val = el.currentStyle;
+ }
+
+ return prop === void 0 ? val : val[prop];
+ }
+ else {
+ if (!(prop in style) && prop.indexOf('webkit') === -1) {
+ prop = '-webkit-' + prop;
+ }
+
+ style[prop] = val + (typeof val === 'string' ? '' : 'px');
+ }
+ }
+}
+
+function matrix(el, selfOnly) {
+ let appliedTransforms = '';
+ if (typeof(el) === 'string') {
+ appliedTransforms = el;
+ } else {
+ do {
+ let transform = css(el, 'transform');
+
+ if (transform && transform !== 'none') {
+ appliedTransforms = transform + ' ' + appliedTransforms;
+ }
+ /* jshint boss:true */
+ } while (!selfOnly && (el = el.parentNode));
+ }
+
+ const matrixFn = window.DOMMatrix || window.WebKitCSSMatrix || window.CSSMatrix || window.MSCSSMatrix;
+ /*jshint -W056 */
+ return matrixFn && (new matrixFn(appliedTransforms));
+}
+
+
+function find(ctx, tagName, iterator) {
+ if (ctx) {
+ let list = ctx.getElementsByTagName(tagName), i = 0, n = list.length;
+
+ if (iterator) {
+ for (; i < n; i++) {
+ iterator(list[i], i);
+ }
+ }
+
+ return list;
+ }
+
+ return [];
+}
+
+
+
+function getWindowScrollingElement() {
+ let scrollingElement = document.scrollingElement;
+
+ if (scrollingElement) {
+ return scrollingElement
+ } else {
+ return document.documentElement
+ }
+}
+
+
+/**
+ * Returns the "bounding client rect" of given element
+ * @param {HTMLElement} el The element whose boundingClientRect is wanted
+ * @param {[Boolean]} relativeToContainingBlock Whether the rect should be relative to the containing block of (including) the container
+ * @param {[Boolean]} relativeToNonStaticParent Whether the rect should be relative to the relative parent of (including) the contaienr
+ * @param {[Boolean]} undoScale Whether the container's scale() should be undone
+ * @param {[HTMLElement]} container The parent the element will be placed in
+ * @return {Object} The boundingClientRect of el, with specified adjustments
+ */
+function getRect(el, relativeToContainingBlock, relativeToNonStaticParent, undoScale, container) {
+ if (!el.getBoundingClientRect && el !== window) return;
+
+ let elRect,
+ top,
+ left,
+ bottom,
+ right,
+ height,
+ width;
+
+ if (el !== window && el.parentNode && el !== getWindowScrollingElement()) {
+ elRect = el.getBoundingClientRect();
+ top = elRect.top;
+ left = elRect.left;
+ bottom = elRect.bottom;
+ right = elRect.right;
+ height = elRect.height;
+ width = elRect.width;
+ } else {
+ top = 0;
+ left = 0;
+ bottom = window.innerHeight;
+ right = window.innerWidth;
+ height = window.innerHeight;
+ width = window.innerWidth;
+ }
+
+ if ((relativeToContainingBlock || relativeToNonStaticParent) && el !== window) {
+ // Adjust for translate()
+ container = container || el.parentNode;
+
+ // solves #1123 (see: https://stackoverflow.com/a/37953806/6088312)
+ // Not needed on <= IE11
+ if (!IE11OrLess) {
+ do {
+ if (
+ container &&
+ container.getBoundingClientRect &&
+ (
+ css(container, 'transform') !== 'none' ||
+ relativeToNonStaticParent &&
+ css(container, 'position') !== 'static'
+ )
+ ) {
+ let containerRect = container.getBoundingClientRect();
+
+ // Set relative to edges of padding box of container
+ top -= containerRect.top + parseInt(css(container, 'border-top-width'));
+ left -= containerRect.left + parseInt(css(container, 'border-left-width'));
+ bottom = top + elRect.height;
+ right = left + elRect.width;
+
+ break;
+ }
+ /* jshint boss:true */
+ } while (container = container.parentNode);
+ }
+ }
+
+ if (undoScale && el !== window) {
+ // Adjust for scale()
+ let elMatrix = matrix(container || el),
+ scaleX = elMatrix && elMatrix.a,
+ scaleY = elMatrix && elMatrix.d;
+
+ if (elMatrix) {
+ top /= scaleY;
+ left /= scaleX;
+
+ width /= scaleX;
+ height /= scaleY;
+
+ bottom = top + height;
+ right = left + width;
+ }
+ }
+
+ return {
+ top: top,
+ left: left,
+ bottom: bottom,
+ right: right,
+ width: width,
+ height: height
+ };
+}
+
+/**
+ * Checks if a side of an element is scrolled past a side of its parents
+ * @param {HTMLElement} el The element who's side being scrolled out of view is in question
+ * @param {String} elSide Side of the element in question ('top', 'left', 'right', 'bottom')
+ * @param {String} parentSide Side of the parent in question ('top', 'left', 'right', 'bottom')
+ * @return {HTMLElement} The parent scroll element that the el's side is scrolled past, or null if there is no such element
+ */
+function isScrolledPast(el, elSide, parentSide) {
+ let parent = getParentAutoScrollElement(el, true),
+ elSideVal = getRect(el)[elSide];
+
+ /* jshint boss:true */
+ while (parent) {
+ let parentSideVal = getRect(parent)[parentSide],
+ visible;
+
+ if (parentSide === 'top' || parentSide === 'left') {
+ visible = elSideVal >= parentSideVal;
+ } else {
+ visible = elSideVal <= parentSideVal;
+ }
+
+ if (!visible) return parent;
+
+ if (parent === getWindowScrollingElement()) break;
+
+ parent = getParentAutoScrollElement(parent, false);
+ }
+
+ return false;
+}
+
+
+
+/**
+ * Gets nth child of el, ignoring hidden children, sortable's elements (does not ignore clone if it's visible)
+ * and non-draggable elements
+ * @param {HTMLElement} el The parent element
+ * @param {Number} childNum The index of the child
+ * @param {Object} options Parent Sortable's options
+ * @return {HTMLElement} The child at index childNum, or null if not found
+ */
+function getChild(el, childNum, options, includeDragEl) {
+ let currentChild = 0,
+ i = 0,
+ children = el.children;
+
+ while (i < children.length) {
+ if (
+ children[i].style.display !== 'none' &&
+ children[i] !== Sortable.ghost &&
+ (includeDragEl || children[i] !== Sortable.dragged) &&
+ closest(children[i], options.draggable, el, false)
+ ) {
+ if (currentChild === childNum) {
+ return children[i];
+ }
+ currentChild++;
+ }
+
+ i++;
+ }
+ return null;
+}
+
+/**
+ * Gets the last child in the el, ignoring ghostEl or invisible elements (clones)
+ * @param {HTMLElement} el Parent element
+ * @param {selector} selector Any other elements that should be ignored
+ * @return {HTMLElement} The last child, ignoring ghostEl
+ */
+function lastChild(el, selector) {
+ let last = el.lastElementChild;
+
+ while (
+ last &&
+ (
+ last === Sortable.ghost ||
+ css(last, 'display') === 'none' ||
+ selector && !matches(last, selector)
+ )
+ ) {
+ last = last.previousElementSibling;
+ }
+
+ return last || null;
+}
+
+
+/**
+ * Returns the index of an element within its parent for a selected set of
+ * elements
+ * @param {HTMLElement} el
+ * @param {selector} selector
+ * @return {number}
+ */
+function index(el, selector) {
+ let index = 0;
+
+ if (!el || !el.parentNode) {
+ return -1;
+ }
+
+ /* jshint boss:true */
+ while (el = el.previousElementSibling) {
+ if ((el.nodeName.toUpperCase() !== 'TEMPLATE') && el !== Sortable.clone && (!selector || matches(el, selector))) {
+ index++;
+ }
+ }
+
+ return index;
+}
+
+/**
+ * Returns the scroll offset of the given element, added with all the scroll offsets of parent elements.
+ * The value is returned in real pixels.
+ * @param {HTMLElement} el
+ * @return {Array} Offsets in the format of [left, top]
+ */
+function getRelativeScrollOffset(el) {
+ let offsetLeft = 0,
+ offsetTop = 0,
+ winScroller = getWindowScrollingElement();
+
+ if (el) {
+ do {
+ let elMatrix = matrix(el),
+ scaleX = elMatrix.a,
+ scaleY = elMatrix.d;
+
+ offsetLeft += el.scrollLeft * scaleX;
+ offsetTop += el.scrollTop * scaleY;
+ } while (el !== winScroller && (el = el.parentNode));
+ }
+
+ return [offsetLeft, offsetTop];
+}
+
+/**
+ * Returns the index of the object within the given array
+ * @param {Array} arr Array that may or may not hold the object
+ * @param {Object} obj An object that has a key-value pair unique to and identical to a key-value pair in the object you want to find
+ * @return {Number} The index of the object in the array, or -1
+ */
+function indexOfObject(arr, obj) {
+ for (let i in arr) {
+ if (!arr.hasOwnProperty(i)) continue;
+ for (let key in obj) {
+ if (obj.hasOwnProperty(key) && obj[key] === arr[i][key]) return Number(i);
+ }
+ }
+ return -1;
+}
+
+
+function getParentAutoScrollElement(el, includeSelf) {
+ // skip to window
+ if (!el || !el.getBoundingClientRect) return getWindowScrollingElement();
+
+ let elem = el;
+ let gotSelf = false;
+ do {
+ // we don't need to get elem css if it isn't even overflowing in the first place (performance)
+ if (elem.clientWidth < elem.scrollWidth || elem.clientHeight < elem.scrollHeight) {
+ let elemCSS = css(elem);
+ if (
+ elem.clientWidth < elem.scrollWidth && (elemCSS.overflowX == 'auto' || elemCSS.overflowX == 'scroll') ||
+ elem.clientHeight < elem.scrollHeight && (elemCSS.overflowY == 'auto' || elemCSS.overflowY == 'scroll')
+ ) {
+ if (!elem.getBoundingClientRect || elem === document.body) return getWindowScrollingElement();
+
+ if (gotSelf || includeSelf) return elem;
+ gotSelf = true;
+ }
+ }
+ /* jshint boss:true */
+ } while (elem = elem.parentNode);
+
+ return getWindowScrollingElement();
+}
+
+function extend(dst, src) {
+ if (dst && src) {
+ for (let key in src) {
+ if (src.hasOwnProperty(key)) {
+ dst[key] = src[key];
+ }
+ }
+ }
+
+ return dst;
+}
+
+
+function isRectEqual(rect1, rect2) {
+ return Math.round(rect1.top) === Math.round(rect2.top) &&
+ Math.round(rect1.left) === Math.round(rect2.left) &&
+ Math.round(rect1.height) === Math.round(rect2.height) &&
+ Math.round(rect1.width) === Math.round(rect2.width);
+}
+
+
+let _throttleTimeout;
+function throttle(callback, ms) {
+ return function () {
+ if (!_throttleTimeout) {
+ let args = arguments,
+ _this = this;
+
+ if (args.length === 1) {
+ callback.call(_this, args[0]);
+ } else {
+ callback.apply(_this, args);
+ }
+
+ _throttleTimeout = setTimeout(function () {
+ _throttleTimeout = void 0;
+ }, ms);
+ }
+ };
+}
+
+
+function cancelThrottle() {
+ clearTimeout(_throttleTimeout);
+ _throttleTimeout = void 0;
+}
+
+
+function scrollBy(el, x, y) {
+ el.scrollLeft += x;
+ el.scrollTop += y;
+}
+
+
+function clone(el) {
+ let Polymer = window.Polymer;
+ let $ = window.jQuery || window.Zepto;
+
+ if (Polymer && Polymer.dom) {
+ return Polymer.dom(el).cloneNode(true);
+ }
+ else if ($) {
+ return $(el).clone(true)[0];
+ }
+ else {
+ return el.cloneNode(true);
+ }
+}
+
+
+function setRect(el, rect) {
+ css(el, 'position', 'absolute');
+ css(el, 'top', rect.top);
+ css(el, 'left', rect.left);
+ css(el, 'width', rect.width);
+ css(el, 'height', rect.height);
+}
+
+function unsetRect(el) {
+ css(el, 'position', '');
+ css(el, 'top', '');
+ css(el, 'left', '');
+ css(el, 'width', '');
+ css(el, 'height', '');
+}
+
+
+const expando = 'Sortable' + (new Date).getTime();
+
+
+export {
+ on,
+ off,
+ matches,
+ getParentOrHost,
+ closest,
+ toggleClass,
+ css,
+ matrix,
+ find,
+ getWindowScrollingElement,
+ getRect,
+ isScrolledPast,
+ getChild,
+ lastChild,
+ index,
+ getRelativeScrollOffset,
+ indexOfObject,
+ getParentAutoScrollElement,
+ extend,
+ isRectEqual,
+ throttle,
+ cancelThrottle,
+ scrollBy,
+ clone,
+ setRect,
+ unsetRect,
+ expando
+};