PageSourceSearch

https://amnistreasury.com/wp-content/themes/hello-elementor/assets/js/mixitup.js?ver=7.1.2

js amnistreasury.com collected 2026-10-01 14:31:27 UTC 352,820 bytes, 10,682 lines download raw bytes

1/**!
2 * MixItUp v3.3.1
3 * A high-performance, dependency-free library for animated filtering, sorting and more
4 * Build 94e0fbf6-cd0b-4987-b3c0-14b59b67b8a0
5 *
6 * @copyright Copyright 2014-2018 KunkaLabs Limited.
7 * @author    KunkaLabs Limited.
8 * @link      https://www.kunkalabs.com/mixitup/
9 *
10 * @license   Commercial use requires a commercial license.
11 *            https://www.kunkalabs.com/mixitup/licenses/
12 *
13 *            Non-commercial use permitted under same terms as CC BY-NC 3.0 license.
14 *            http://creativecommons.org/licenses/by-nc/3.0/
vendor: 52,211 bytes, lines 14-1794
14
15 */
16
17(function(window) {
18    'use strict';
19
20    var mixitup = null,
21        h       = null;
22
23    (function() {
24        var VENDORS = ['webkit', 'moz', 'o', 'ms'],
25            canary  = window.document.createElement('div'),
26            i       = -1;
27
28        // window.requestAnimationFrame
29
30        for (i = 0; i < VENDORS.length && !window.requestAnimationFrame; i++) {
31            window.requestAnimationFrame = window[VENDORS[i] + 'RequestAnimationFrame'];
32        }
33
34        // Element.nextElementSibling
35
36        if (typeof canary.nextElementSibling === 'undefined') {
37            Object.defineProperty(window.Element.prototype, 'nextElementSibling', {
38                get: function() {
39                    var el = this.nextSibling;
40
41                    while (el) {
42                        if (el.nodeType === 1) {
43                            return el;
44                        }
45
46                        el = el.nextSibling;
47                    }
48
49                    return null;
50                }
51            });
52        }
53
54        // Element.matches
55
56        (function(ElementPrototype) {
57            ElementPrototype.matches =
58                ElementPrototype.matches ||
59                ElementPrototype.machesSelector ||
60                ElementPrototype.mozMatchesSelector ||
61                ElementPrototype.msMatchesSelector ||
62                ElementPrototype.oMatchesSelector ||
63                ElementPrototype.webkitMatchesSelector ||
64                function (selector) {
65                    return Array.prototype.indexOf.call(this.parentElement.querySelectorAll(selector), this) > -1;
66                };
67        })(window.Element.prototype);
68
69        // Object.keys
70        // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
71
72        if (!Object.keys) {
73            Object.keys = (function() {
74                var hasOwnProperty      = Object.prototype.hasOwnProperty,
75                    hasDontEnumBug      = false,
76                    dontEnums           = [],
77                    dontEnumsLength     = -1;
78
79                hasDontEnumBug = !({
80                    toString: null
81                })
82                    .propertyIsEnumerable('toString');
83
84                dontEnums = [
85                    'toString',
86                    'toLocaleString',
87                    'valueOf',
88                    'hasOwnProperty',
89                    'isPrototypeOf',
90                    'propertyIsEnumerable',
91                    'constructor'
92                ];
93
94                dontEnumsLength = dontEnums.length;
95
96                return function(obj) {
97                    var result  = [],
98                        prop    = '',
99                        i       = -1;
100
101                    if (typeof obj !== 'object' && (typeof obj !== 'function' || obj === null)) {
102                        throw new TypeError('Object.keys called on non-object');
103                    }
104
105                    for (prop in obj) {
106                        if (hasOwnProperty.call(obj, prop)) {
107                            result.push(prop);
108                        }
109                    }
110
111                    if (hasDontEnumBug) {
112                        for (i = 0; i < dontEnumsLength; i++) {
113                            if (hasOwnProperty.call(obj, dontEnums[i])) {
114                                result.push(dontEnums[i]);
115                            }
116                        }
117                    }
118
119                    return result;
120                };
121            }());
122        }
123
124        // Array.isArray
125        // https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray
126
127        if (!Array.isArray) {
128            Array.isArray = function(arg) {
129                return Object.prototype.toString.call(arg) === '[object Array]';
130            };
131        }
132
133        // Object.create
134        // https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/create
135
136        if (typeof Object.create !== 'function') {
137            Object.create = (function(undefined) {
138                var Temp = function() {};
139
140                return function (prototype, propertiesObject) {
141                    if (prototype !== Object(prototype) && prototype !== null) {
142                        throw TypeError('Argument must be an object, or null');
143                    }
144
145                    Temp.prototype = prototype || {};
146
147                    var result = new Temp();
148
149                    Temp.prototype = null;
150
151                    if (propertiesObject !== undefined) {
152                        Object.defineProperties(result, propertiesObject);
153                    }
154
155                    if (prototype === null) {
156                        /* jshint ignore:start */
157                        result.__proto__ = null;
158                        /* jshint ignore:end */
159                    }
160
161                    return result;
162                };
163            })();
164        }
165
166        // String.prototyoe.trim
167
168        if (!String.prototype.trim) {
169            String.prototype.trim = function() {
170                return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
171            };
172        }
173
174        // Array.prototype.indexOf
175        // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
176
177        if (!Array.prototype.indexOf) {
178            Array.prototype.indexOf = function(searchElement) {
179                var n, k, t, len;
180
181                if (this === null) {
182                    throw new TypeError();
183                }
184
185                t = Object(this);
186
187                len = t.length >>> 0;
188
189                if (len === 0) {
190                    return -1;
191                }
192
193                n = 0;
194
195                if (arguments.length > 1) {
196                    n = Number(arguments[1]);
197
198                    if (n !== n) {
199                        n = 0;
200                    } else if (n !== 0 && n !== Infinity && n !== -Infinity) {
201                        n = (n > 0 || -1) * Math.floor(Math.abs(n));
202                    }
203                }
204
205                if (n >= len) {
206                    return -1;
207                }
208
209                for (k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); k < len; k++) {
210                    if (k in t && t[k] === searchElement) {
211                        return k;
212                    }
213                }
214
215                return -1;
216            };
217        }
218
219        // Function.prototype.bind
220        // https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind
221
222        if (!Function.prototype.bind) {
223            Function.prototype.bind = function(oThis) {
224                var aArgs, self, FNOP, fBound;
225
226                if (typeof this !== 'function') {
227                    throw new TypeError();
228                }
229
230                aArgs = Array.prototype.slice.call(arguments, 1);
231
232                self = this;
233
234                FNOP = function() {};
235
236                fBound = function() {
237                    return self.apply(this instanceof FNOP ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments)));
238                };
239
240                if (this.prototype) {
241                    FNOP.prototype = this.prototype;
242                }
243
244                fBound.prototype = new FNOP();
245
246                return fBound;
247            };
248        }
249
250        // Element.prototype.dispatchEvent
251
252        if (!window.Element.prototype.dispatchEvent) {
253            window.Element.prototype.dispatchEvent = function(event) {
254                try {
255                    return this.fireEvent('on' + event.type, event);
256                } catch (err) {}
257            };
258        }
259    })();
260
261    /**
262     * The `mixitup()` "factory" function creates and returns individual instances
263     * of MixItUp, known as "mixers", on which API methods can be called.
264     *
265     * When loading MixItUp via a script tag, the factory function is accessed
266     * via the global variable `mixitup`. When using a module loading
267     * system (e.g. ES2015, CommonJS, RequireJS), the factory function is
268     * exported into your module when you require the MixItUp library.
269     *
270     * @example
271     * mixitup(container [,config] [,foreignDoc])
272     *
273     * @example <caption>Example 1: Creating a mixer instance with an element reference</caption>
274     * var containerEl = document.querySelector('.container');
275     *
276     * var mixer = mixitup(containerEl);
277     *
278     * @example <caption>Example 2: Creating a mixer instance with a selector string</caption>
279     * var mixer = mixitup('.container');
280     *
281     * @example <caption>Example 3: Passing a configuration object</caption>
282     * var mixer = mixitup(containerEl, {
283     *     animation: {
284     *         effects: 'fade scale(0.5)'
285     *     }
286     * });
287     *
288     * @example <caption>Example 4: Passing an iframe reference</caption>
289     * var mixer = mixitup(containerEl, config, foreignDocument);
290     *
291     * @global
292     * @namespace
293     * @public
294     * @kind        function
295     * @since       3.0.0
296     * @param       {(Element|string)}  container
297     *      A DOM element or selector string representing the container(s) on which to instantiate MixItUp.
298     * @param       {object}            [config]
299     *      An optional "configuration object" used to customize the behavior of the MixItUp instance.
300     * @param       {object}            [foreignDoc]
301     *      An optional reference to a `document`, which can be used to control a MixItUp instance in an iframe.
302     * @return      {mixitup.Mixer}
303     *      A "mixer" object holding the MixItUp instance.
304     */
305
306    mixitup = function(container, config, foreignDoc) {
307        var el                  = null,
308            returnCollection    = false,
309            instance            = null,
310            facade              = null,
311            doc                 = null,
312            output              = null,
313            instances           = [],
314            id                  = '',
315            elements            = [],
316            i                   = -1;
317
318        doc = foreignDoc || window.document;
319
320        if (returnCollection = arguments[3]) {
321            // A non-documented 4th paramater enabling control of multiple instances
322
323            returnCollection = typeof returnCollection === 'boolean';
324        }
325
326        if (typeof container === 'string') {
327            elements = doc.querySelectorAll(container);
328        } else if (container && typeof container === 'object' && h.isElement(container, doc)) {
329            elements = [container];
330        } else if (container && typeof container === 'object' && container.length) {
331            // Although not documented, the container may also be an array-like list of
332            // elements such as a NodeList or jQuery collection, is returnCollection is true
333
334            elements = container;
335        } else {
336            throw new Error(mixitup.messages.errorFactoryInvalidContainer());
337        }
338
339        if (elements.length < 1) {
340            throw new Error(mixitup.messages.errorFactoryContainerNotFound());
341        }
342
343        for (i = 0; el = elements[i]; i++) {
344            if (i > 0 && !returnCollection) break;
345
346            if (!el.id) {
347                id = 'MixItUp' + h.randomHex();
348
349                el.id = id;
350            } else {
351                id = el.id;
352            }
353
354            if (mixitup.instances[id] instanceof mixitup.Mixer) {
355                instance = mixitup.instances[id];
356
357                if (!config || (config && config.debug && config.debug.showWarnings !== false)) {
358                    console.warn(mixitup.messages.warningFactoryPreexistingInstance());
359                }
360            } else {
361                instance = new mixitup.Mixer();
362
363                instance.attach(el, doc, id, config);
364
365                mixitup.instances[id] = instance;
366            }
367
368            facade = new mixitup.Facade(instance);
369
370            if (config && config.debug && config.debug.enable) {
371                instances.push(instance);
372            } else {
373                instances.push(facade);
374            }
375        }
376
377        if (returnCollection) {
378            output = new mixitup.Collection(instances);
379        } else {
380            // Return the first instance regardless
381
382            output = instances[0];
383        }
384
385        return output;
386    };
387
388    /**
389     * The `.use()` static method is used to extend the functionality of mixitup with compatible
390     * extensions and libraries in an environment with modular scoping e.g. ES2015, CommonJS, or RequireJS.
391     *
392     * You need only call the `.use()` function once per project, per extension, as module loaders
393     * will cache a single reference to MixItUp inclusive of all changes made.
394     *
395     * @example
396     * mixitup.use(extension)
397     *
398     * @example <caption>Example 1: Extending MixItUp with the Pagination Extension</caption>
399     *
400     * import mixitup from 'mixitup';
401     * import mixitupPagination from 'mixitup-pagination';
402     *
403     * mixitup.use(mixitupPagination);
404     *
405     * // All mixers created by the factory function in all modules will now
406     * // have pagination functionality
407     *
408     * var mixer = mixitup('.container');
409     *
410     * @public
411     * @name     use
412     * @memberof mixitup
413     * @kind     function
414     * @static
415     * @since    3.0.0
416     * @param    {*}  extension   A reference to the extension or library to be used.
417     * @return   {void}
418     */
419
420    mixitup.use = function(extension) {
421        mixitup.Base.prototype.callActions.call(mixitup, 'beforeUse', arguments);
422
423        // Call the extension's factory function, passing
424        // the mixitup factory as a paramater
425
426        if (typeof extension === 'function' && extension.TYPE === 'mixitup-extension') {
427            // Mixitup extension
428
429            if (typeof mixitup.extensions[extension.NAME] === 'undefined') {
430                extension(mixitup);
431
432                mixitup.extensions[extension.NAME] = extension;
433            }
434        } else if (extension.fn && extension.fn.jquery) {
435            // jQuery
436
437            mixitup.libraries.$ = extension;
438        }
439
440        mixitup.Base.prototype.callActions.call(mixitup, 'afterUse', arguments);
441    };
442
443    mixitup.instances   = {};
444    mixitup.extensions  = {};
445    mixitup.libraries   = {};
446
447    /**
448     * @private
449     */
450
451    h = {
452
453        /**
454         * @private
455         * @param   {HTMLElement}   el
456         * @param   {string}        cls
457         * @return  {boolean}
458         */
459
460        hasClass: function(el, cls) {
461            return !!el.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)'));
462        },
463
464        /**
465         * @private
466         * @param   {HTMLElement}   el
467         * @param   {string}        cls
468         * @return  {void}
469         */
470
471        addClass: function(el, cls) {
472            if (!this.hasClass(el, cls)) el.className += el.className ? ' ' + cls : cls;
473        },
474
475        /**
476         * @private
477         * @param   {HTMLElement}   el
478         * @param   {string}        cls
479         * @return  {void}
480         */
481
482        removeClass: function(el, cls) {
483            if (this.hasClass(el, cls)) {
484                var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)');
485
486                el.className = el.className.replace(reg, ' ').trim();
487            }
488        },
489
490        /**
491         * Merges the properties of the source object onto the
492         * target object. Alters the target object.
493         *
494         * @private
495         * @param   {object}    destination
496         * @param   {object}    source
497         * @param   {boolean}   [deep=false]
498         * @param   {boolean}   [handleErrors=false]
499         * @return  {void}
500         */
501
502        extend: function(destination, source, deep, handleErrors) {
503            var sourceKeys  = [],
504                key         = '',
505                i           = -1;
506
507            deep = deep || false;
508            handleErrors = handleErrors || false;
509
510            try {
511                if (Array.isArray(source)) {
512                    for (i = 0; i < source.length; i++) {
513                        sourceKeys.push(i);
514                    }
515                } else if (source) {
516                    sourceKeys = Object.keys(source);
517                }
518
519                for (i = 0; i < sourceKeys.length; i++) {
520                    key = sourceKeys[i];
521
522                    if (!deep || typeof source[key] !== 'object' || this.isElement(source[key])) {
523                        // All non-object properties, or all properties if shallow extend
524
525                        destination[key] = source[key];
526                    } else if (Array.isArray(source[key])) {
527                        // Arrays
528
529                        if (!destination[key]) {
530                            destination[key] = [];
531                        }
532
533                        this.extend(destination[key], source[key], deep, handleErrors);
534                    } else {
535                        // Objects
536
537                        if (!destination[key]) {
538                            destination[key] = {};
539                        }
540
541                        this.extend(destination[key], source[key], deep, handleErrors);
542                    }
543                }
544            } catch(err) {
545                if (handleErrors) {
546                    this.handleExtendError(err, destination);
547                } else {
548                    throw err;
549                }
550            }
551
552            return destination;
553        },
554
555        /**
556         * @private
557         * @param   {Error}  err
558         * @param   {object} destination
559         * @return  {void}
560         */
561
562        handleExtendError: function(err, destination) {
563            var re                  = /property "?(\w*)"?[,:] object/i,
564                matches             = null,
565                erroneous           = '',
566                message             = '',
567                suggestion          = '',
568                probableMatch       = '',
569                key                 = '',
570                mostMatchingChars   = -1,
571                i                   = -1;
572
573            if (err instanceof TypeError && (matches = re.exec(err.message))) {
574                erroneous = matches[1];
575
576                for (key in destination) {
577                    i = 0;
578
579                    while (i < erroneous.length && erroneous.charAt(i) === key.charAt(i)) {
580                        i++;
581                    }
582
583                    if (i > mostMatchingChars) {
584                        mostMatchingChars = i;
585                        probableMatch = key;
586                    }
587                }
588
589                if (mostMatchingChars > 1) {
590                    suggestion = mixitup.messages.errorConfigInvalidPropertySuggestion({
591                        probableMatch: probableMatch
592                    });
593                }
594
595                message = mixitup.messages.errorConfigInvalidProperty({
596                    erroneous: erroneous,
597                    suggestion: suggestion
598                });
599
600                throw new TypeError(message);
601            }
602
603            throw err;
604        },
605
606        /**
607         * @private
608         * @param   {string} str
609         * @return  {function}
610         */
611
612        template: function(str) {
613            var re          = /\${([\w]*)}/g,
614                dynamics    = {},
615                matches     = null;
616
617            while ((matches = re.exec(str))) {
618                dynamics[matches[1]] = new RegExp('\\${' + matches[1] + '}', 'g');
619            }
620
621            return function(data) {
622                var key     = '',
623                    output  = str;
624
625                data = data || {};
626
627                for (key in dynamics) {
628                    output = output.replace(dynamics[key], typeof data[key] !== 'undefined' ? data[key] : '');
629                }
630
631                return output;
632            };
633        },
634
635        /**
636         * @private
637         * @param   {HTMLElement}   el
638         * @param   {string}        type
639         * @param   {function}      fn
640         * @param   {boolean}       useCapture
641         * @return  {void}
642         */
643
644        on: function(el, type, fn, useCapture) {
645            if (!el) return;
646
647            if (el.addEventListener) {
648                el.addEventListener(type, fn, useCapture);
649            } else if (el.attachEvent) {
650                el['e' + type + fn] = fn;
651
652                el[type + fn] = function() {
653                    el['e' + type + fn](window.event);
654                };
655
656                el.attachEvent('on' + type, el[type + fn]);
657            }
658        },
659
660        /**
661         * @private
662         * @param   {HTMLElement}   el
663         * @param   {string}        type
664         * @param   {function}      fn
665         * @return  {void}
666         */
667
668        off: function(el, type, fn) {
669            if (!el) return;
670
671            if (el.removeEventListener) {
672                el.removeEventListener(type, fn, false);
673            } else if (el.detachEvent) {
674                el.detachEvent('on' + type, el[type + fn]);
675                el[type + fn] = null;
676            }
677        },
678
679        /**
680         * @private
681         * @param   {string}      eventType
682         * @param   {object}      detail
683         * @param   {Document}    [doc]
684         * @return  {CustomEvent}
685         */
686
687        getCustomEvent: function(eventType, detail, doc) {
688            var event = null;
689
690            doc = doc || window.document;
691
692            if (typeof window.CustomEvent === 'function') {
693                event = new window.CustomEvent(eventType, {
694                    detail: detail,
695                    bubbles: true,
696                    cancelable: true
697                });
698            } else if (typeof doc.createEvent === 'function') {
699                event = doc.createEvent('CustomEvent');
700                event.initCustomEvent(eventType, true, true, detail);
701            } else {
702                event = doc.createEventObject(),
703                event.type = eventType;
704
705                event.returnValue = false;
706                event.cancelBubble = false;
707                event.detail = detail;
708            }
709
710            return event;
711        },
712
713        /**
714         * @private
715         * @param   {Event} e
716         * @return  {Event}
717         */
718
719        getOriginalEvent: function(e) {
720            if (e.touches && e.touches.length) {
721                return e.touches[0];
722            } else if (e.changedTouches && e.changedTouches.length) {
723                return e.changedTouches[0];
724            } else {
725                return e;
726            }
727        },
728
729        /**
730         * @private
731         * @param   {HTMLElement}   el
732         * @param   {string}        selector
733         * @return  {Number}
734         */
735
736        index: function(el, selector) {
737            var i = 0;
738
739            while ((el = el.previousElementSibling) !== null) {
740                if (!selector || el.matches(selector)) {
741                    ++i;
742                }
743            }
744
745            return i;
746        },
747
748        /**
749         * Converts a dash or snake-case string to camel case.
750         *
751         * @private
752         * @param   {string}    str
753         * @param   {boolean}   [isPascal]
754         * @return  {string}
755         */
756
757        camelCase: function(str) {
758            return str.toLowerCase().replace(/([_-][a-z])/g, function($1) {
759                return $1.toUpperCase().replace(/[_-]/, '');
760            });
761        },
762
763        /**
764         * Converts a dash or snake-case string to pascal case.
765         *
766         * @private
767         * @param   {string}    str
768         * @param   {boolean}   [isPascal]
769         * @return  {string}
770         */
771
772        pascalCase: function(str) {
773            return (str = this.camelCase(str)).charAt(0).toUpperCase() + str.slice(1);
774        },
775
776        /**
777         * Converts a camel or pascal-case string to dash case.
778         *
779         * @private
780         * @param   {string}    str
781         * @return  {string}
782         */
783
784        dashCase: function(str) {
785            return str.replace(/([A-Z])/g, '-$1').replace(/^-/, '').toLowerCase();
786        },
787
788        /**
789         * @private
790         * @param   {HTMLElement}       el
791         * @param   {HTMLHtmlElement}   [doc]
792         * @return  {boolean}
793         */
794
795        isElement: function(el, doc) {
796            doc = doc || window.document;
797
798            if (
799                window.HTMLElement &&
800                el instanceof window.HTMLElement
801            ) {
802                return true;
803            } else if (
804                doc.defaultView &&
805                doc.defaultView.HTMLElement &&
806                el instanceof doc.defaultView.HTMLElement
807            ) {
808                return true;
809            } else {
810                return (
811                    el !== null &&
812                    el.nodeType === 1 &&
813                    typeof el.nodeName === 'string'
814                );
815            }
816        },
817
818        /**
819         * @private
820         * @param   {string}            htmlString
821         * @param   {HTMLHtmlElement}   [doc]
822         * @return  {DocumentFragment}
823         */
824
825        createElement: function(htmlString, doc) {
826            var frag = null,
827                temp = null;
828
829            doc = doc || window.document;
830
831            frag = doc.createDocumentFragment();
832            temp = doc.createElement('div');
833
834            temp.innerHTML = htmlString.trim();
835
836            while (temp.firstChild) {
837                frag.appendChild(temp.firstChild);
838            }
839
840            return frag;
841        },
842
843        /**
844         * @private
845         * @param   {Node} node
846         * @return  {void}
847         */
848
849        removeWhitespace: function(node) {
850            var deleting;
851
852            while (node && node.nodeName === '#text') {
853                deleting = node;
854
855                node = node.previousSibling;
856
857                deleting.parentElement && deleting.parentElement.removeChild(deleting);
858            }
859        },
860
861        /**
862         * @private
863         * @param   {Array<*>}  a
864         * @param   {Array<*>}  b
865         * @return  {boolean}
866         */
867
868        isEqualArray: function(a, b) {
869            var i = a.length;
870
871            if (i !== b.length) return false;
872
873            while (i--) {
874                if (a[i] !== b[i]) return false;
875            }
876
877            return true;
878        },
879
880        /**
881         * @private
882         * @param   {object}  a
883         * @param   {object}  b
884         * @return  {boolean}
885         */
886
887        deepEquals: function(a, b) {
888            var key;
889
890            if (typeof a === 'object' && a && typeof b === 'object' && b) {
891                if (Object.keys(a).length !== Object.keys(b).length) return false;
892
893                for (key in a) {
894                    if (!b.hasOwnProperty(key) || !this.deepEquals(a[key], b[key])) return false;
895                }
896            } else if (a !== b) {
897                return false;
898            }
899
900            return true;
901        },
902
903        /**
904         * @private
905         * @param   {Array<*>}  oldArray
906         * @return  {Array<*>}
907         */
908
909        arrayShuffle: function(oldArray) {
910            var newArray    = oldArray.slice(),
911                len         = newArray.length,
912                i           = len,
913                p           = -1,
914                t           = [];
915
916            while (i--) {
917                p = ~~(Math.random() * len);
918                t = newArray[i];
919
920                newArray[i] = newArray[p];
921                newArray[p] = t;
922            }
923
924            return newArray;
925        },
926
927        /**
928         * @private
929         * @param   {object}    list
930         */
931
932        arrayFromList: function(list) {
933            var output, i;
934
935            try {
936                return Array.prototype.slice.call(list);
937            } catch(err) {
938                output = [];
939
940                for (i = 0; i < list.length; i++) {
941                    output.push(list[i]);
942                }
943
944                return output;
945            }
946        },
947
948        /**
949         * @private
950         * @param   {function}  func
951         * @param   {Number}    wait
952         * @param   {boolean}   immediate
953         * @return  {function}
954         */
955
956        debounce: function(func, wait, immediate) {
957            var timeout;
958
959            return function() {
960                var self     = this,
961                    args     = arguments,
962                    callNow  = immediate && !timeout,
963                    later    = null;
964
965                later = function() {
966                    timeout  = null;
967
968                    if (!immediate) {
969                        func.apply(self, args);
970                    }
971                };
972
973                clearTimeout(timeout);
974
975                timeout = setTimeout(later, wait);
976
977                if (callNow) func.apply(self, args);
978            };
979        },
980
981        /**
982         * @private
983         * @param   {HTMLElement}   element
984         * @return  {object}
985         */
986
987        position: function(element) {
988            var xPosition       = 0,
989                yPosition       = 0,
990                offsetParent    = element;
991
992            while (element) {
993                xPosition -= element.scrollLeft;
994                yPosition -= element.scrollTop;
995
996                if (element === offsetParent) {
997                    xPosition += element.offsetLeft;
998                    yPosition += element.offsetTop;
999
1000                    offsetParent = element.offsetParent;
1001                }
1002
1003                element = element.parentElement;
1004            }
1005
1006            return {
1007                x: xPosition,
1008                y: yPosition
1009            };
1010        },
1011
1012        /**
1013         * @private
1014         * @param   {object}    node1
1015         * @param   {object}    node2
1016         * @return  {Number}
1017         */
1018
1019        getHypotenuse: function(node1, node2) {
1020            var distanceX = node1.x - node2.x,
1021                distanceY = node1.y - node2.y;
1022
1023            distanceX = distanceX < 0 ? distanceX * -1 : distanceX,
1024            distanceY = distanceY < 0 ? distanceY * -1 : distanceY;
1025
1026            return Math.sqrt(Math.pow(distanceX, 2) + Math.pow(distanceY, 2));
1027        },
1028
1029        /**
1030         * Calcuates the area of intersection between two rectangles and expresses it as
1031         * a ratio in comparison to the area of the first rectangle.
1032         *
1033         * @private
1034         * @param   {Rect}  box1
1035         * @param   {Rect}  box2
1036         * @return  {number}
1037         */
1038
1039        getIntersectionRatio: function(box1, box2) {
1040            var controlArea         = box1.width * box1.height,
1041                intersectionX       = -1,
1042                intersectionY       = -1,
1043                intersectionArea    = -1,
1044                ratio               = -1;
1045
1046            intersectionX =
1047                Math.max(0, Math.min(box1.left + box1.width, box2.left + box2.width) - Math.max(box1.left, box2.left));
1048
1049            intersectionY =
1050                Math.max(0, Math.min(box1.top + box1.height, box2.top + box2.height) - Math.max(box1.top, box2.top));
1051
1052            intersectionArea = intersectionY * intersectionX;
1053
1054            ratio = intersectionArea / controlArea;
1055
1056            return ratio;
1057        },
1058
1059        /**
1060         * @private
1061         * @param   {object}            el
1062         * @param   {string}            selector
1063         * @param   {boolean}           [includeSelf]
1064         * @param   {HTMLHtmlElement}   [doc]
1065         * @return  {Element|null}
1066         */
1067
1068        closestParent: function(el, selector, includeSelf, doc) {
1069            var parent  = el.parentNode;
1070
1071            doc = doc || window.document;
1072
1073            if (includeSelf && el.matches(selector)) {
1074                return el;
1075            }
1076
1077            while (parent && parent != doc.body) {
1078                if (parent.matches && parent.matches(selector)) {
1079                    return parent;
1080                } else if (parent.parentNode) {
1081                    parent = parent.parentNode;
1082                } else {
1083                    return null;
1084                }
1085            }
1086
1087            return null;
1088        },
1089
1090        /**
1091         * @private
1092         * @param   {HTMLElement}       el
1093         * @param   {string}            selector
1094         * @param   {HTMLHtmlElement}   [doc]
1095         * @return  {NodeList}
1096         */
1097
1098        children: function(el, selector, doc) {
1099            var children    = [],
1100                tempId      = '';
1101
1102            doc = doc || window.doc;
1103
1104            if (el) {
1105                if (!el.id) {
1106                    tempId = 'Temp' + this.randomHexKey();
1107
1108                    el.id = tempId;
1109                }
1110
1111                children = doc.querySelectorAll('#' + el.id + ' > ' + selector);
1112
1113                if (tempId) {
1114                    el.removeAttribute('id');
1115                }
1116            }
1117
1118            return children;
1119        },
1120
1121        /**
1122         * Creates a clone of a provided array, with any empty strings removed.
1123         *
1124         * @private
1125         * @param   {Array<*>} originalArray
1126         * @return  {Array<*>}
1127         */
1128
1129        clean: function(originalArray) {
1130            var cleanArray = [],
1131                i = -1;
1132
1133            for (i = 0; i < originalArray.length; i++) {
1134                if (originalArray[i] !== '') {
1135                    cleanArray.push(originalArray[i]);
1136                }
1137            }
1138
1139            return cleanArray;
1140        },
1141
1142        /**
1143         * Abstracts an ES6 promise into a q-like deferred interface for storage and deferred resolution.
1144         *
1145         * @private
1146         * @param  {object} libraries
1147         * @return {h.Deferred}
1148         */
1149
1150        defer: function(libraries) {
1151            var deferred       = null,
1152                promiseWrapper = null,
1153                $              = null;
1154
1155            promiseWrapper = new this.Deferred();
1156
1157            if (mixitup.features.has.promises) {
1158                // ES6 native promise or polyfill
1159
1160                promiseWrapper.promise = new Promise(function(resolve, reject) {
1161                    promiseWrapper.resolve = resolve;
1162                    promiseWrapper.reject  = reject;
1163                });
1164            } else if (($ = (window.jQuery || libraries.$)) && typeof $.Deferred === 'function') {
1165                // jQuery
1166
1167                deferred = $.Deferred();
1168
1169                promiseWrapper.promise = deferred.promise();
1170                promiseWrapper.resolve = deferred.resolve;
1171                promiseWrapper.reject  = deferred.reject;
1172            } else if (window.console) {
1173                // No implementation
1174
1175                console.warn(mixitup.messages.warningNoPromiseImplementation());
1176            }
1177
1178            return promiseWrapper;
1179        },
1180
1181        /**
1182         * @private
1183         * @param   {Array<Promise>}    tasks
1184         * @param   {object}            libraries
1185         * @return  {Promise<Array>}
1186         */
1187
1188        all: function(tasks, libraries) {
1189            var $ = null;
1190
1191            if (mixitup.features.has.promises) {
1192                return Promise.all(tasks);
1193            } else if (($ = (window.jQuery || libraries.$)) && typeof $.when === 'function') {
1194                return $.when.apply($, tasks)
1195                    .done(function() {
1196                        // jQuery when returns spread arguments rather than an array or resolutions
1197
1198                        return arguments;
1199                    });
1200            }
1201
1202            // No implementation
1203
1204            if (window.console) {
1205                console.warn(mixitup.messages.warningNoPromiseImplementation());
1206            }
1207
1208            return [];
1209        },
1210
1211        /**
1212         * @private
1213         * @param   {HTMLElement}   el
1214         * @param   {string}        property
1215         * @param   {Array<string>} vendors
1216         * @return  {string}
1217         */
1218
1219        getPrefix: function(el, property, vendors) {
1220            var i       = -1,
1221                prefix  = '';
1222
1223            if (h.dashCase(property) in el.style) return '';
1224
1225            for (i = 0; prefix = vendors[i]; i++) {
1226                if (prefix + property in el.style) {
1227                    return prefix.toLowerCase();
1228                }
1229            }
1230
1231            return 'unsupported';
1232        },
1233
1234        /**
1235         * @private
1236         * @return  {string}
1237         */
1238
1239        randomHex: function() {
1240            return ('00000' + (Math.random() * 16777216 << 0).toString(16)).substr(-6).toUpperCase();
1241        },
1242
1243        /**
1244         * @private
1245         * @param   {HTMLDocument}  [doc]
1246         * @return  {object}
1247         */
1248
1249        getDocumentState: function(doc) {
1250            doc = typeof doc.body === 'object' ? doc : window.document;
1251
1252            return {
1253                scrollTop: window.pageYOffset,
1254                scrollLeft: window.pageXOffset,
1255                docHeight: doc.documentElement.scrollHeight,
1256                docWidth: doc.documentElement.scrollWidth,
1257                viewportHeight: doc.documentElement.clientHeight,
1258                viewportWidth: doc.documentElement.clientWidth
1259            };
1260        },
1261
1262        /**
1263         * @private
1264         * @param   {object}    obj
1265         * @param   {function}  fn
1266         * @return  {function}
1267         */
1268
1269        bind: function(obj, fn) {
1270            return function() {
1271                return fn.apply(obj, arguments);
1272            };
1273        },
1274
1275        /**
1276         * @private
1277         * @param   {HTMLElement}   el
1278         * @return  {boolean}
1279         */
1280
1281        isVisible: function(el) {
1282            var styles = null;
1283
1284            if (el.offsetParent) return true;
1285
1286            styles = window.getComputedStyle(el);
1287
1288            if (
1289                styles.position === 'fixed' &&
1290                styles.visibility !== 'hidden' &&
1291                styles.opacity !== '0'
1292            ) {
1293                // Fixed elements report no offsetParent,
1294                // but may still be invisible
1295
1296                return true;
1297            }
1298
1299            return false;
1300        },
1301
1302        /**
1303         * @private
1304         * @param   {object}    obj
1305         */
1306
1307        seal: function(obj) {
1308            if (typeof Object.seal === 'function') {
1309                Object.seal(obj);
1310            }
1311        },
1312
1313        /**
1314         * @private
1315         * @param   {object}    obj
1316         */
1317
1318        freeze: function(obj) {
1319            if (typeof Object.freeze === 'function') {
1320                Object.freeze(obj);
1321            }
1322        },
1323
1324        /**
1325         * @private
1326         * @param   {string}    control
1327         * @param   {string}    specimen
1328         * @return  {boolean}
1329         */
1330
1331        compareVersions: function(control, specimen) {
1332            var controlParts    = control.split('.'),
1333                specimenParts   = specimen.split('.'),
1334                controlPart     = -1,
1335                specimenPart    = -1,
1336                i               = -1;
1337
1338            for (i = 0; i < controlParts.length; i++) {
1339                controlPart     = parseInt(controlParts[i].replace(/[^\d.]/g, ''));
1340                specimenPart    = parseInt(specimenParts[i].replace(/[^\d.]/g, '') || 0);
1341
1342                if (specimenPart < controlPart) {
1343                    return false;
1344                } else if (specimenPart > controlPart) {
1345                    return true;
1346                }
1347            }
1348
1349            return true;
1350        },
1351
1352        /**
1353         * @private
1354         * @constructor
1355         */
1356
1357        Deferred: function() {
1358            this.promise    = null;
1359            this.resolve    = null;
1360            this.reject     = null;
1361            this.id         = h.randomHex();
1362        },
1363
1364        /**
1365         * @private
1366         * @param   {object}  obj
1367         * @return  {boolean}
1368         */
1369
1370        isEmptyObject: function(obj) {
1371            var key = '';
1372
1373            if (typeof Object.keys === 'function') {
1374                return Object.keys(obj).length === 0;
1375            }
1376
1377            for (key in obj) {
1378                if (obj.hasOwnProperty(key)) {
1379                    return false;
1380                }
1381            }
1382
1383            return true;
1384        },
1385
1386        /**
1387         * @param   {mixitup.Config.ClassNames}   classNames
1388         * @param   {string}                      elementName
1389         * @param   {string}                      [modifier]
1390         * @return  {string}
1391         */
1392
1393        getClassname: function(classNames, elementName, modifier) {
1394            var classname = '';
1395
1396            classname += classNames.block;
1397
1398            if (classname.length) {
1399                classname += classNames.delineatorElement;
1400            }
1401
1402            classname += classNames['element' + this.pascalCase(elementName)];
1403
1404            if (!modifier) return classname;
1405
1406            if (classname.length) {
1407                classname += classNames.delineatorModifier;
1408            }
1409
1410            classname += modifier;
1411
1412            return classname;
1413        },
1414
1415        /**
1416         * Returns the value of a property on a given object via its string key.
1417         *
1418         * @param   {object}    obj
1419         * @param   {string}    stringKey
1420         * @return  {*} value
1421         */
1422
1423        getProperty: function(obj, stringKey) {
1424            var parts           = stringKey.split('.'),
1425                returnCurrent   = null,
1426                current         = '',
1427                i               = 0;
1428
1429            if (!stringKey) {
1430                return obj;
1431            }
1432
1433            returnCurrent = function(obj) {
1434                if (!obj) {
1435                    return null;
1436                } else {
1437                    return obj[current];
1438                }
1439            };
1440
1441            while (i < parts.length) {
1442                current = parts[i];
1443
1444                obj = returnCurrent(obj);
1445
1446                i++;
1447            }
1448
1449            if (typeof obj !== 'undefined') {
1450                return obj;
1451            } else {
1452                return null;
1453            }
1454        }
1455    };
1456
1457    mixitup.h = h;
1458
1459    /**
1460     * The Base class adds instance methods to all other extensible MixItUp classes,
1461     * enabling the calling of any registered hooks.
1462     *
1463     * @constructor
1464     * @namespace
1465     * @memberof    mixitup
1466     * @private
1467     * @since       3.0.0
1468     */
1469
1470    mixitup.Base = function() {};
1471
1472    mixitup.Base.prototype = {
1473        constructor: mixitup.Base,
1474
1475        /**
1476         * Calls any registered hooks for the provided action.
1477         *
1478         * @memberof    mixitup.Base
1479         * @private
1480         * @instance
1481         * @since       2.0.0
1482         * @param       {string}    actionName
1483         * @param       {Array<*>}  args
1484         * @return      {void}
1485         */
1486
1487        callActions: function(actionName, args) {
1488            var self            = this,
1489                hooks           = self.constructor.actions[actionName],
1490                extensionName   = '';
1491
1492            if (!hooks || h.isEmptyObject(hooks)) return;
1493
1494            for (extensionName in hooks) {
1495                hooks[extensionName].apply(self, args);
1496            }
1497        },
1498
1499        /**
1500         * Calls any registered hooks for the provided filter.
1501         *
1502         * @memberof    mixitup.Base
1503         * @private
1504         * @instance
1505         * @since       2.0.0
1506         * @param       {string}    filterName
1507         * @param       {*}         input
1508         * @param       {Array<*>}  args
1509         * @return      {*}
1510         */
1511
1512        callFilters: function(filterName, input, args) {
1513            var self            = this,
1514                hooks           = self.constructor.filters[filterName],
1515                output          = input,
1516                extensionName   = '';
1517
1518            if (!hooks || h.isEmptyObject(hooks)) return output;
1519
1520            args = args || [];
1521
1522            for (extensionName in hooks) {
1523                args = h.arrayFromList(args);
1524
1525                args.unshift(output);
1526
1527                output = hooks[extensionName].apply(self, args);
1528            }
1529
1530            return output;
1531        }
1532    };
1533
1534    /**
1535     * The BaseStatic class holds a set of static methods which are then added to all other
1536     * extensible MixItUp classes as a means of integrating extensions via the addition of new
1537     * methods and/or actions and hooks.
1538     *
1539     * @constructor
1540     * @namespace
1541     * @memberof    mixitup
1542     * @private
1543     * @since       3.0.0
1544     */
1545
1546    mixitup.BaseStatic = function() {
1547        this.actions = {};
1548        this.filters = {};
1549
1550        /**
1551         * Performs a shallow extend on the class's prototype, adding one or more new members to
1552         * the class in a single operation.
1553         *
1554         * @memberof    mixitup.BaseStatic
1555         * @public
1556         * @static
1557         * @since       2.1.0
1558         * @param       {object} extension
1559         * @return      {void}
1560         */
1561
1562        this.extend = function(extension) {
1563            h.extend(this.prototype, extension);
1564        };
1565
1566        /**
1567         * Registers a function to be called on the action hook of the provided name.
1568         *
1569         * @memberof    mixitup.BaseStatic
1570         * @public
1571         * @static
1572         * @since       2.1.0
1573         * @param       {string}    hookName
1574         * @param       {string}    extensionName
1575         * @param       {function}  func
1576         * @return      {void}
1577         */
1578
1579        this.registerAction = function(hookName, extensionName, func) {
1580            (this.actions[hookName] = this.actions[hookName] || {})[extensionName] = func;
1581        };
1582
1583        /**
1584         * Registers a function to be called on the filter of the provided name.
1585         *
1586         * @memberof    mixitup.BaseStatic
1587         * @public
1588         * @static
1589         * @since       2.1.0
1590         * @param       {string}    hookName
1591         * @param       {string}    extensionName
1592         * @param       {function}  func
1593         * @return      {void}
1594         */
1595
1596        this.registerFilter = function(hookName, extensionName, func) {
1597            (this.filters[hookName] = this.filters[hookName] || {})[extensionName] = func;
1598        };
1599    };
1600
1601    /**
1602     * The `mixitup.Features` class performs all feature and CSS prefix detection
1603     * neccessary for MixItUp to function correctly, as well as storing various
1604     * string and array constants. All feature decection is on evaluation of the
1605     * library and stored in a singleton instance for use by other internal classes.
1606     *
1607     * @constructor
1608     * @namespace
1609     * @memberof    mixitup
1610     * @private
1611     * @since       3.0.0
1612     */
1613
1614    mixitup.Features = function() {
1615        mixitup.Base.call(this);
1616
1617        this.callActions('beforeConstruct');
1618
1619        this.boxSizingPrefix            = '';
1620        this.transformPrefix            = '';
1621        this.transitionPrefix           = '';
1622
1623        this.boxSizingPrefix            = '';
1624        this.transformProp              = '';
1625        this.transformRule              = '';
1626        this.transitionProp             = '';
1627        this.perspectiveProp            = '';
1628        this.perspectiveOriginProp      = '';
1629
1630        this.has                        = new mixitup.Has();
1631
1632        this.canary                     = null;
1633
1634        this.BOX_SIZING_PROP            = 'boxSizing';
1635        this.TRANSITION_PROP            = 'transition';
1636        this.TRANSFORM_PROP             = 'transform';
1637        this.PERSPECTIVE_PROP           = 'perspective';
1638        this.PERSPECTIVE_ORIGIN_PROP    = 'perspectiveOrigin';
1639        this.VENDORS                    = ['Webkit', 'moz', 'O', 'ms'];
1640
1641        this.TWEENABLE = [
1642            'opacity',
1643            'width', 'height',
1644            'marginRight', 'marginBottom',
1645            'x', 'y',
1646            'scale',
1647            'translateX', 'translateY', 'translateZ',
1648            'rotateX', 'rotateY', 'rotateZ'
1649        ];
1650
1651        this.callActions('afterConstruct');
1652    };
1653
1654    mixitup.BaseStatic.call(mixitup.Features);
1655
1656    mixitup.Features.prototype = Object.create(mixitup.Base.prototype);
1657
1658    h.extend(mixitup.Features.prototype,
1659    /** @lends mixitup.Features */
1660    {
1661        constructor: mixitup.Features,
1662
1663        /**
1664         * @private
1665         * @return  {void}
1666         */
1667
1668        init: function() {
1669            var self = this;
1670
1671            self.callActions('beforeInit', arguments);
1672
1673            self.canary = document.createElement('div');
1674
1675            self.setPrefixes();
1676            self.runTests();
1677
1678            self.callActions('beforeInit', arguments);
1679        },
1680
1681        /**
1682         * @private
1683         * @return  {void}
1684         */
1685
1686        runTests: function() {
1687            var self = this;
1688
1689            self.callActions('beforeRunTests', arguments);
1690
1691            self.has.promises       = typeof window.Promise === 'function';
1692            self.has.transitions    = self.transitionPrefix !== 'unsupported';
1693
1694            self.callActions('afterRunTests', arguments);
1695
1696            h.freeze(self.has);
1697        },
1698
1699        /**
1700         * @private
1701         * @return  {void}
1702         */
1703
1704        setPrefixes: function() {
1705            var self = this;
1706
1707            self.callActions('beforeSetPrefixes', arguments);
1708
1709            self.transitionPrefix   = h.getPrefix(self.canary, 'Transition', self.VENDORS);
1710            self.transformPrefix    = h.getPrefix(self.canary, 'Transform', self.VENDORS);
1711            self.boxSizingPrefix    = h.getPrefix(self.canary, 'BoxSizing', self.VENDORS);
1712
1713            self.boxSizingProp = self.boxSizingPrefix ?
1714                self.boxSizingPrefix + h.pascalCase(self.BOX_SIZING_PROP) : self.BOX_SIZING_PROP;
1715
1716            self.transitionProp = self.transitionPrefix ?
1717                self.transitionPrefix + h.pascalCase(self.TRANSITION_PROP) : self.TRANSITION_PROP;
1718
1719            self.transformProp = self.transformPrefix ?
1720                self.transformPrefix + h.pascalCase(self.TRANSFORM_PROP) : self.TRANSFORM_PROP;
1721
1722            self.transformRule = self.transformPrefix ?
1723                '-' + self.transformPrefix + '-' + self.TRANSFORM_PROP : self.TRANSFORM_PROP;
1724
1725            self.perspectiveProp = self.transformPrefix ?
1726                self.transformPrefix + h.pascalCase(self.PERSPECTIVE_PROP) : self.PERSPECTIVE_PROP;
1727
1728            self.perspectiveOriginProp = self.transformPrefix ?
1729                self.transformPrefix + h.pascalCase(self.PERSPECTIVE_ORIGIN_PROP) :
1730                self.PERSPECTIVE_ORIGIN_PROP;
1731
1732            self.callActions('afterSetPrefixes', arguments);
1733        }
1734    });
1735
1736    /**
1737     * @constructor
1738     * @memberof    mixitup
1739     * @private
1740     * @since       3.0.0
1741     */
1742
1743    mixitup.Has = function() {
1744        this.transitions    = false;
1745        this.promises       = false;
1746
1747        h.seal(this);
1748    };
1749
1750    // Assign a singleton instance to `mixitup.features` and initialise:
1751
1752    mixitup.features = new mixitup.Features();
1753
1754    mixitup.features.init();
1755
1756    /**
1757     * A group of properties defining the mixer's animation and effects settings.
1758     *
1759     * @constructor
1760     * @memberof    mixitup.Config
1761     * @name        animation
1762     * @namespace
1763     * @public
1764     * @since       2.0.0
1765     */
1766
1767    mixitup.ConfigAnimation = function() {
1768        mixitup.Base.call(this);
1769
1770        this.callActions('beforeConstruct');
1771
1772        /**
1773         * A boolean dictating whether or not animation should be enabled for the MixItUp instance.
1774         * If `false`, all operations will occur instantly and syncronously, although callback
1775         * functions and any returned promises will still be fulfilled.
1776         *
1777         * @example <caption>Example: Create a mixer with all animations disabled</caption>
1778         * var mixer = mixitup(containerEl, {
1779         *     animation: {
1780         *         enable: false
1781         *     }
1782         * });
1783         *
1784         * @name        enable
1785         * @memberof    mixitup.Config.animation
1786         * @instance
1787         * @type        {boolean}
1788         * @default     true
1789         */
1790
1791        this.enable = true;
1792
1793        /**
1794         * A string of one or more space-sep
1794erated properties to which transitions will be
1795         * applied for all filtering animations.
1796         *
1797         * Properties can be listed any order or combination, although they will be applied in a specific
1798         * predefined order to produce consistent results.
1799         *
1800         * To learn more about available effects, experiment with our <a href="https://www.kunkalabs.com/mixitup/">
1801         * sandbox demo</a> and try out the "Export config" button in the Animation options drop down
vendor: 299,530 bytes, lines 1801-10681
1801.
1802         *
1803         * @example <caption>Example: Apply "fade" and "translateZ" effects to all animations</caption>
1804         * // As targets are filtered in and out, they will fade between
1805         * // opacity 1 and 0 and transform between translateZ(-100px) and
1806         * // translateZ(0).
1807         *
1808         * var mixer = mixitup(containerEl, {
1809         *     animation: {
1810         *         effects: 'fade translateZ(-100px)'
1811         *     }
1812         * });
1813         *
1814         * @name        effects
1815         * @memberof    mixitup.Config.animation
1816         * @instance
1817         * @type        {string}
1818         * @default     'fade scale'
1819         */
1820
1821        this.effects = 'fade scale';
1822
1823        /**
1824         * A string of one or more space-seperated effects to be applied only to filter-in
1825         * animations, overriding `config.animation.effects` if set.
1826         *
1827         * @example <caption>Example: Apply downwards vertical translate to targets being filtered in</caption>
1828         *
1829         * var mixer = mixitup(containerEl, {
1830         *     animation: {
1831         *         effectsIn: 'fade translateY(-100%)'
1832         *     }
1833         * });
1834         *
1835         * @name        effectsIn
1836         * @memberof    mixitup.Config.animation
1837         * @instance
1838         * @type        {string}
1839         * @default     ''
1840         */
1841
1842        this.effectsIn = '';
1843
1844        /**
1845         * A string of one or more space-seperated effects to be applied only to filter-out
1846         * animations, overriding `config.animation.effects` if set.
1847         *
1848         * @example <caption>Example: Apply upwards vertical translate to targets being filtered out</caption>
1849         *
1850         * var mixer = mixitup(containerEl, {
1851         *     animation: {
1852         *         effectsOut: 'fade translateY(-100%)'
1853         *     }
1854         * });
1855         *
1856         * @name        effectsOut
1857         * @memberof    mixitup.Config.animation
1858         * @instance
1859         * @type        {string}
1860         * @default     ''
1861         */
1862
1863        this.effectsOut = '';
1864
1865        /**
1866         * An integer dictating the duration of all MixItUp animations in milliseconds, not
1867         * including any additional delay apllied via the `'stagger'` effect.
1868         *
1869         * @example <caption>Example: Apply an animation duration of 200ms to all mixitup animations</caption>
1870         *
1871         * var mixer = mixitup(containerEl, {
1872         *     animation: {
1873         *         duration: 200
1874         *     }
1875         * });
1876         *
1877         * @name        duration
1878         * @memberof    mixitup.Config.animation
1879         * @instance
1880         * @type        {number}
1881         * @default     600
1882         */
1883
1884        this.duration = 600;
1885
1886        /**
1887         * A valid CSS3 transition-timing function or shorthand. For a full list of accepted
1888         * values, visit <a href="http://easings.net" target="_blank">easings.net</a>.
1889         *
1890         * @example <caption>Example 1: Apply "ease-in-out" easing to all animations</caption>
1891         *
1892         * var mixer = mixitup(containerEl, {
1893         *     animation: {
1894         *         easing: 'ease-in-out'
1895         *     }
1896         * });
1897         *
1898         * @example <caption>Example 2: Apply a custom "cubic-bezier" easing function to all animations</caption>
1899         * var mixer = mixitup(containerEl, {
1900         *     animation: {
1901         *         easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)'
1902         *     }
1903         * });
1904         *
1905         * @name        easing
1906         * @memberof    mixitup.Config.animation
1907         * @instance
1908         * @type        {string}
1909         * @default     'ease'
1910         */
1911
1912        this.easing = 'ease';
1913
1914        /**
1915         * A boolean dictating whether or not to apply perspective to the MixItUp container
1916         * during animations. By default, perspective is always applied and creates the
1917         * illusion of three-dimensional space for effects such as `translateZ`, `rotateX`,
1918         * and `rotateY`.
1919         *
1920         * You may wish to disable this and define your own perspective settings via CSS.
1921         *
1922         * @example <caption>Example: Prevent perspective from being applied to any 3D transforms</caption>
1923         * var mixer = mixitup(containerEl, {
1924         *     animation: {
1925         *         applyPerspective: false
1926         *     }
1927         * });
1928         *
1929         * @name        applyPerspective
1930         * @memberof    mixitup.Config.animation
1931         * @instance
1932         * @type        {bolean}
1933         * @default     true
1934         */
1935
1936        this.applyPerspective = true;
1937
1938        /**
1939         * The perspective distance value to be applied to the container during animations,
1940         * affecting any 3D-transform-based effects.
1941         *
1942         * @example <caption>Example: Set a perspective distance of 2000px</caption>
1943         * var mixer = mixitup(containerEl, {
1944         *     animation: {
1945         *         effects: 'rotateY(-25deg)',
1946         *         perspectiveDistance: '2000px'
1947         *     }
1948         * });
1949         *
1950         * @name        perspectiveDistance
1951         * @memberof    mixitup.Config.animation
1952         * @instance
1953         * @type        {string}
1954         * @default     '3000px'
1955         */
1956
1957        this.perspectiveDistance = '3000px';
1958
1959        /**
1960         * The perspective-origin value to be applied to the container during animations,
1961         * affecting any 3D-transform-based effects.
1962         *
1963         * @example <caption>Example: Set a perspective origin in the top-right of the container</caption>
1964         * var mixer = mixitup(containerEl, {
1965         *     animation: {
1966         *         effects: 'transateZ(-200px)',
1967         *         perspectiveOrigin: '100% 0'
1968         *     }
1969         * });
1970         *
1971         * @name        perspectiveOrigin
1972         * @memberof    mixitup.Config.animation
1973         * @instance
1974         * @type        {string}
1975         * @default     '50% 50%'
1976         */
1977
1978        this.perspectiveOrigin = '50% 50%';
1979
1980        /**
1981         * A boolean dictating whether or not to enable the queuing of operations.
1982         *
1983         * If `true` (default), and a control is clicked or an API call is made while another
1984         * operation is progress, the operation will go into the queue and will be automatically exectuted
1985         * when the previous operaitons is finished.
1986         *
1987         * If `false`, any requested operations will be ignored, and the `onMixBusy` callback and `mixBusy`
1988         * event will be fired. If `debug.showWarnings` is enabled, a console warning will also occur.
1989         *
1990         * @example <caption>Example: Disable queuing</caption>
1991         * var mixer = mixitup(containerEl, {
1992         *     animation: {
1993         *         queue: false
1994         *     }
1995         * });
1996         *
1997         * @name        queue
1998         * @memberof    mixitup.Config.animation
1999         * @instance
2000         * @type        {boolean}
2001         * @default     true
2002         */
2003
2004        this.queue = true;
2005
2006        /**
2007         * An integer dictacting the maximum number of operations allowed in the queue at
2008         * any time, when queuing is enabled.
2009         *
2010         * @example <caption>Example: Allow a maximum of 5 operations in the queue at any time</caption>
2011         * var mixer = mixitup(containerEl, {
2012         *     animation: {
2013         *         queueLimit: 5
2014         *     }
2015         * });
2016         *
2017         * @name        queueLimit
2018         * @memberof    mixitup.Config.animation
2019         * @instance
2020         * @type        {number}
2021         * @default     3
2022         */
2023
2024        this.queueLimit = 3;
2025
2026        /**
2027         * A boolean dictating whether or not to transition the height and width of the
2028         * container as elements are filtered in and out. If disabled, the container height
2029         * will change abruptly.
2030         *
2031         * It may be desirable to disable this on mobile devices as the CSS `height` and
2032         * `width` properties do not receive GPU-acceleration and can therefore cause stuttering.
2033         *
2034         * @example <caption>Example 1: Disable the transitioning of the container height and/or width</caption>
2035         * var mixer = mixitup(containerEl, {
2036         *     animation: {
2037         *         animateResizeContainer: false
2038         *     }
2039         * });
2040         *
2041         * @example <caption>Example 2: Disable the transitioning of the container height and/or width for mobile devices only</caption>
2042         * var mixer = mixitup(containerEl, {
2043         *     animation: {
2044         *         animateResizeContainer: myFeatureTests.isMobile ? false : true
2045         *     }
2046         * });
2047         *
2048         * @name        animateResizeContainer
2049         * @memberof    mixitup.Config.animation
2050         * @instance
2051         * @type        {boolean}
2052         * @default     true
2053         */
2054
2055        this.animateResizeContainer = true;
2056
2057        /**
2058         * A boolean dictating whether or not to transition the height and width of target
2059         * elements as they change throughout the course of an animation.
2060         *
2061         * This is often a must for flex-box grid layouts where the size of target elements may change
2062         * depending on final their position in relation to their siblings, or for `.changeLayout()`
2063         * operations where the size of targets change between layouts.
2064         *
2065         * NB: This feature requires additional calculations and manipulation to non-hardware-accelerated
2066         * properties which may adversely affect performance on slower devices, and is therefore
2067         * disabled by default.
2068         *
2069         * @example <caption>Example: Enable the transitioning of target widths and heights</caption>
2070         * var mixer = mixitup(containerEl, {
2071         *     animation: {
2072         *         animateResizeTargets: true
2073         *     }
2074         * });
2075         *
2076         * @name        animateResizeTargets
2077         * @memberof    mixitup.Config.animation
2078         * @instance
2079         * @type        {boolean}
2080         * @default     false
2081         */
2082
2083        this.animateResizeTargets = false;
2084
2085        /**
2086         * A custom function used to manipulate the order in which the stagger delay is
2087         * incremented when using the ‘stagger’ effect.
2088         *
2089         * When using the 'stagger' effect, the delay applied to each target element is incremented
2090         * based on its index. You may create a custom function to manipulate the order in which the
2091         * delay is incremented and create engaging non-linear stagger effects.
2092         *
2093         * The function receives the index of the target element as a parameter, and must
2094         * return an integer which serves as the multiplier for the stagger delay.
2095         *
2096         * @example <caption>Example 1: Stagger target elements by column in a 3-column grid</caption>
2097         * var mixer = mixitup(containerEl, {
2098         *     animation: {
2099         *         effects: 'fade stagger(100ms)',
2100         *         staggerSequence: function(i) {
2101         *             return i % 3;
2102         *         }
2103         *     }
2104         * });
2105         *
2106         * @example <caption>Example 2: Using an algorithm to produce a more complex sequence</caption>
2107         * var mixer = mixitup(containerEl, {
2108         *     animation: {
2109         *         effects: 'fade stagger(100ms)',
2110         *         staggerSequence: function(i) {
2111         *             return (2*i) - (5*((i/3) - ((1/3) * (i%3))));
2112         *         }
2113         *     }
2114         * });
2115         *
2116         * @name        staggerSequence
2117         * @memberof    mixitup.Config.animation
2118         * @instance
2119         * @type        {function}
2120         * @default     null
2121         */
2122
2123        this.staggerSequence = null;
2124
2125        /**
2126         * A boolean dictating whether or not to reverse the direction of `translate`
2127         * and `rotate` transforms for elements being filtered out.
2128         *
2129         * It can be used to create carousel-like animations where elements enter and exit
2130         * from opposite directions. If enabled, the effect `translateX(-100%)` for elements
2131         * being filtered in would become `translateX(100%)` for targets being filtered out.
2132         *
2133         * This functionality can also be achieved by providing seperate effects
2134         * strings for `config.animation.effectsIn` and `config.animation.effectsOut`.
2135         *
2136         * @example <caption>Example: Reverse the desired direction on any translate/rotate effect for targets being filtered out</caption>
2137         * // Elements being filtered in will be translated from '100%' to '0' while
2138         * // elements being filtered out will be translated from 0 to '-100%'
2139         *
2140         * var mixer = mixitup(containerEl, {
2141         *     animation: {
2142         *         effects: 'fade translateX(100%)',
2143         *         reverseOut: true,
2144         *         nudge: false // Disable nudging to create a carousel-like effect
2145         *     }
2146         * });
2147         *
2148         * @name        reverseOut
2149         * @memberof    mixitup.Config.animation
2150         * @instance
2151         * @type        {boolean}
2152         * @default     false
2153         */
2154
2155        this.reverseOut = false;
2156
2157        /**
2158         * A boolean dictating whether or not to "nudge" the animation path of targets
2159         * when they are being filtered in and out simulatenously.
2160         *
2161         * This has been the default behavior of MixItUp since version 1, but it
2162         * may be desirable to disable this effect when filtering directly from
2163         * one exclusive set of targets to a different exclusive set of targets,
2164         * to create a carousel-like effect, or a generally more subtle animation.
2165         *
2166         * @example <caption>Example: Disable the "nudging" of targets being filtered in and out simulatenously</caption>
2167         *
2168         * var mixer = mixitup(containerEl, {
2169         *     animation: {
2170         *         nudge: false
2171         *     }
2172         * });
2173         *
2174         * @name        nudge
2175         * @memberof    mixitup.Config.animation
2176         * @instance
2177         * @type        {boolean}
2178         * @default     true
2179         */
2180
2181        this.nudge = true;
2182
2183        /**
2184         * A boolean dictating whether or not to clamp the height of the container while MixItUp's
2185         * geometry tests are carried out before an operation.
2186         *
2187         * To prevent scroll-bar flicker, clamping is turned on by default. But in the case where the
2188         * height of the container might affect its vertical positioning in the viewport
2189         * (e.g. a vertically-centered container), this should be turned off to ensure accurate
2190         * test results and a smooth animation.
2191         *
2192         * @example <caption>Example: Disable container height-clamping</caption>
2193         *
2194         * var mixer = mixitup(containerEl, {
2195         *     animation: {
2196         *         clampHeight: false
2197         *     }
2198         * });
2199         *
2200         * @name        clampHeight
2201         * @memberof    mixitup.Config.animation
2202         * @instance
2203         * @type        {boolean}
2204         * @default     true
2205         */
2206
2207        this.clampHeight = true;
2208
2209        /**
2210         * A boolean dictating whether or not to clamp the width of the container while MixItUp's
2211         * geometry tests are carried out before an operation.
2212         *
2213         * To prevent scroll-bar flicker, clamping is turned on by default. But in the case where the
2214         * width of the container might affect its horitzontal positioning in the viewport
2215         * (e.g. a horizontall-centered container), this should be turned off to ensure accurate
2216         * test results and a smooth animation.
2217         *
2218         * @example <caption>Example: Disable container width-clamping</caption>
2219         *
2220         * var mixer = mixitup(containerEl, {
2221         *     animation: {
2222         *         clampWidth: false
2223         *     }
2224         * });
2225         *
2226         * @name        clampWidth
2227         * @memberof    mixitup.Config.animation
2228         * @instance
2229         * @type        {boolean}
2230         * @default     true
2231         */
2232
2233        this.clampWidth = true;
2234
2235        this.callActions('afterConstruct');
2236
2237        h.seal(this);
2238    };
2239
2240    mixitup.BaseStatic.call(mixitup.ConfigAnimation);
2241
2242    mixitup.ConfigAnimation.prototype = Object.create(mixitup.Base.prototype);
2243
2244    mixitup.ConfigAnimation.prototype.constructor = mixitup.ConfigAnimation;
2245
2246    /**
2247     * A group of properties relating to the behavior of the Mixer.
2248     *
2249     * @constructor
2250     * @memberof    mixitup.Config
2251     * @name        behavior
2252     * @namespace
2253     * @public
2254     * @since       3.1.12
2255     */
2256
2257    mixitup.ConfigBehavior = function() {
2258        mixitup.Base.call(this);
2259
2260        this.callActions('beforeConstruct');
2261
2262        /**
2263         * A boolean dictating whether to allow "live" sorting of the mixer.
2264         *
2265         * Because of the expensive nature of sorting, MixItUp makes use of several
2266         * internal optimizations to skip redundant sorting operations, such as when
2267         * the newly requested sort command is the same as the active one. The caveat
2268         * to this optimization is that "live" edits to the value of a target's sorting
2269         * attribute will be ignored when requesting a re-sort by the same attribute.
2270         *
2271         * By setting to `behavior.liveSort` to `true`, the mixer will always re-sort
2272         * regardless of whether or not the sorting attribute and order have changed.
2273         *
2274         * @example <caption>Example: Enabling `liveSort` to allow for re-sorting</caption>
2275         *
2276         * var mixer = mixitup(containerEl, {
2277         *     behavior: {
2278         *         liveSort: true
2279         *     },
2280         *     load: {
2281         *         sort: 'edited:desc'
2282         *     }
2283         * });
2284         *
2285         * var target = containerEl.children[3];
2286         *
2287         * console.log(target.getAttribute('data-edited')); // '2015-04-24'
2288         *
2289         * target.setAttribute('data-edited', '2017-08-10'); // Update the target's edited date
2290         *
2291         * mixer.sort('edited:desc')
2292         *     .then(function(state) {
2293         *         // The target is now at the top of the list
2294         *
2295         *         console.log(state.targets[0] === target); // true
2296         *     });
2297         *
2298         * @name        liveSort
2299         * @memberof    mixitup.Config.behavior
2300         * @instance
2301         * @type        {boolean}
2302         * @default     false
2303         */
2304
2305        this.liveSort = false;
2306
2307        this.callActions('afterConstruct');
2308
2309        h.seal(this);
2310    };
2311
2312    mixitup.BaseStatic.call(mixitup.ConfigBehavior);
2313
2314    mixitup.ConfigBehavior.prototype = Object.create(mixitup.Base.prototype);
2315
2316    mixitup.ConfigBehavior.prototype.constructor = mixitup.ConfigBehavior;
2317
2318    /**
2319     * A group of optional callback functions to be invoked at various
2320     * points within the lifecycle of a mixer operation.
2321     *
2322     * Each function is analogous to an event of the same name triggered from the
2323     * container element, and is invoked immediately after it.
2324     *
2325     * All callback functions receive the current `state` object as their first
2326     * argument, as well as other more specific arguments described below.
2327     *
2328     * @constructor
2329     * @memberof    mixitup.Config
2330     * @name        callbacks
2331     * @namespace
2332     * @public
2333     * @since       2.0.0
2334     */
2335
2336    mixitup.ConfigCallbacks = function() {
2337        mixitup.Base.call(this);
2338
2339        this.callActions('beforeConstruct');
2340
2341        /**
2342         * A callback function invoked immediately after any MixItUp operation is requested
2343         * and before animations have begun.
2344         *
2345         * A second `futureState` argument is passed to the function which represents the final
2346         * state of the mixer once the requested operation has completed.
2347         *
2348         * @example <caption>Example: Adding an `onMixStart` callback function</caption>
2349         * var mixer = mixitup(containerEl, {
2350         *     callbacks: {
2351         *         onMixStart: function(state, futureState) {
2352         *              console.log('Starting operation...');
2353         *         }
2354         *     }
2355         * });
2356         *
2357         * @name        onMixStart
2358         * @memberof    mixitup.Config.callbacks
2359         * @instance
2360         * @type        {function}
2361         * @default     null
2362         */
2363
2364        this.onMixStart = null;
2365
2366        /**
2367         * A callback function invoked when a MixItUp operation is requested while another
2368         * operation is in progress, and the animation queue is full, or queueing
2369         * is disabled.
2370         *
2371         * @example <caption>Example: Adding an `onMixBusy` callback function</caption>
2372         * var mixer = mixitup(containerEl, {
2373         *     callbacks: {
2374         *         onMixBusy: function(state) {
2375         *              console.log('Mixer busy');
2376         *         }
2377         *     }
2378         * });
2379         *
2380         * @name        onMixBusy
2381         * @memberof    mixitup.Config.callbacks
2382         * @instance
2383         * @type        {function}
2384         * @default     null
2385         */
2386
2387        this.onMixBusy  = null;
2388
2389        /**
2390         * A callback function invoked after any MixItUp operation has completed, and the
2391         * state has been updated.
2392         *
2393         * @example <caption>Example: Adding an `onMixEnd` callback function</caption>
2394         * var mixer = mixitup(containerEl, {
2395         *     callbacks: {
2396         *         onMixEnd: function(state) {
2397         *              console.log('Operation complete');
2398         *         }
2399         *     }
2400         * });
2401         *
2402         * @name        onMixEnd
2403         * @memberof    mixitup.Config.callbacks
2404         * @instance
2405         * @type        {function}
2406         * @default     null
2407         */
2408
2409        this.onMixEnd   = null;
2410
2411        /**
2412         * A callback function invoked whenever an operation "fails", i.e. no targets
2413         * could be found matching the requested filter.
2414         *
2415         * @example <caption>Example: Adding an `onMixFail` callback function</caption>
2416         * var mixer = mixitup(containerEl, {
2417         *     callbacks: {
2418         *         onMixFail: function(state) {
2419         *              console.log('No items could be found matching the requested filter');
2420         *         }
2421         *     }
2422         * });
2423         *
2424         * @name        onMixFail
2425         * @memberof    mixitup.Config.callbacks
2426         * @instance
2427         * @type        {function}
2428         * @default     null
2429         */
2430
2431        this.onMixFail  = null;
2432
2433        /**
2434         * A callback function invoked whenever a MixItUp control is clicked, and before its
2435         * respective operation is requested.
2436         *
2437         * The clicked element is assigned to the `this` keyword within the function. The original
2438         * click event is passed to the function as the second argument, which can be useful if
2439         * using `<a>` tags as controls where the default behavior needs to be prevented.
2440         *
2441         * Returning `false` from the callback will prevent the control click from triggering
2442         * an operation.
2443         *
2444         * @example <caption>Example 1: Adding an `onMixClick` callback function</caption>
2445         * var mixer = mixitup(containerEl, {
2446         *     callbacks: {
2447         *         onMixClick: function(state, originalEvent) {
2448         *              console.log('The control "' + this.innerText + '" was clicked');
2449         *         }
2450         *     }
2451         * });
2452         *
2453         * @example <caption>Example 2: Using `onMixClick` to manipulate the original click event</caption>
2454         * var mixer = mixitup(containerEl, {
2455         *     callbacks: {
2456         *         onMixClick: function(state, originalEvent) {
2457         *              // Prevent original click event from bubbling up:
2458         *              originalEvent.stopPropagation();
2459         *
2460         *              // Prevent default behavior of clicked element:
2461         *              originalEvent.preventDefault();
2462         *         }
2463         *     }
2464         * });
2465         *
2466         * @example <caption>Example 3: Using `onMixClick` to conditionally cancel operations</caption>
2467         * var mixer = mixitup(containerEl, {
2468         *     callbacks: {
2469         *         onMixClick: function(state, originalEvent) {
2470         *              // Perform some conditional check:
2471         *
2472         *              if (myApp.isLoading) {
2473         *                  // By returning false, we can prevent the control click from triggering an operation.
2474         *
2475         *                  return false;
2476         *              }
2477         *         }
2478         *     }
2479         * });
2480         *
2481         * @name        onMixClick
2482         * @memberof    mixitup.Config.callbacks
2483         * @instance
2484         * @type        {function}
2485         * @default     null
2486         */
2487
2488        this.onMixClick = null;
2489
2490        this.callActions('afterConstruct');
2491
2492        h.seal(this);
2493    };
2494
2495    mixitup.BaseStatic.call(mixitup.ConfigCallbacks);
2496
2497    mixitup.ConfigCallbacks.prototype = Object.create(mixitup.Base.prototype);
2498
2499    mixitup.ConfigCallbacks.prototype.constructor = mixitup.ConfigCallbacks;
2500
2501    /**
2502     * A group of properties relating to clickable control elements.
2503     *
2504     * @constructor
2505     * @memberof    mixitup.Config
2506     * @name        controls
2507     * @namespace
2508     * @public
2509     * @since       2.0.0
2510     */
2511
2512    mixitup.ConfigControls = function() {
2513        mixitup.Base.call(this);
2514
2515        this.callActions('beforeConstruct');
2516
2517        /**
2518         * A boolean dictating whether or not controls should be enabled for the mixer instance.
2519         *
2520         * If `true` (default behavior), MixItUp will search the DOM for any clickable elements with
2521         * `data-filter`, `data-sort` or `data-toggle` attributes, and bind them for click events.
2522         *
2523         * If `false`, no click handlers will be bound, and all functionality must therefore be performed
2524         * via the mixer's API methods.
2525         *
2526         * If you do not intend to use the default controls, setting this property to `false` will
2527         * marginally improve the startup time of your mixer instance, and will also prevent any other active
2528         * mixer instances in the DOM which are bound to controls from controlling the instance.
2529         *
2530         * @example <caption>Example: Disabling controls</caption>
2531         * var mixer = mixitup(containerEl, {
2532         *     controls: {
2533         *         enable: false
2534         *     }
2535         * });
2536         *
2537         * // With the default controls disabled, we can only control
2538         * // the mixer via its API methods, e.g.:
2539         *
2540         * mixer.filter('.cat-1');
2541         *
2542         * @name        enable
2543         * @memberof    mixitup.Config.controls
2544         * @instance
2545         * @type        {boolean}
2546         * @default     true
2547         */
2548
2549        this.enable = true;
2550
2551        /**
2552         * A boolean dictating whether or not to use event delegation when binding click events
2553         * to the default controls.
2554         *
2555         * If `false` (default behavior), each control button in the DOM will be found and
2556         * individually bound when a mixer is instantiated, with their corresponding actions
2557         * cached for performance.
2558         *
2559         * If `true`, a single click handler will be applied to the `window` (or container element - see
2560         * `config.controls.scope`), and any click events triggered by elements with `data-filter`,
2561         * `data-sort` or `data-toggle` attributes present will be handled as they propagate upwards.
2562         *
2563         * If you require a user interface where control buttons may be added, removed, or changed during the
2564         * lifetime of a mixer, `controls.live` should be set to `true`. There is a marginal but unavoidable
2565         * performance deficit when using live controls, as the value of each control button must be read
2566         * from the DOM in real time once the click event has propagated.
2567         *
2568         * @example <caption>Example: Setting live controls</caption>
2569         * var mixer = mixitup(containerEl, {
2570         *     controls: {
2571         *         live: true
2572         *     }
2573         * });
2574         *
2575         * // Control buttons can now be added, remove and changed without breaking
2576         * // the mixer's UI
2577         *
2578         * @name        live
2579         * @memberof    mixitup.Config.controls
2580         * @instance
2581         * @type        {boolean}
2582         * @default     true
2583         */
2584
2585        this.live = false;
2586
2587        /**
2588         * A string dictating the "scope" to use when binding or querying the default controls. The available
2589         * values are `'global'` or `'local'`.
2590         *
2591         * When set to `'global'` (default behavior), MixItUp will query the entire document for control buttons
2592         * to bind, or delegate click events from (see `config.controls.live`).
2593         *
2594         * When set to `'local'`, MixItUp will only query (or bind click events to) its own container element.
2595         * This may be desireable if you require multiple active mixer instances within the same document, with
2596         * controls that would otherwise intefere with each other if scoped globally.
2597         *
2598         * Conversely, if you wish to control multiple instances with a single UI, you would create one
2599         * set of controls and keep the controls scope of each mixer set to `global`.
2600         *
2601         * @example <caption>Example: Setting 'local' scoped controls</caption>
2602         * var mixerOne = mixitup(containerOne, {
2603         *     controls: {
2604         *         scope: 'local'
2605         *     }
2606         * });
2607         *
2608         * var mixerTwo = mixitup(containerTwo, {
2609         *     controls: {
2610         *         scope: 'local'
2611         *     }
2612         * });
2613         *
2614         * // Both mixers can now exist within the same document with
2615         * // isolated controls placed within their container elements.
2616         *
2617         * @name        scope
2618         * @memberof    mixitup.Config.controls
2619         * @instance
2620         * @type        {string}
2621         * @default     'global'
2622         */
2623
2624        this.scope = 'global'; // enum: ['local' ,'global']
2625
2626        /**
2627         * A string dictating the type of logic to apply when concatenating the filter selectors of
2628         * active toggle buttons (i.e. any clickable element with a `data-toggle` attribute).
2629         *
2630         * If set to `'or'` (default behavior), selectors will be concatenated together as
2631         * a comma-seperated list. For example:
2632         *
2633         * `'.cat-1, .cat-2'` (shows any elements matching `'.cat-1'` OR `'.cat-2'`)
2634         *
2635         * If set to `'and'`, selectors will be directly concatenated together. For example:
2636         *
2637         * `'.cat-1.cat-2'` (shows any elements which match both `'.cat-1'` AND `'.cat-2'`)
2638         *
2639         * @example <caption>Example: Setting "and" toggle logic</caption>
2640         * var mixer = mixitup(containerEl, {
2641         *     controls: {
2642         *         toggleLogic: 'and'
2643         *     }
2644         * });
2645         *
2646         * @name        toggleLogic
2647         * @memberof    mixitup.Config.controls
2648         * @instance
2649         * @type        {string}
2650         * @default     'or'
2651         */
2652
2653        this.toggleLogic = 'or'; // enum: ['or', 'and']
2654
2655        /**
2656         * A string dictating the filter behavior when all toggles are inactive.
2657         *
2658         * When set to `'all'` (default behavior), *all* targets will be shown by default
2659         * when no toggles are active, or at the moment all active toggles are toggled off.
2660         *
2661         * When set to `'none'`, no targets will be shown by default when no toggles are
2662         * active, or at the moment all active toggles are toggled off.
2663         *
2664         * @example <caption>Example 1: Setting the default toggle behavior to `'all'`</caption>
2665         * var mixer = mixitup(containerEl, {
2666         *     controls: {
2667         *         toggleDefault: 'all'
2668         *     }
2669         * });
2670         *
2671         * mixer.toggleOn('.cat-2')
2672         *     .then(function() {
2673         *         // Deactivate all active toggles
2674         *
2675         *         return mixer.toggleOff('.cat-2')
2676         *     })
2677         *     .then(function(state) {
2678         *          console.log(state.activeFilter.selector); // 'all'
2679         *          console.log(state.totalShow); // 12
2680         *     });
2681         *
2682         * @example <caption>Example 2: Setting the default toggle behavior to `'none'`</caption>
2683         * var mixer = mixitup(containerEl, {
2684         *     controls: {
2685         *         toggleDefault: 'none'
2686         *     }
2687         * });
2688         *
2689         * mixer.toggleOn('.cat-2')
2690         *     .then(function() {
2691         *         // Deactivate all active toggles
2692         *
2693         *         return mixer.toggleOff('.cat-2')
2694         *     })
2695         *     .then(function(state) {
2696         *          console.log(state.activeFilter.selector); // 'none'
2697         *          console.log(state.totalShow); // 0
2698         *     });
2699         *
2700         * @name        toggleDefault
2701         * @memberof    mixitup.Config.controls
2702         * @instance
2703         * @type        {string}
2704         * @default     'all'
2705         */
2706
2707        this.toggleDefault = 'all'; // enum: ['all', 'none']
2708
2709        this.callActions('afterConstruct');
2710
2711        h.seal(this);
2712    };
2713
2714    mixitup.BaseStatic.call(mixitup.ConfigControls);
2715
2716    mixitup.ConfigControls.prototype = Object.create(mixitup.Base.prototype);
2717
2718    mixitup.ConfigControls.prototype.constructor = mixitup.ConfigControls;
2719
2720    /**
2721     * A group of properties defining the output and structure of class names programmatically
2722     * added to controls and containers to reflect the state of the mixer.
2723     *
2724     * Most commonly, class names are added to controls by MixItUp to indicate that
2725     * the control is active so that it can be styled accordingly - `'mixitup-control-active'` by default.
2726     *
2727     * Using a "BEM" like structure, each classname is broken into the three parts:
2728     * a block namespace (`'mixitup'`), an element name (e.g. `'control'`), and an optional modifier
2729     * name (e.g. `'active'`) reflecting the state of the element.
2730     *
2731     * By default, each part of the classname is concatenated together using single hyphens as
2732     * delineators, but this can be easily customised to match the naming convention and style of
2733     * your project.
2734     *
2735     * @constructor
2736     * @memberof    mixitup.Config
2737     * @name        classNames
2738     * @namespace
2739     * @public
2740     * @since       3.0.0
2741     */
2742
2743    mixitup.ConfigClassNames = function() {
2744        mixitup.Base.call(this);
2745
2746        this.callActions('beforeConstruct');
2747
2748        /**
2749         * The "block" portion, or top-level namespace added to the start of any class names created by MixItUp.
2750         *
2751         * @example <caption>Example 1: changing the `config.classNames.block` value</caption>
2752         * var mixer = mixitup(containerEl, {
2753         *     classNames: {
2754         *         block: 'portfolio'
2755         *     }
2756         * });
2757         *
2758         * // Active control output: "portfolio-control-active"
2759         *
2760         * @example <caption>Example 2: Removing `config.classNames.block`</caption>
2761         * var mixer = mixitup(containerEl, {
2762         *     classNames: {
2763         *         block: ''
2764         *     }
2765         * });
2766         *
2767         * // Active control output: "control-active"
2768         *
2769         * @name        block
2770         * @memberof    mixitup.Config.classNames
2771         * @instance
2772         * @type        {string}
2773         * @default     'mixitup'
2774         */
2775
2776        this.block = 'mixitup';
2777
2778        /**
2779         * The "element" portion of the class name added to container.
2780         *
2781         * @name        elementContainer
2782         * @memberof    mixitup.Config.classNames
2783         * @instance
2784         * @type        {string}
2785         * @default     'container'
2786         */
2787
2788        this.elementContainer = 'container';
2789
2790        /**
2791         * The "element" portion of the class name added to filter controls.
2792         *
2793         * By default, all filter, sort, multimix and toggle controls take the same element value of `'control'`, but
2794         * each type's element value can be individually overwritten to match the unique classNames of your controls as needed.
2795         *
2796         * @example <caption>Example 1: changing the `config.classNames.elementFilter` value</caption>
2797         * var mixer = mixitup(containerEl, {
2798         *     classNames: {
2799         *         elementFilter: 'filter'
2800         *     }
2801         * });
2802         *
2803         * // Active filter output: "mixitup-filter-active"
2804         *
2805         * @example <caption>Example 2: changing the `config.classNames.block` and `config.classNames.elementFilter` values</caption>
2806         * var mixer = mixitup(containerEl, {
2807         *     classNames: {
2808         *         block: 'portfolio',
2809         *         elementFilter: 'filter'
2810         *     }
2811         * });
2812         *
2813         * // Active filter output: "portfolio-filter-active"
2814         *
2815         * @name        elementFilter
2816         * @memberof    mixitup.Config.classNames
2817         * @instance
2818         * @type        {string}
2819         * @default     'control'
2820         */
2821
2822        this.elementFilter = 'control';
2823
2824        /**
2825         * The "element" portion of the class name added to sort controls.
2826         *
2827         * By default, all filter, sort, multimix and toggle controls take the same element value of `'control'`, but
2828         * each type's element value can be individually overwritten to match the unique classNames of your controls as needed.
2829         *
2830         * @example <caption>Example 1: changing the `config.classNames.elementSort` value</caption>
2831         * var mixer = mixitup(containerEl, {
2832         *     classNames: {
2833         *         elementSort: 'sort'
2834         *     }
2835         * });
2836         *
2837         * // Active sort output: "mixitup-sort-active"
2838         *
2839         * @example <caption>Example 2: changing the `config.classNames.block` and `config.classNames.elementSort` values</caption>
2840         * var mixer = mixitup(containerEl, {
2841         *     classNames: {
2842         *         block: 'portfolio',
2843         *         elementSort: 'sort'
2844         *     }
2845         * });
2846         *
2847         * // Active sort output: "portfolio-sort-active"
2848         *
2849         * @name        elementSort
2850         * @memberof    mixitup.Config.classNames
2851         * @instance
2852         * @type        {string}
2853         * @default     'control'
2854         */
2855
2856        this.elementSort = 'control';
2857
2858        /**
2859         * The "element" portion of the class name added to multimix controls.
2860         *
2861         * By default, all filter, sort, multimix and toggle controls take the same element value of `'control'`, but
2862         * each type's element value can be individually overwritten to match the unique classNames of your controls as needed.
2863         *
2864         * @example <caption>Example 1: changing the `config.classNames.elementMultimix` value</caption>
2865         * var mixer = mixitup(containerEl, {
2866         *     classNames: {
2867         *         elementMultimix: 'multimix'
2868         *     }
2869         * });
2870         *
2871         * // Active multimix output: "mixitup-multimix-active"
2872         *
2873         * @example <caption>Example 2: changing the `config.classNames.block` and `config.classNames.elementMultimix` values</caption>
2874         * var mixer = mixitup(containerEl, {
2875         *     classNames: {
2876         *         block: 'portfolio',
2877         *         elementSort: 'multimix'
2878         *     }
2879         * });
2880         *
2881         * // Active multimix output: "portfolio-multimix-active"
2882         *
2883         * @name        elementMultimix
2884         * @memberof    mixitup.Config.classNames
2885         * @instance
2886         * @type        {string}
2887         * @default     'control'
2888         */
2889
2890        this.elementMultimix = 'control';
2891
2892        /**
2893         * The "element" portion of the class name added to toggle controls.
2894         *
2895         * By default, all filter, sort, multimix and toggle controls take the same element value of `'control'`, but
2896         * each type's element value can be individually overwritten to match the unique classNames of your controls as needed.
2897         *
2898         * @example <caption>Example 1: changing the `config.classNames.elementToggle` value</caption>
2899         * var mixer = mixitup(containerEl, {
2900         *     classNames: {
2901         *         elementToggle: 'toggle'
2902         *     }
2903         * });
2904         *
2905         * // Active toggle output: "mixitup-toggle-active"
2906         *
2907         * @example <caption>Example 2: changing the `config.classNames.block` and `config.classNames.elementToggle` values</caption>
2908         * var mixer = mixitup(containerEl, {
2909         *     classNames: {
2910         *         block: 'portfolio',
2911         *         elementToggle: 'toggle'
2912         *     }
2913         * });
2914         *
2915         * // Active toggle output: "portfolio-toggle-active"
2916         *
2917         * @name        elementToggle
2918         * @memberof    mixitup.Config.classNames
2919         * @instance
2920         * @type        {string}
2921         * @default     'control'
2922         */
2923
2924        this.elementToggle = 'control';
2925
2926        /**
2927         * The "modifier" portion of the class name added to active controls.
2928         * @name        modifierActive
2929         * @memberof    mixitup.Config.classNames
2930         * @instance
2931         * @type        {string}
2932         * @default     'active'
2933         */
2934
2935        this.modifierActive = 'active';
2936
2937        /**
2938         * The "modifier" portion of the class name added to disabled controls.
2939         *
2940         * @name        modifierDisabled
2941         * @memberof    mixitup.Config.classNames
2942         * @instance
2943         * @type        {string}
2944         * @default     'disabled'
2945         */
2946
2947        this.modifierDisabled = 'disabled';
2948
2949        /**
2950         * The "modifier" portion of the class name added to the container when in a "failed" state.
2951         *
2952         * @name        modifierFailed
2953         * @memberof    mixitup.Config.classNames
2954         * @instance
2955         * @type        {string}
2956         * @default     'failed'
2957         */
2958
2959        this.modifierFailed = 'failed';
2960
2961        /**
2962         * The delineator used between the "block" and "element" portions of any class name added by MixItUp.
2963         *
2964         * If the block portion is ommited by setting it to an empty string, no delineator will be added.
2965         *
2966         * @example <caption>Example: changing the delineator to match BEM convention</caption>
2967         * var mixer = mixitup(containerEl, {
2968         *     classNames: {
2969         *         delineatorElement: '__'
2970         *     }
2971         * });
2972         *
2973         * // example active control output: "mixitup__control-active"
2974         *
2975         * @name        delineatorElement
2976         * @memberof    mixitup.Config.classNames
2977         * @instance
2978         * @type        {string}
2979         * @default     '-'
2980         */
2981
2982        this.delineatorElement = '-';
2983
2984        /**
2985         * The delineator used between the "element" and "modifier" portions of any class name added by MixItUp.
2986         *
2987         * If the element portion is ommited by setting it to an empty string, no delineator will be added.
2988         *
2989         * @example <caption>Example: changing both delineators to match BEM convention</caption>
2990         * var mixer = mixitup(containerEl, {
2991         *     classNames: {
2992         *         delineatorElement: '__'
2993         *         delineatorModifier: '--'
2994         *     }
2995         * });
2996         *
2997         * // Active control output: "mixitup__control--active"
2998         *
2999         * @name        delineatorModifier
3000         * @memberof    mixitup.Config.classNames
3001         * @instance
3002         * @type        {string}
3003         * @default     '-'
3004         */
3005
3006        this.delineatorModifier = '-';
3007
3008        this.callActions('afterConstruct');
3009
3010        h.seal(this);
3011    };
3012
3013    mixitup.BaseStatic.call(mixitup.ConfigClassNames);
3014
3015    mixitup.ConfigClassNames.prototype = Object.create(mixitup.Base.prototype);
3016
3017    mixitup.ConfigClassNames.prototype.constructor = mixitup.ConfigClassNames;
3018
3019    /**
3020     * A group of properties relating to MixItUp's dataset API.
3021     *
3022     * @constructor
3023     * @memberof    mixitup.Config
3024     * @name        data
3025     * @namespace
3026     * @public
3027     * @since       3.0.0
3028     */
3029
3030    mixitup.ConfigData = function() {
3031        mixitup.Base.call(this);
3032
3033        this.callActions('beforeConstruct');
3034
3035        /**
3036         * A string specifying the name of the key containing your data model's unique
3037         * identifier (UID). To use the dataset API, a UID key must be specified and
3038         * be present and unique on all objects in the dataset you provide to MixItUp.
3039         *
3040         * For example, if your dataset is made up of MongoDB documents, the UID
3041         * key would be `'id'` or `'_id'`.
3042         *
3043         * @example <caption>Example: Setting the UID to `'id'`</caption>
3044         * var mixer = mixitup(containerEl, {
3045         *     data: {
3046         *         uidKey: 'id'
3047         *     }
3048         * });
3049         *
3050         * @name        uidKey
3051         * @memberof    mixitup.Config.data
3052         * @instance
3053         * @type        {string}
3054         * @default     ''
3055         */
3056
3057        this.uidKey = '';
3058
3059        /**
3060         * A boolean dictating whether or not MixItUp should "dirty check" each object in
3061         * your dataset for changes whenever `.dataset()` is called, and re-render any targets
3062         * for which a change is found.
3063         *
3064         * Depending on the complexity of your data model, dirty checking can be expensive
3065         * and is therefore disabled by default.
3066         *
3067         * NB: For changes to be detected, a new immutable instance of the edited model must be
3068         * provided to mixitup, rather than manipulating properties on the existing instance.
3069         * If your changes are a result of a DB write and read, you will most likely be calling
3070         * `.dataset()` with a clean set of objects each time, so this will not be an issue.
3071         *
3072         * @example <caption>Example: Enabling dirty checking</caption>
3073         *
3074         * var myDataset = [
3075         *     {
3076         *         id: 0,
3077         *         title: "Blog Post Title 0"
3078         *         ...
3079         *     },
3080         *     {
3081         *         id: 1,
3082         *         title: "Blog Post Title 1"
3083         *         ...
3084         *     }
3085         * ];
3086         *
3087         * // Instantiate a mixer with a pre-loaded dataset, and a target renderer
3088         * // function defined
3089         *
3090         * var mixer = mixitup(containerEl, {
3091         *     data: {
3092         *         uidKey: 'id',
3093         *         dirtyCheck: true
3094         *     },
3095         *     load: {
3096         *         dataset: myDataset
3097         *     },
3098         *     render: {
3099         *         target: function() { ... }
3100         *     }
3101         * });
3102         *
3103         * // For illustration, we will clone and edit the second object in the dataset.
3104         * // NB: this would typically be done server-side in response to a DB update,
3105         * and then re-queried via an API.
3106         *
3107         * myDataset[1] = Object.assign({}, myDataset[1]);
3108         *
3109         * myDataset[1].title = 'Blog Post Title 11';
3110         *
3111         * mixer.dataset(myDataset)
3112         *    .then(function() {
3113         *        // the target with ID "1", will be re-rendered reflecting its new title
3114         *    });
3115         *
3116         * @name        dirtyCheck
3117         * @memberof    mixitup.Config.data
3118         * @instance
3119         * @type        {boolean}
3120         * @default     false
3121         */
3122
3123        this.dirtyCheck = false;
3124
3125        this.callActions('afterConstruct');
3126
3127        h.seal(this);
3128    };
3129
3130    mixitup.BaseStatic.call(mixitup.ConfigData);
3131
3132    mixitup.ConfigData.prototype = Object.create(mixitup.Base.prototype);
3133
3134    mixitup.ConfigData.prototype.constructor = mixitup.ConfigData;
3135
3136    /**
3137     * A group of properties allowing the toggling of various debug features.
3138     *
3139     * @constructor
3140     * @memberof    mixitup.Config
3141     * @name        debug
3142     * @namespace
3143     * @public
3144     * @since       3.0.0
3145     */
3146
3147    mixitup.ConfigDebug = function() {
3148        mixitup.Base.call(this);
3149
3150        this.callActions('beforeConstruct');
3151
3152        /**
3153         * A boolean dictating whether or not the mixer instance returned by the
3154         * `mixitup()` factory function should expose private properties and methods.
3155         *
3156         * By default, mixer instances only expose their public API, but enabling
3157         * debug mode will give you access to various mixer internals which may aid
3158         * in debugging, or the authoring of extensions.
3159         *
3160         * @example <caption>Example: Enabling debug mode</caption>
3161         *
3162         * var mixer = mixitup(containerEl, {
3163         *     debug: {
3164         *         enable: true
3165         *     }
3166         * });
3167         *
3168         * // Private properties and methods will now be visible on the mixer instance:
3169         *
3170         * console.log(mixer);
3171         *
3172         * @name        enable
3173         * @memberof    mixitup.Config.debug
3174         * @instance
3175         * @type        {boolean}
3176         * @default     false
3177         */
3178
3179        this.enable = false;
3180
3181        /**
3182         * A boolean dictating whether or not warnings should be shown when various
3183         * common gotchas occur.
3184         *
3185         * Warnings are intended to provide insights during development when something
3186         * occurs that is not a fatal, but may indicate an issue with your integration,
3187         * and are therefore turned on by default. However, you may wish to disable
3188         * them in production.
3189         *
3190         * @example <caption>Example 1: Disabling warnings</caption>
3191         *
3192         * var mixer = mixitup(containerEl, {
3193         *     debug: {
3194         *         showWarnings: false
3195         *     }
3196         * });
3197         *
3198         * @example <caption>Example 2: Disabling warnings based on environment</caption>
3199         *
3200         * var showWarnings = myAppConfig.environment === 'development' ? true : false;
3201         *
3202         * var mixer = mixitup(containerEl, {
3203         *     debug: {
3204         *         showWarnings: showWarnings
3205         *     }
3206         * });
3207         *
3208         * @name        showWarnings
3209         * @memberof    mixitup.Config.debug
3210         * @instance
3211         * @type        {boolean}
3212         * @default     true
3213         */
3214
3215        this.showWarnings = true;
3216
3217        /**
3218         * Used for server-side testing only.
3219         *
3220         * @private
3221         * @name        fauxAsync
3222         * @memberof    mixitup.Config.debug
3223         * @instance
3224         * @type        {boolean}
3225         * @default     false
3226         */
3227
3228        this.fauxAsync = false;
3229
3230        this.callActions('afterConstruct');
3231
3232        h.seal(this);
3233    };
3234
3235    mixitup.BaseStatic.call(mixitup.ConfigDebug);
3236
3237    mixitup.ConfigDebug.prototype = Object.create(mixitup.Base.prototype);
3238
3239    mixitup.ConfigDebug.prototype.constructor = mixitup.ConfigDebug;
3240
3241    /**
3242     * A group of properties relating to the layout of the container.
3243     *
3244     * @constructor
3245     * @memberof    mixitup.Config
3246     * @name        layout
3247     * @namespace
3248     * @public
3249     * @since       3.0.0
3250     */
3251
3252    mixitup.ConfigLayout = function() {
3253        mixitup.Base.call(this);
3254
3255        this.callActions('beforeConstruct');
3256
3257        /**
3258         * A boolean dictating whether or not mixitup should query all descendants
3259         * of the container for targets, or only immediate children.
3260         *
3261         * By default, mixitup will query all descendants matching the
3262         * `selectors.target` selector when indexing targets upon instantiation.
3263         * This allows for targets to be nested inside a sub-container which is
3264         * useful when ring-fencing targets from locally scoped controls in your
3265         * markup (see `controls.scope`).
3266         *
3267         * However, if you are building a more complex UI requiring the nesting
3268         * of mixers within mixers, you will most likely want to limit targets to
3269         * immediate children of the container by setting this property to `false`.
3270         *
3271         * @example <caption>Example: Restricting targets to immediate children</caption>
3272         *
3273         * var mixer = mixitup(containerEl, {
3274         *     layout: {
3275         *         allowNestedTargets: false
3276         *     }
3277         * });
3278         *
3279         * @name        allowNestedTargets
3280         * @memberof    mixitup.Config.layout
3281         * @instance
3282         * @type        {boolean}
3283         * @default     true
3284         */
3285
3286        this.allowNestedTargets = true;
3287
3288        /**
3289         * A string specifying an optional class name to apply to the container when in
3290         * its default state.
3291         *
3292         * By changing this class name or adding a class name to the container via the
3293         * `.changeLayout()` API method, the CSS layout of the container can be changed,
3294         * and MixItUp will attemp to gracefully animate the container and its targets
3295         * between states.
3296         *
3297         * @example <caption>Example 1: Specifying a container class name</caption>
3298         *
3299         * var mixer = mixitup(containerEl, {
3300         *     layout: {
3301         *         containerClassName: 'grid'
3302         *     }
3303         * });
3304         *
3305         * @example <caption>Example 2: Changing the default class name with `.changeLayout()`</caption>
3306         *
3307         * var mixer = mixitup(containerEl, {
3308         *     layout: {
3309         *         containerClassName: 'grid'
3310         *     }
3311         * });
3312         *
3313         * mixer.changeLayout('list')
3314         *     .then(function(state) {
3315         *          console.log(state.activeContainerClass); // "list"
3316         *     });
3317         *
3318         * @name        containerClassName
3319         * @memberof    mixitup.Config.layout
3320         * @instance
3321         * @type        {string}
3322         * @default     ''
3323         */
3324
3325        this.containerClassName = '';
3326
3327        /**
3328         * A reference to a non-target sibling element after which to insert targets
3329         * when there are no targets in the container.
3330         *
3331         * @example <caption>Example: Setting a `siblingBefore` reference element</caption>
3332         *
3333         * var addButton = containerEl.querySelector('button');
3334         *
3335         * var mixer = mixitup(containerEl, {
3336         *     layout: {
3337         *         siblingBefore: addButton
3338         *     }
3339         * });
3340         *
3341         * @name        siblingBefore
3342         * @memberof    mixitup.Config.layout
3343         * @instance
3344         * @type        {HTMLElement}
3345         * @default     null
3346         */
3347
3348        this.siblingBefore = null;
3349
3350        /**
3351         * A reference to a non-target sibling element before which to insert targets
3352         * when there are no targets in the container.
3353         *
3354         * @example <caption>Example: Setting an `siblingAfter` reference element</caption>
3355         *
3356         * var gap = containerEl.querySelector('.gap');
3357         *
3358         * var mixer = mixitup(containerEl, {
3359         *     layout: {
3360         *         siblingAfter: gap
3361         *     }
3362         * });
3363         *
3364         * @name        siblingAfter
3365         * @memberof    mixitup.Config.layout
3366         * @instance
3367         * @type        {HTMLElement}
3368         * @default     null
3369         */
3370
3371        this.siblingAfter = null;
3372
3373        this.callActions('afterConstruct');
3374
3375        h.seal(this);
3376    };
3377
3378    mixitup.BaseStatic.call(mixitup.ConfigLayout);
3379
3380    mixitup.ConfigLayout.prototype = Object.create(mixitup.Base.prototype);
3381
3382    mixitup.ConfigLayout.prototype.constructor = mixitup.ConfigLayout;
3383
3384    /**
3385     * A group of properties defining the initial state of the mixer on load (instantiation).
3386     *
3387     * @constructor
3388     * @memberof    mixitup.Config
3389     * @name        load
3390     * @namespace
3391     * @public
3392     * @since       2.0.0
3393     */
3394
3395    mixitup.ConfigLoad = function() {
3396        mixitup.Base.call(this);
3397
3398        this.callActions('beforeConstruct');
3399
3400        /**
3401         * A string defining any filtering to be statically applied to the mixer on load.
3402         * As per the `.filter()` API, this can be any valid selector string, or the
3403         * values `'all'` or `'none'`.
3404         *
3405         * @example <caption>Example 1: Defining an initial filter selector to be applied on load</caption>
3406         *
3407         * // The mixer will show only those targets matching '.category-a' on load.
3408         *
3409         * var mixer = mixitup(containerEl, {
3410         *     load: {
3411         *         filter: '.category-a'
3412         *     }
3413         * });
3414         *
3415         * @example <caption>Example 2: Hiding all targets on load</caption>
3416         *
3417         * // The mixer will show hide all targets on load.
3418         *
3419         * var mixer = mixitup(containerEl, {
3420         *     load: {
3421         *         filter: 'none'
3422         *     }
3423         * });
3424         *
3425         * @name        filter
3426         * @memberof    mixitup.Config.load
3427         * @instance
3428         * @type        {string}
3429         * @default     'all'
3430         */
3431
3432        this.filter = 'all';
3433
3434        /**
3435         * A string defining any sorting to be statically applied to the mixer on load.
3436         * As per the `.sort()` API, this should be a valid "sort string" made up of
3437         * an attribute to sort by (or `'default'`) followed by an optional sorting
3438         * order, or the value `'random'`;
3439         *
3440         * @example <caption>Example: Defining sorting to be applied on load</caption>
3441         *
3442         * // The mixer will sort the container by the value of the `data-published-date`
3443         * // attribute, in descending order.
3444         *
3445         * var mixer = mixitup(containerEl, {
3446         *     load: {
3447         *         sort: 'published-date:desc'
3448         *     }
3449         * });
3450         *
3451         * @name        sort
3452         * @memberof    mixitup.Config.load
3453         * @instance
3454         * @type        {string}
3455         * @default     'default:asc'
3456         */
3457
3458        this.sort = 'default:asc';
3459
3460        /**
3461         * An array of objects representing the underlying data of any pre-rendered targets,
3462         * when using the `.dataset()` API.
3463         *
3464         * NB: If targets are pre-rendered when the mixer is instantiated, this must be set.
3465         *
3466         * @example <caption>Example: Defining the initial underyling dataset</caption>
3467         *
3468         * var myDataset = [
3469         *     {
3470         *         id: 0,
3471         *         title: "Blog Post Title 0",
3472         *         ...
3473         *     },
3474         *     {
3475         *         id: 1,
3476         *         title: "Blog Post Title 1",
3477         *         ...
3478         *     }
3479         * ];
3480         *
3481         * var mixer = mixitup(containerEl, {
3482         *     data: {
3483         *         uidKey: 'id'
3484         *     },
3485         *     load: {
3486         *         dataset: myDataset
3487         *     }
3488         * });
3489         *
3490         * @name        dataset
3491         * @memberof    mixitup.Config.load
3492         * @instance
3493         * @type        {Array.<object>}
3494         * @default     null
3495         */
3496
3497        this.dataset = null;
3498
3499        this.callActions('afterConstruct');
3500
3501        h.seal(this);
3502    };
3503
3504    mixitup.BaseStatic.call(mixitup.ConfigLoad);
3505
3506    mixitup.ConfigLoad.prototype = Object.create(mixitup.Base.prototype);
3507
3508    mixitup.ConfigLoad.prototype.constructor = mixitup.ConfigLoad;
3509
3510    /**
3511     * A group of properties defining the selectors used to query elements within a mixitup container.
3512     *
3513     * @constructor
3514     * @memberof    mixitup.Config
3515     * @name        selectors
3516     * @namespace
3517     * @public
3518     * @since       3.0.0
3519     */
3520
3521    mixitup.ConfigSelectors = function() {
3522        mixitup.Base.call(this);
3523
3524        this.callActions('beforeConstruct');
3525
3526        /**
3527         * A selector string used to query and index target elements within the container.
3528         *
3529         * By default, the class selector `'.mix'` is used, but this can be changed to an
3530         * attribute or element selector to match the style of your project.
3531         *
3532         * @example <caption>Example 1: Changing the target selector</caption>
3533         *
3534         * var mixer = mixitup(containerEl, {
3535         *     selectors: {
3536         *         target: '.portfolio-item'
3537         *     }
3538         * });
3539         *
3540         * @example <caption>Example 2: Using an attribute selector as a target selector</caption>
3541         *
3542         * // The mixer will search for any children with the attribute `data-ref="mix"`
3543         *
3544         * var mixer = mixitup(containerEl, {
3545         *     selectors: {
3546         *         target: '[data-ref="mix"]'
3547         *     }
3548         * });
3549         *
3550         * @name        target
3551         * @memberof    mixitup.Config.selectors
3552         * @instance
3553         * @type        {string}
3554         * @default     '.mix'
3555         */
3556
3557        this.target = '.mix';
3558
3559        /**
3560         * A optional selector string used to add further specificity to the querying of control elements,
3561         * in addition to their mandatory data attribute (e.g. `data-filter`, `data-toggle`, `data-sort`).
3562         *
3563         * This can be used if other elements in your document must contain the above attributes
3564         * (e.g. for use in third-party scripts), and would otherwise interfere with MixItUp. Adding
3565         * an additional `control` selector of your choice allows MixItUp to restrict event handling
3566         * to only those elements matching the defined selector.
3567         *
3568         * @name        control
3569         * @memberof    mixitup.Config.selectors
3570         * @instance
3571         * @type        {string}
3572         * @default     ''
3573         *
3574         * @example <caption>Example 1: Adding a `selectors.control` selector</caption>
3575         *
3576         * var mixer = mixitup(containerEl, {
3577         *     selectors: {
3578         *         control: '.mixitup-control'
3579         *     }
3580         * });
3581         *
3582         * // Will not be handled:
3583         * // <button data-filter=".category-a"></button>
3584         *
3585         * // Will be handled:
3586         * // <button class="mixitup-control" data-filter=".category-a"></button>
3587         */
3588
3589        this.control = '';
3590
3591        this.callActions('afterConstruct');
3592
3593        h.seal(this);
3594    };
3595
3596    mixitup.BaseStatic.call(mixitup.ConfigSelectors);
3597
3598    mixitup.ConfigSelectors.prototype = Object.create(mixitup.Base.prototype);
3599
3600    mixitup.ConfigSelectors.prototype.constructor = mixitup.ConfigSelectors;
3601
3602    /**
3603     * A group of optional render functions for creating and updating elements.
3604     *
3605     * All render functions receive a data object, and should return a valid HTML string.
3606     *
3607     * @constructor
3608     * @memberof    mixitup.Config
3609     * @name        render
3610     * @namespace
3611     * @public
3612     * @since       3.0.0
3613     */
3614
3615    mixitup.ConfigRender = function() {
3616        mixitup.Base.call(this);
3617
3618        this.callActions('beforeConstruct');
3619
3620        /**
3621         * A function returning an HTML string representing a target element, or a reference to a
3622         * single DOM element.
3623         *
3624         * The function is invoked as part of the `.dataset()` API, whenever a new item is added
3625         * to the dataset, or an item in the dataset changes (if `dataset.dirtyCheck` is enabled).
3626         *
3627         * The function receives the relevant dataset item as its first parameter.
3628         *
3629         * @example <caption>Example 1: Using string concatenation</caption>
3630         *
3631         * var mixer = mixitup(containerEl, {
3632         *     render: {
3633         *         target: function(item) {
3634         *             return (
3635         *                 '&lt;div class="mix"&gt;' +
3636         *                     '&lt;h2&gt;' + item.title + '&lt;/h2&gt;' +
3637         *                 '&lt;/div&gt;'
3638         *             );
3639         *         }
3640         *     }
3641         * });
3642         *
3643         * @example <caption>Example 2: Using an ES2015 template literal</caption>
3644         *
3645         * var mixer = mixitup(containerEl, {
3646         *     render: {
3647         *         target: function(item) {
3648         *             return (
3649         *                 `&lt;div class="mix"&gt;
3650         *                     &lt;h2&gt;${item.title}&lt;/h2&gt;
3651         *                  &lt;/div&gt;`
3652         *             );
3653         *         }
3654         *     }
3655         * });
3656         *
3657         * @example <caption>Example 3: Using a Handlebars template</caption>
3658         *
3659         * var targetTemplate = Handlebars.compile('&lt;div class="mix"&gt;&lt;h2&gt;{{title}}&lt;/h2&gt;&lt;/div&gt;');
3660         *
3661         * var mixer = mixitup(containerEl, {
3662         *     render: {
3663         *         target: targetTemplate
3664         *     }
3665         * });
3666         *
3667         * @example <caption>Example 4: Returning a DOM element</caption>
3668         *
3669         * var mixer = mixitup(containerEl, {
3670         *     render: {
3671         *         target: function(item) {
3672         *              // Create a single element using your framework's built-in renderer
3673         *
3674         *              var el = ...
3675         *
3676         *              return el;
3677         *         }
3678         *     }
3679         * });
3680         *
3681         * @name        target
3682         * @memberof    mixitup.Config.render
3683         * @instance
3684         * @type        {function}
3685         * @default     'null'
3686         */
3687
3688        this.target = null;
3689
3690        this.callActions('afterConstruct');
3691
3692        h.seal(this);
3693    };
3694
3695    mixitup.BaseStatic.call(mixitup.ConfigRender);
3696
3697    mixitup.ConfigRender.prototype = Object.create(mixitup.Base.prototype);
3698
3699    mixitup.ConfigRender.prototype.constructor = mixitup.ConfigRender;
3700
3701    /**
3702     * @constructor
3703     * @memberof    mixitup
3704     * @private
3705     * @since       3.0.0
3706     */
3707
3708    mixitup.ConfigTemplates = function() {
3709        mixitup.Base.call(this);
3710
3711        this.callActions('beforeConstruct');
3712
3713        this.callActions('afterConstruct');
3714
3715        h.seal(this);
3716    };
3717
3718    mixitup.BaseStatic.call(mixitup.ConfigTemplates);
3719
3720    mixitup.ConfigTemplates.prototype = Object.create(mixitup.Base.prototype);
3721
3722    mixitup.ConfigTemplates.prototype.constructor = mixitup.ConfigTemplates;
3723
3724    /**
3725     * `mixitup.Config` is an interface used for customising the functionality of a
3726     * mixer instance. It is organised into several semantically distinct sub-objects,
3727     * each one pertaining to a particular aspect of MixItUp functionality.
3728     *
3729     * An object literal containing any or all of the available properies,
3730     * known as the "configuration object", can be passed as the second parameter to
3731     * the `mixitup` factory function when creating a mixer instance to customise its
3732     * functionality as needed.
3733     *
3734     * If no configuration object is passed, the mixer instance will take on the default
3735     * configuration values detailed below.
3736     *
3737     * @example <caption>Example 1: Creating and passing the configuration object</caption>
3738     * // Create a configuration object with desired values
3739     *
3740     * var config = {
3741     *     animation: {
3742     *         enable: false
3743     *     },
3744     *     selectors: {
3745     *         target: '.item'
3746     *     }
3747     * };
3748     *
3749     * // Pass the configuration object to the mixitup factory function
3750     *
3751     * var mixer = mixitup(containerEl, config);
3752     *
3753     * @example <caption>Example 2: Passing the configuration object inline</caption>
3754     * // Typically, the configuration object is passed inline for brevity.
3755     *
3756     * var mixer = mixitup(containerEl, {
3757     *     controls: {
3758     *         live: true,
3759     *         toggleLogic: 'and'
3760     *     }
3761     * });
3762     *
3763     *
3764     * @constructor
3765     * @memberof    mixitup
3766     * @namespace
3767     * @public
3768     * @since       2.0.0
3769     */
3770
3771    mixitup.Config = function() {
3772        mixitup.Base.call(this);
3773
3774        this.callActions('beforeConstruct');
3775
3776        this.animation          = new mixitup.ConfigAnimation();
3777        this.behavior           = new mixitup.ConfigBehavior();
3778        this.callbacks          = new mixitup.ConfigCallbacks();
3779        this.controls           = new mixitup.ConfigControls();
3780        this.classNames         = new mixitup.ConfigClassNames();
3781        this.data               = new mixitup.ConfigData();
3782        this.debug              = new mixitup.ConfigDebug();
3783        this.layout             = new mixitup.ConfigLayout();
3784        this.load               = new mixitup.ConfigLoad();
3785        this.selectors          = new mixitup.ConfigSelectors();
3786        this.render             = new mixitup.ConfigRender();
3787        this.templates          = new mixitup.ConfigTemplates();
3788
3789        this.callActions('afterConstruct');
3790
3791        h.seal(this);
3792    };
3793
3794    mixitup.BaseStatic.call(mixitup.Config);
3795
3796    mixitup.Config.prototype = Object.create(mixitup.Base.prototype);
3797
3798    mixitup.Config.prototype.constructor = mixitup.Config;
3799
3800    /**
3801     * @constructor
3802     * @memberof    mixitup
3803     * @private
3804     * @since       3.0.0
3805     */
3806
3807    mixitup.MixerDom = function() {
3808        mixitup.Base.call(this);
3809
3810        this.callActions('beforeConstruct');
3811
3812        this.document               = null;
3813        this.body                   = null;
3814        this.container              = null;
3815        this.parent                 = null;
3816        this.targets                = [];
3817
3818        this.callActions('afterConstruct');
3819
3820        h.seal(this);
3821    };
3822
3823    mixitup.BaseStatic.call(mixitup.MixerDom);
3824
3825    mixitup.MixerDom.prototype = Object.create(mixitup.Base.prototype);
3826
3827    mixitup.MixerDom.prototype.constructor = mixitup.MixerDom;
3828
3829    /**
3830     * @constructor
3831     * @memberof    mixitup
3832     * @private
3833     * @since       3.0.0
3834     */
3835
3836    mixitup.UiClassNames = function() {
3837        mixitup.Base.call(this);
3838
3839        this.callActions('beforeConstruct');
3840
3841        this.base       = '';
3842        this.active     = '';
3843        this.disabled   = '';
3844
3845        this.callActions('afterConstruct');
3846
3847        h.seal(this);
3848    };
3849
3850    mixitup.BaseStatic.call(mixitup.UiClassNames);
3851
3852    mixitup.UiClassNames.prototype = Object.create(mixitup.Base.prototype);
3853
3854    mixitup.UiClassNames.prototype.constructor = mixitup.UiClassNames;
3855
3856    /**
3857     * An object into which all arbitrary arguments sent to '.dataset()' are mapped.
3858     *
3859     * @constructor
3860     * @memberof    mixitup
3861     * @private
3862     * @since       3.0.0
3863     */
3864
3865    mixitup.CommandDataset = function() {
3866        mixitup.Base.call(this);
3867
3868        this.callActions('beforeConstruct');
3869
3870        this.dataset = null;
3871
3872        this.callActions('afterConstruct');
3873
3874        h.seal(this);
3875    };
3876
3877    mixitup.BaseStatic.call(mixitup.CommandDataset);
3878
3879    mixitup.CommandDataset.prototype = Object.create(mixitup.Base.prototype);
3880
3881    mixitup.CommandDataset.prototype.constructor = mixitup.CommandDataset;
3882
3883    /**
3884     * An object into which all arbitrary arguments sent to '.multimix()' are mapped.
3885     *
3886     * @constructor
3887     * @memberof    mixitup
3888     * @private
3889     * @since       3.0.0
3890     */
3891
3892    mixitup.CommandMultimix = function() {
3893        mixitup.Base.call(this);
3894
3895        this.callActions('beforeConstruct');
3896
3897        this.filter       = null;
3898        this.sort         = null;
3899        this.insert       = null;
3900        this.remove       = null;
3901        this.changeLayout = null;
3902
3903        this.callActions('afterConstruct');
3904
3905        h.seal(this);
3906    };
3907
3908    mixitup.BaseStatic.call(mixitup.CommandMultimix);
3909
3910    mixitup.CommandMultimix.prototype = Object.create(mixitup.Base.prototype);
3911
3912    mixitup.CommandMultimix.prototype.constructor = mixitup.CommandMultimix;
3913
3914    /**
3915     * An object into which all arbitrary arguments sent to '.filter()' are mapped.
3916     *
3917     * @constructor
3918     * @memberof    mixitup
3919     * @private
3920     * @since       3.0.0
3921     */
3922
3923    mixitup.CommandFilter = function() {
3924        mixitup.Base.call(this);
3925
3926        this.callActions('beforeConstruct');
3927
3928        this.selector   = '';
3929        this.collection = null;
3930        this.action     = 'show'; // enum: ['show', 'hide']
3931
3932        this.callActions('afterConstruct');
3933
3934        h.seal(this);
3935    };
3936
3937    mixitup.BaseStatic.call(mixitup.CommandFilter);
3938
3939    mixitup.CommandFilter.prototype = Object.create(mixitup.Base.prototype);
3940
3941    mixitup.CommandFilter.prototype.constructor = mixitup.CommandFilter;
3942
3943    /**
3944     * An object into which all arbitrary arguments sent to '.sort()' are mapped.
3945     *
3946     * @constructor
3947     * @memberof    mixitup
3948     * @private
3949     * @since       3.0.0
3950     */
3951
3952    mixitup.CommandSort = function() {
3953        mixitup.Base.call(this);
3954
3955        this.callActions('beforeConstruct');
3956
3957        this.sortString = '';
3958        this.attribute  = '';
3959        this.order      = 'asc';
3960        this.collection = null;
3961        this.next       = null;
3962
3963        this.callActions('afterConstruct');
3964
3965        h.seal(this);
3966    };
3967
3968    mixitup.BaseStatic.call(mixitup.CommandSort);
3969
3970    mixitup.CommandSort.prototype = Object.create(mixitup.Base.prototype);
3971
3972    mixitup.CommandSort.prototype.constructor = mixitup.CommandSort;
3973
3974    /**
3975     * An object into which all arbitrary arguments sent to '.insert()' are mapped.
3976     *
3977     * @constructor
3978     * @memberof    mixitup
3979     * @private
3980     * @since       3.0.0
3981     */
3982
3983    mixitup.CommandInsert = function() {
3984        mixitup.Base.call(this);
3985
3986        this.callActions('beforeConstruct');
3987
3988        this.index      = 0;
3989        this.collection = [];
3990        this.position   = 'before'; // enum: ['before', 'after']
3991        this.sibling    = null;
3992
3993        this.callActions('afterConstruct');
3994
3995        h.seal(this);
3996    };
3997
3998    mixitup.BaseStatic.call(mixitup.CommandInsert);
3999
4000    mixitup.CommandInsert.prototype = Object.create(mixitup.Base.prototype);
4001
4002    mixitup.CommandInsert.prototype.constructor = mixitup.CommandInsert;
4003
4004    /**
4005     * An object into which all arbitrary arguments sent to '.remove()' are mapped.
4006     *
4007     * @constructor
4008     * @memberof    mixitup
4009     * @private
4010     * @since       3.0.0
4011     */
4012
4013    mixitup.CommandRemove = function() {
4014        mixitup.Base.call(this);
4015
4016        this.callActions('beforeConstruct');
4017
4018        this.targets    = [];
4019        this.collection = [];
4020
4021        this.callActions('afterConstruct');
4022
4023        h.seal(this);
4024    };
4025
4026    mixitup.BaseStatic.call(mixitup.CommandRemove);
4027
4028    mixitup.CommandRemove.prototype = Object.create(mixitup.Base.prototype);
4029
4030    mixitup.CommandRemove.prototype.constructor = mixitup.CommandRemove;
4031
4032    /**
4033     * An object into which all arbitrary arguments sent to '.changeLayout()' are mapped.
4034     *
4035     * @constructor
4036     * @memberof    mixitup
4037     * @private
4038     * @since       3.0.0
4039     */
4040
4041    mixitup.CommandChangeLayout = function() {
4042        mixitup.Base.call(this);
4043
4044        this.callActions('beforeConstruct');
4045
4046        this.containerClassName = '';
4047
4048        this.callActions('afterConstruct');
4049
4050        h.seal(this);
4051    };
4052
4053    mixitup.BaseStatic.call(mixitup.CommandChangeLayout);
4054
4055    mixitup.CommandChangeLayout.prototype = Object.create(mixitup.Base.prototype);
4056
4057    mixitup.CommandChangeLayout.prototype.constructor = mixitup.CommandChangeLayout;
4058
4059    /**
4060     * @constructor
4061     * @memberof    mixitup
4062     * @private
4063     * @since       3.0.0
4064     * @param       {string}        type
4065     * @param       {string}        selector
4066     * @param       {boolean}       [live]
4067     * @param       {string}        [parent]
4068     *     An optional string representing the name of the mixer.dom property containing a reference to a parent element.
4069     */
4070
4071    mixitup.ControlDefinition = function(type, selector, live, parent) {
4072        mixitup.Base.call(this);
4073
4074        this.callActions('beforeConstruct');
4075
4076        this.type    = type;
4077        this.selector  = selector;
4078        this.live      = live || false;
4079        this.parent    = parent || '';
4080
4081        this.callActions('afterConstruct');
4082
4083        h.freeze(this);
4084        h.seal(this);
4085    };
4086
4087    mixitup.BaseStatic.call(mixitup.ControlDefinition);
4088
4089    mixitup.ControlDefinition.prototype = Object.create(mixitup.Base.prototype);
4090
4091    mixitup.ControlDefinition.prototype.constructor = mixitup.ControlDefinition;
4092
4093    mixitup.controlDefinitions = [];
4094
4095    mixitup.controlDefinitions.push(new mixitup.ControlDefinition('multimix', '[data-filter][data-sort]'));
4096    mixitup.controlDefinitions.push(new mixitup.ControlDefinition('filter', '[data-filter]'));
4097    mixitup.controlDefinitions.push(new mixitup.ControlDefinition('sort', '[data-sort]'));
4098    mixitup.controlDefinitions.push(new mixitup.ControlDefinition('toggle', '[data-toggle]'));
4099
4100    /**
4101     * @constructor
4102     * @memberof    mixitup
4103     * @private
4104     * @since       3.0.0
4105     */
4106
4107    mixitup.Control = function() {
4108        mixitup.Base.call(this);
4109
4110        this.callActions('beforeConstruct');
4111
4112        this.el         = null;
4113        this.selector   = '';
4114        this.bound      = [];
4115        this.pending    = -1;
4116        this.type       = '';
4117        this.status     = 'inactive'; // enum: ['inactive', 'active', 'disabled', 'live']
4118        this.filter     = '';
4119        this.sort       = '';
4120        this.canDisable = false;
4121        this.handler    = null;
4122        this.classNames = new mixitup.UiClassNames();
4123
4124        this.callActions('afterConstruct');
4125
4126        h.seal(this);
4127    };
4128
4129    mixitup.BaseStatic.call(mixitup.Control);
4130
4131    mixitup.Control.prototype = Object.create(mixitup.Base.prototype);
4132
4133    h.extend(mixitup.Control.prototype,
4134    /** @lends mixitup.Control */
4135    {
4136        constructor: mixitup.Control,
4137
4138        /**
4139         * @private
4140         * @param {HTMLElement} el
4141         * @param {string}      type
4142         * @param {string}      selector
4143         */
4144
4145        init: function(el, type, selector) {
4146            var self = this;
4147
4148            this.callActions('beforeInit', arguments);
4149
4150            self.el         = el;
4151            self.type       = type;
4152            self.selector   = selector;
4153
4154            if (self.selector) {
4155                self.status = 'live';
4156            } else {
4157                self.canDisable = typeof self.el.disable === 'boolean';
4158
4159                switch (self.type) {
4160                    case 'filter':
4161                        self.filter = self.el.getAttribute('data-filter');
4162
4163                        break;
4164                    case 'toggle':
4165                        self.filter = self.el.getAttribute('data-toggle');
4166
4167                        break;
4168                    case 'sort':
4169                        self.sort   = self.el.getAttribute('data-sort');
4170
4171                        break;
4172                    case 'multimix':
4173                        self.filter = self.el.getAttribute('data-filter');
4174                        self.sort   = self.el.getAttribute('data-sort');
4175
4176                        break;
4177                }
4178            }
4179
4180            self.bindClick();
4181
4182            mixitup.controls.push(self);
4183
4184            this.callActions('afterInit', arguments);
4185        },
4186
4187        /**
4188         * @private
4189         * @param  {mixitup.Mixer} mixer
4190         * @return {boolean}
4191         */
4192
4193        isBound: function(mixer) {
4194            var self    = this,
4195                isBound = false;
4196
4197            this.callActions('beforeIsBound', arguments);
4198
4199            isBound = self.bound.indexOf(mixer) > -1;
4200
4201            return self.callFilters('afterIsBound', isBound, arguments);
4202        },
4203
4204        /**
4205         * @private
4206         * @param  {mixitup.Mixer} mixer
4207         * @return {void}
4208         */
4209
4210        addBinding: function(mixer) {
4211            var self = this;
4212
4213            this.callActions('beforeAddBinding', arguments);
4214
4215            if (!self.isBound()) {
4216                self.bound.push(mixer);
4217            }
4218
4219            this.callActions('afterAddBinding', arguments);
4220        },
4221
4222        /**
4223         * @private
4224         * @param  {mixitup.Mixer} mixer
4225         * @return {void}
4226         */
4227
4228        removeBinding: function(mixer) {
4229            var self        = this,
4230                removeIndex = -1;
4231
4232            this.callActions('beforeRemoveBinding', arguments);
4233
4234            if ((removeIndex = self.bound.indexOf(mixer)) > -1) {
4235                self.bound.splice(removeIndex, 1);
4236            }
4237
4238            if (self.bound.length < 1) {
4239                // No bindings exist, unbind event click handlers
4240
4241                self.unbindClick();
4242
4243                // Remove from `mixitup.controls` list
4244
4245                removeIndex = mixitup.controls.indexOf(self);
4246
4247                mixitup.controls.splice(removeIndex, 1);
4248
4249                if (self.status === 'active') {
4250                    self.renderStatus(self.el, 'inactive');
4251                }
4252            }
4253
4254            this.callActions('afterRemoveBinding', arguments);
4255        },
4256
4257        /**
4258         * @private
4259         * @return {void}
4260         */
4261
4262        bindClick: function() {
4263            var self = this;
4264
4265            this.callActions('beforeBindClick', arguments);
4266
4267            self.handler = function(e) {
4268                self.handleClick(e);
4269            };
4270
4271            h.on(self.el, 'click', self.handler);
4272
4273            this.callActions('afterBindClick', arguments);
4274        },
4275
4276        /**
4277         * @private
4278         * @return {void}
4279         */
4280
4281        unbindClick: function() {
4282            var self = this;
4283
4284            this.callActions('beforeUnbindClick', arguments);
4285
4286            h.off(self.el, 'click', self.handler);
4287
4288            self.handler = null;
4289
4290            this.callActions('afterUnbindClick', arguments);
4291        },
4292
4293        /**
4294         * @private
4295         * @param   {MouseEvent} e
4296         * @return  {void}
4297         */
4298
4299        handleClick: function(e) {
4300            var self        = this,
4301                button      = null,
4302                mixer       = null,
4303                isActive    = false,
4304                returnValue = void(0),
4305                command     = {},
4306                clone       = null,
4307                commands    = [],
4308                i           = -1;
4309
4310            this.callActions('beforeHandleClick', arguments);
4311
4312            this.pending = 0;
4313
4314            mixer = self.bound[0];
4315
4316            if (!self.selector) {
4317                button = self.el;
4318            } else {
4319                button = h.closestParent(e.target, mixer.config.selectors.control + self.selector, true, mixer.dom.document);
4320            }
4321
4322            if (!button) {
4323                self.callActions('afterHandleClick', arguments);
4324
4325                return;
4326            }
4327
4328            switch (self.type) {
4329                case 'filter':
4330                    command.filter = self.filter || button.getAttribute('data-filter');
4331
4332                    break;
4333                case 'sort':
4334                    command.sort = self.sort || button.getAttribute('data-sort');
4335
4336                    break;
4337                case 'multimix':
4338                    command.filter  = self.filter || button.getAttribute('data-filter');
4339                    command.sort    = self.sort || button.getAttribute('data-sort');
4340
4341                    break;
4342                case 'toggle':
4343                    command.filter  = self.filter || button.getAttribute('data-toggle');
4344
4345                    if (self.status === 'live') {
4346                        isActive = h.hasClass(button, self.classNames.active);
4347                    } else {
4348                        isActive = self.status === 'active';
4349                    }
4350
4351                    break;
4352            }
4353
4354            for (i = 0; i < self.bound.length; i++) {
4355                // Create a clone of the command for each bound mixer instance
4356
4357                clone = new mixitup.CommandMultimix();
4358
4359                h.extend(clone, command);
4360
4361                commands.push(clone);
4362            }
4363
4364            commands = self.callFilters('commandsHandleClick', commands, arguments);
4365
4366            self.pending = self.bound.length;
4367
4368            for (i = 0; mixer = self.bound[i]; i++) {
4369                command = commands[i];
4370
4371                if (!command) {
4372                    // An extension may set a command null to indicate that the click should not be handled
4373
4374                    continue;
4375                }
4376
4377                if (!mixer.lastClicked) {
4378                    mixer.lastClicked = button;
4379                }
4380
4381                mixitup.events.fire('mixClick', mixer.dom.container, {
4382                    state: mixer.state,
4383                    instance: mixer,
4384                    originalEvent: e,
4385                    control: mixer.lastClicked
4386                }, mixer.dom.document);
4387
4388                if (typeof mixer.config.callbacks.onMixClick === 'function') {
4389                    returnValue = mixer.config.callbacks.onMixClick.call(mixer.lastClicked, mixer.state, e, mixer);
4390
4391                    if (returnValue === false) {
4392                        // User has returned `false` from the callback, so do not handle click
4393
4394                        continue;
4395                    }
4396                }
4397
4398                if (self.type === 'toggle') {
4399                    isActive ? mixer.toggleOff(command.filter) : mixer.toggleOn(command.filter);
4400                } else {
4401                    mixer.multimix(command);
4402                }
4403            }
4404
4405            this.callActions('afterHandleClick', arguments);
4406        },
4407
4408        /**
4409         * @param   {object}          command
4410         * @param   {Array<string>}   toggleArray
4411         * @return  {void}
4412         */
4413
4414        update: function(command, toggleArray) {
4415            var self    = this,
4416                actions = new mixitup.CommandMultimix();
4417
4418            self.callActions('beforeUpdate', arguments);
4419
4420            self.pending--;
4421
4422            self.pending = Math.max(0, self.pending);
4423
4424            if (self.pending > 0) return;
4425
4426            if (self.status === 'live') {
4427                // Live control (status unknown)
4428
4429                self.updateLive(command, toggleArray);
4430            } else {
4431                // Static control
4432
4433                actions.sort    = self.sort;
4434                actions.filter  = self.filter;
4435
4436                self.callFilters('actionsUpdate', actions, arguments);
4437
4438                self.parseStatusChange(self.el, command, actions, toggleArray);
4439            }
4440
4441            self.callActions('afterUpdate', arguments);
4442        },
4443
4444        /**
4445         * @param   {mixitup.CommandMultimix} command
4446         * @param   {Array<string>}           toggleArray
4447         * @return  {void}
4448         */
4449
4450        updateLive: function(command, toggleArray) {
4451            var self            = this,
4452                controlButtons  = null,
4453                actions         = null,
4454                button          = null,
4455                i               = -1;
4456
4457            self.callActions('beforeUpdateLive', arguments);
4458
4459            if (!self.el) return;
4460
4461            controlButtons = self.el.querySelectorAll(self.selector);
4462
4463            for (i = 0; button = controlButtons[i]; i++) {
4464                actions = new mixitup.CommandMultimix();
4465
4466                switch (self.type) {
4467                    case 'filter':
4468                        actions.filter = button.getAttribute('data-filter');
4469
4470                        break;
4471                    case 'sort':
4472                        actions.sort = button.getAttribute('data-sort');
4473
4474                        break;
4475                    case 'multimix':
4476                        actions.filter  = button.getAttribute('data-filter');
4477                        actions.sort    = button.getAttribute('data-sort');
4478
4479                        break;
4480                    case 'toggle':
4481                        actions.filter  = button.getAttribute('data-toggle');
4482
4483                        break;
4484                }
4485
4486                actions = self.callFilters('actionsUpdateLive', actions, arguments);
4487
4488                self.parseStatusChange(button, command, actions, toggleArray);
4489            }
4490
4491            self.callActions('afterUpdateLive', arguments);
4492        },
4493
4494        /**
4495         * @param   {HTMLElement}             button
4496         * @param   {mixitup.CommandMultimix} command
4497         * @param   {mixitup.CommandMultimix} actions
4498         * @param   {Array<string>}           toggleArray
4499         * @return  {void}
4500         */
4501
4502        parseStatusChange: function(button, command, actions, toggleArray) {
4503            var self    = this,
4504                alias   = '',
4505                toggle  = '',
4506                i       = -1;
4507
4508            self.callActions('beforeParseStatusChange', arguments);
4509
4510            switch (self.type) {
4511                case 'filter':
4512                    if (command.filter === actions.filter) {
4513                        self.renderStatus(button, 'active');
4514                    } else {
4515                        self.renderStatus(button, 'inactive');
4516                    }
4517
4518                    break;
4519                case 'multimix':
4520                    if (command.sort === actions.sort && command.filter === actions.filter) {
4521                        self.renderStatus(button, 'active');
4522                    } else {
4523                        self.renderStatus(button, 'inactive');
4524                    }
4525
4526                    break;
4527                case 'sort':
4528                    if (command.sort.match(/:asc/g)) {
4529                        alias = command.sort.replace(/:asc/g, '');
4530                    }
4531
4532                    if (command.sort === actions.sort || alias === actions.sort) {
4533                        self.renderStatus(button, 'active');
4534                    } else {
4535                        self.renderStatus(button, 'inactive');
4536                    }
4537
4538                    break;
4539                case 'toggle':
4540                    if (toggleArray.length < 1) self.renderStatus(button, 'inactive');
4541
4542                    if (command.filter === actions.filter) {
4543                        self.renderStatus(button, 'active');
4544                    }
4545
4546                    for (i = 0; i < toggleArray.length; i++) {
4547                        toggle = toggleArray[i];
4548
4549                        if (toggle === actions.filter) {
4550                            // Button matches one active toggle
4551
4552                            self.renderStatus(button, 'active');
4553
4554                            break;
4555                        }
4556
4557                        self.renderStatus(button, 'inactive');
4558                    }
4559
4560                    break;
4561            }
4562
4563            self.callActions('afterParseStatusChange', arguments);
4564        },
4565
4566        /**
4567         * @param   {HTMLElement}   button
4568         * @param   {string}        status
4569         * @return  {void}
4570         */
4571
4572        renderStatus: function(button, status) {
4573            var self = this;
4574
4575            self.callActions('beforeRenderStatus', arguments);
4576
4577            switch (status) {
4578                case 'active':
4579                    h.addClass(button, self.classNames.active);
4580                    h.removeClass(button, self.classNames.disabled);
4581
4582                    if (self.canDisable) self.el.disabled = false;
4583
4584                    break;
4585                case 'inactive':
4586                    h.removeClass(button, self.classNames.active);
4587                    h.removeClass(button, self.classNames.disabled);
4588
4589                    if (self.canDisable) self.el.disabled = false;
4590
4591                    break;
4592                case 'disabled':
4593                    if (self.canDisable) self.el.disabled = true;
4594
4595                    h.addClass(button, self.classNames.disabled);
4596                    h.removeClass(button, self.classNames.active);
4597
4598                    break;
4599            }
4600
4601            if (self.status !== 'live') {
4602                // Update the control's status propery if not live
4603
4604                self.status = status;
4605            }
4606
4607            self.callActions('afterRenderStatus', arguments);
4608        }
4609    });
4610
4611    mixitup.controls = [];
4612
4613    /**
4614     * @constructor
4615     * @memberof    mixitup
4616     * @private
4617     * @since       3.0.0
4618     */
4619
4620    mixitup.StyleData = function() {
4621        mixitup.Base.call(this);
4622
4623        this.callActions('beforeConstruct');
4624
4625        this.x              = 0;
4626        this.y              = 0;
4627        this.top            = 0;
4628        this.right          = 0;
4629        this.bottom         = 0;
4630        this.left           = 0;
4631        this.width          = 0;
4632        this.height         = 0;
4633        this.marginRight    = 0;
4634        this.marginBottom   = 0;
4635        this.opacity        = 0;
4636        this.scale          = new mixitup.TransformData();
4637        this.translateX     = new mixitup.TransformData();
4638        this.translateY     = new mixitup.TransformData();
4639        this.translateZ     = new mixitup.TransformData();
4640        this.rotateX        = new mixitup.TransformData();
4641        this.rotateY        = new mixitup.TransformData();
4642        this.rotateZ        = new mixitup.TransformData();
4643
4644        this.callActions('afterConstruct');
4645
4646        h.seal(this);
4647    };
4648
4649    mixitup.BaseStatic.call(mixitup.StyleData);
4650
4651    mixitup.StyleData.prototype = Object.create(mixitup.Base.prototype);
4652
4653    mixitup.StyleData.prototype.constructor = mixitup.StyleData;
4654
4655    /**
4656     * @constructor
4657     * @memberof    mixitup
4658     * @private
4659     * @since       3.0.0
4660     */
4661
4662    mixitup.TransformData = function() {
4663        mixitup.Base.call(this);
4664
4665        this.callActions('beforeConstruct');
4666
4667        this.value  = 0;
4668        this.unit   = '';
4669
4670        this.callActions('afterConstruct');
4671
4672        h.seal(this);
4673    };
4674
4675    mixitup.BaseStatic.call(mixitup.TransformData);
4676
4677    mixitup.TransformData.prototype = Object.create(mixitup.Base.prototype);
4678
4679    mixitup.TransformData.prototype.constructor = mixitup.TransformData;
4680
4681    /**
4682     * @constructor
4683     * @memberof    mixitup
4684     * @private
4685     * @since       3.0.0
4686     */
4687
4688    mixitup.TransformDefaults = function() {
4689        mixitup.StyleData.apply(this);
4690
4691        this.callActions('beforeConstruct');
4692
4693        this.scale.value        = 0.01;
4694        this.scale.unit         = '';
4695
4696        this.translateX.value   = 20;
4697        this.translateX.unit    = 'px';
4698
4699        this.translateY.value   = 20;
4700        this.translateY.unit    = 'px';
4701
4702        this.translateZ.value   = 20;
4703        this.translateZ.unit    = 'px';
4704
4705        this.rotateX.value      = 90;
4706        this.rotateX.unit       = 'deg';
4707
4708        this.rotateY.value      = 90;
4709        this.rotateY.unit       = 'deg';
4710
4711        this.rotateX.value      = 90;
4712        this.rotateX.unit       = 'deg';
4713
4714        this.rotateZ.value      = 180;
4715        this.rotateZ.unit       = 'deg';
4716
4717        this.callActions('afterConstruct');
4718
4719        h.seal(this);
4720    };
4721
4722    mixitup.BaseStatic.call(mixitup.TransformDefaults);
4723
4724    mixitup.TransformDefaults.prototype = Object.create(mixitup.StyleData.prototype);
4725
4726    mixitup.TransformDefaults.prototype.constructor = mixitup.TransformDefaults;
4727
4728    /**
4729     * @private
4730     * @static
4731     * @since   3.0.0
4732     * @type    {mixitup.TransformDefaults}
4733     */
4734
4735    mixitup.transformDefaults = new mixitup.TransformDefaults();
4736
4737    /**
4738     * @constructor
4739     * @memberof    mixitup
4740     * @private
4741     * @since       3.0.0
4742     */
4743
4744    mixitup.EventDetail = function() {
4745        this.state          = null;
4746        this.futureState    = null;
4747        this.instance       = null;
4748        this.originalEvent  = null;
4749    };
4750
4751    /**
4752     * The `mixitup.Events` class contains all custom events dispatched by MixItUp at various
4753     * points within the lifecycle of a mixer operation.
4754     *
4755     * Each event is analogous to the callback function of the same name defined in
4756     * the `callbacks` configuration object, and is triggered immediately before it.
4757     *
4758     * Events are always triggered from the container element on which MixItUp is instantiated
4759     * upon.
4760     *
4761     * As with any event, registered event handlers receive the event object as a parameter
4762     * which includes a `detail` property containting references to the current `state`,
4763     * the `mixer` instance, and other event-specific properties described below.
4764     *
4765     * @constructor
4766     * @namespace
4767     * @memberof    mixitup
4768     * @public
4769     * @since       3.0.0
4770     */
4771
4772    mixitup.Events = function() {
4773        mixitup.Base.call(this);
4774
4775        this.callActions('beforeConstruct');
4776
4777        /**
4778         * A custom event triggered immediately after any MixItUp operation is requested
4779         * and before animations have begun.
4780         *
4781         * The `mixStart` event also exposes a `futureState` property via the
4782         * `event.detail` object, which represents the final state of the mixer once
4783         * the requested operation has completed.
4784         *
4785         * @name        mixStart
4786         * @memberof    mixitup.Events
4787         * @static
4788         * @type        {CustomEvent}
4789         */
4790
4791        this.mixStart = null;
4792
4793        /**
4794         * A custom event triggered when a MixItUp operation is requested while another
4795         * operation is in progress, and the animation queue is full, or queueing
4796         * is disabled.
4797         *
4798         * @name        mixBusy
4799         * @memberof    mixitup.Events
4800         * @static
4801         * @type        {CustomEvent}
4802         */
4803
4804        this.mixBusy = null;
4805
4806        /**
4807         * A custom event triggered after any MixItUp operation has completed, and the
4808         * state has been updated.
4809         *
4810         * @name        mixEnd
4811         * @memberof    mixitup.Events
4812         * @static
4813         * @type        {CustomEvent}
4814         */
4815
4816        this.mixEnd = null;
4817
4818        /**
4819         * A custom event triggered whenever a filter operation "fails", i.e. no targets
4820         * could be found matching the requested filter.
4821         *
4822         * @name        mixFail
4823         * @memberof    mixitup.Events
4824         * @static
4825         * @type        {CustomEvent}
4826         */
4827
4828        this.mixFail = null;
4829
4830        /**
4831         * A custom event triggered whenever a MixItUp control is clicked, and before its
4832         * respective operation is requested.
4833         *
4834         * This event also exposes an `originalEvent` property via the `event.detail`
4835         * object, which holds a reference to the original click event.
4836         *
4837         * @name        mixClick
4838         * @memberof    mixitup.Events
4839         * @static
4840         * @type        {CustomEvent}
4841         */
4842
4843        this.mixClick = null;
4844
4845        this.callActions('afterConstruct');
4846
4847        h.seal(this);
4848    };
4849
4850    mixitup.BaseStatic.call(mixitup.Events);
4851
4852    mixitup.Events.prototype = Object.create(mixitup.Base.prototype);
4853
4854    mixitup.Events.prototype.constructor = mixitup.Events;
4855
4856    /**
4857     * @private
4858     * @param   {string}      eventType
4859     * @param   {Element}     el
4860     * @param   {object}      detail
4861     * @param   {Document}    [doc]
4862     */
4863
4864    mixitup.Events.prototype.fire = function(eventType, el, detail, doc) {
4865        var self        = this,
4866            event       = null,
4867            eventDetail = new mixitup.EventDetail();
4868
4869        self.callActions('beforeFire', arguments);
4870
4871        if (typeof self[eventType] === 'undefined') {
4872            throw new Error('Event type "' + eventType + '" not found.');
4873        }
4874
4875        eventDetail.state = new mixitup.State();
4876
4877        h.extend(eventDetail.state, detail.state);
4878
4879        if (detail.futureState) {
4880            eventDetail.futureState = new mixitup.State();
4881
4882            h.extend(eventDetail.futureState, detail.futureState);
4883        }
4884
4885        eventDetail.instance = detail.instance;
4886
4887        if (detail.originalEvent) {
4888            eventDetail.originalEvent = detail.originalEvent;
4889        }
4890
4891        event = h.getCustomEvent(eventType, eventDetail, doc);
4892
4893        self.callFilters('eventFire', event, arguments);
4894
4895        el.dispatchEvent(event);
4896    };
4897
4898    // Asign a singleton instance to `mixitup.events`:
4899
4900    mixitup.events = new mixitup.Events();
4901
4902    /**
4903     * @constructor
4904     * @memberof    mixitup
4905     * @private
4906     * @since       3.0.0
4907     */
4908
4909    mixitup.QueueItem = function() {
4910        mixitup.Base.call(this);
4911
4912        this.callActions('beforeConstruct');
4913
4914        this.args           = [];
4915        this.instruction    = null;
4916        this.triggerElement = null;
4917        this.deferred       = null;
4918        this.isToggling     = false;
4919
4920        this.callActions('afterConstruct');
4921
4922        h.seal(this);
4923    };
4924
4925    mixitup.BaseStatic.call(mixitup.QueueItem);
4926
4927    mixitup.QueueItem.prototype = Object.create(mixitup.Base.prototype);
4928
4929    mixitup.QueueItem.prototype.constructor = mixitup.QueueItem;
4930
4931    /**
4932     * The `mixitup.Mixer` class is used to hold discreet, user-configured
4933     * instances of MixItUp on a provided container element.
4934     *
4935     * Mixer instances are returned whenever the `mixitup()` factory function is called,
4936     * which expose a range of methods enabling API-based filtering, sorting,
4937     * insertion, removal and more.
4938     *
4939     * @constructor
4940     * @namespace
4941     * @memberof    mixitup
4942     * @public
4943     * @since       3.0.0
4944     */
4945
4946    mixitup.Mixer = function() {
4947        mixitup.Base.call(this);
4948
4949        this.callActions('beforeConstruct');
4950
4951        this.config            = new mixitup.Config();
4952
4953        this.id                = '';
4954
4955        this.isBusy            = false;
4956        this.isToggling        = false;
4957        this.incPadding        = true;
4958
4959        this.controls          = [];
4960        this.targets           = [];
4961        this.origOrder         = [];
4962        this.cache             = {};
4963
4964        this.toggleArray       = [];
4965
4966        this.targetsMoved      = 0;
4967        this.targetsImmovable  = 0;
4968        this.targetsBound      = 0;
4969        this.targetsDone       = 0;
4970
4971        this.staggerDuration   = 0;
4972        this.effectsIn         = null;
4973        this.effectsOut        = null;
4974        this.transformIn       = [];
4975        this.transformOut      = [];
4976        this.queue             = [];
4977
4978        this.state             = null;
4979        this.lastOperation     = null;
4980        this.lastClicked       = null;
4981        this.userCallback      = null;
4982        this.userDeferred      = null;
4983
4984        this.dom               = new mixitup.MixerDom();
4985
4986        this.callActions('afterConstruct');
4987
4988        h.seal(this);
4989    };
4990
4991    mixitup.BaseStatic.call(mixitup.Mixer);
4992
4993    mixitup.Mixer.prototype = Object.create(mixitup.Base.prototype);
4994
4995    h.extend(mixitup.Mixer.prototype,
4996    /** @lends mixitup.Mixer */
4997    {
4998        constructor: mixitup.Mixer,
4999
5000        /**
5001         * @private
5002         * @instance
5003         * @since 3.0.0
5004         * @param {HTMLElement} container
5005         * @param {HTMLElement} document
5006         * @param {string}      id
5007         * @param {object}      [config]
5008         */
5009
5010        attach: function(container, document, id, config) {
5011            var self    = this,
5012                target  = null,
5013                i       = -1;
5014
5015            self.callActions('beforeAttach', arguments);
5016
5017            self.id = id;
5018
5019            if (config) {
5020                h.extend(self.config, config, true, true);
5021            }
5022
5023            self.sanitizeConfig();
5024
5025            self.cacheDom(container, document);
5026
5027            if (self.config.layout.containerClassName) {
5028                h.addClass(self.dom.container, self.config.layout.containerClassName);
5029            }
5030
5031            if (!mixitup.features.has.transitions) {
5032                self.config.animation.enable = false;
5033            }
5034
5035            if (typeof window.console === 'undefined') {
5036                self.config.debug.showWarnings = false;
5037            }
5038
5039            if (self.config.data.uidKey) {
5040                // If the dataset API is in use, force disable controls
5041
5042                self.config.controls.enable = false;
5043            }
5044
5045            self.indexTargets();
5046
5047            self.state = self.getInitialState();
5048
5049            for (i = 0; target = self.lastOperation.toHide[i]; i++) {
5050                target.hide();
5051            }
5052
5053            if (self.config.controls.enable) {
5054                self.initControls();
5055
5056                self.buildToggleArray(null, self.state);
5057
5058                self.updateControls({
5059                    filter: self.state.activeFilter,
5060                    sort: self.state.activeSort
5061                });
5062            }
5063
5064            self.parseEffects();
5065
5066            self.callActions('afterAttach', arguments);
5067        },
5068
5069        /**
5070         * @private
5071         * @instance
5072         * @since 3.0.0
5073         * @return {void}
5074         */
5075
5076        sanitizeConfig: function() {
5077            var self = this;
5078
5079            self.callActions('beforeSanitizeConfig', arguments);
5080
5081            // Sanitize enum/string config options
5082
5083            self.config.controls.scope          = self.config.controls.scope.toLowerCase().trim();
5084            self.config.controls.toggleLogic    = self.config.controls.toggleLogic.toLowerCase().trim();
5085            self.config.controls.toggleDefault  = self.config.controls.toggleDefault.toLowerCase().trim();
5086
5087            self.config.animation.effects       = self.config.animation.effects.trim();
5088
5089            self.callActions('afterSanitizeConfig', arguments);
5090        },
5091
5092        /**
5093         * @private
5094         * @instance
5095         * @since   3.0.0
5096         * @return  {mixitup.State}
5097         */
5098
5099        getInitialState: function() {
5100            var self        = this,
5101                state       = new mixitup.State(),
5102                operation   = new mixitup.Operation();
5103
5104            self.callActions('beforeGetInitialState', arguments);
5105
5106            // Map initial values into a mock state object in order to construct an operation
5107
5108            state.activeContainerClassName = self.config.layout.containerClassName;
5109
5110            if (self.config.load.dataset) {
5111                // Dataset API
5112
5113                if (!self.config.data.uidKey || typeof self.config.data.uidKey !== 'string') {
5114                    throw new TypeError(mixitup.messages.errorConfigDataUidKeyNotSet());
5115                }
5116
5117                operation.startDataset = operation.newDataset = state.activeDataset = self.config.load.dataset.slice();
5118                operation.startContainerClassName = operation.newContainerClassName = state.activeContainerClassName;
5119                operation.show = self.targets.slice();
5120
5121                state = self.callFilters('stateGetInitialState', state, arguments);
5122            } else {
5123                // DOM API
5124
5125                state.activeFilter              = self.parseFilterArgs([self.config.load.filter]).command;
5126                state.activeSort                = self.parseSortArgs([self.config.load.sort]).command;
5127                state.totalTargets              = self.targets.length;
5128
5129                state = self.callFilters('stateGetInitialState', state, arguments);
5130
5131                if (
5132                    state.activeSort.collection || state.activeSort.attribute ||
5133                    state.activeSort.order === 'random' || state.activeSort.order === 'desc'
5134                ) {
5135                    // Sorting on load
5136
5137                    operation.newSort = state.activeSort;
5138
5139                    self.sortOperation(operation);
5140
5141                    self.printSort(false, operation);
5142
5143                    self.targets = operation.newOrder;
5144                } else {
5145                    operation.startOrder = operation.newOrder = self.targets;
5146                }
5147
5148                operation.startFilter               = operation.newFilter               = state.activeFilter;
5149                operation.startSort                 = operation.newSort                 = state.activeSort;
5150                operation.startContainerClassName   = operation.newContainerClassName   = state.activeContainerClassName;
5151
5152                if (operation.newFilter.selector === 'all') {
5153                    operation.newFilter.selector = self.config.selectors.target;
5154                } else if (operation.newFilter.selector === 'none') {
5155                    operation.newFilter.selector = '';
5156                }
5157            }
5158
5159            operation = self.callFilters('operationGetInitialState', operation, [state]);
5160
5161            self.lastOperation = operation;
5162
5163            if (operation.newFilter) {
5164                self.filterOperation(operation);
5165            }
5166
5167            state = self.buildState(operation);
5168
5169            return state;
5170        },
5171
5172        /**
5173         * Caches references of DOM elements neccessary for the mixer's functionality.
5174         *
5175         * @private
5176         * @instance
5177         * @since   3.0.0
5178         * @param   {HTMLElement}       el
5179         * @param   {HTMLHtmlElement}   document
5180         * @return  {void}
5181         */
5182
5183        cacheDom: function(el, document) {
5184            var self    = this;
5185
5186            self.callActions('beforeCacheDom', arguments);
5187
5188            self.dom.document  = document;
5189            self.dom.body      = self.dom.document.querySelector('body');
5190            self.dom.container = el;
5191            self.dom.parent    = el;
5192
5193            self.callActions('afterCacheDom', arguments);
5194        },
5195
5196        /**
5197         * Indexes all child elements of the mixer matching the `selectors.target`
5198         * selector, instantiating a mixitup.Target for each one.
5199         *
5200         * @private
5201         * @instance
5202         * @since   3.0.0
5203         * @return  {void}
5204         */
5205
5206        indexTargets: function() {
5207            var self            = this,
5208                target          = null,
5209                el              = null,
5210                dataset         = null,
5211                i               = -1;
5212
5213            self.callActions('beforeIndexTargets', arguments);
5214
5215            self.dom.targets = self.config.layout.allowNestedTargets ?
5216                self.dom.container.querySelectorAll(self.config.selectors.target) :
5217                h.children(self.dom.container, self.config.selectors.target, self.dom.document);
5218
5219            self.dom.targets = h.arrayFromList(self.dom.targets);
5220
5221            self.targets = [];
5222
5223            if ((dataset = self.config.load.dataset) && dataset.length !== self.dom.targets.length) {
5224                throw new Error(mixitup.messages.errorDatasetPrerenderedMismatch());
5225            }
5226
5227            if (self.dom.targets.length) {
5228                for (i = 0; el = self.dom.targets[i]; i++) {
5229                    target = new mixitup.Target();
5230
5231                    target.init(el, self, dataset ? dataset[i] : void(0));
5232
5233                    target.isInDom = true;
5234
5235                    self.targets.push(target);
5236                }
5237
5238                self.dom.parent = self.dom.targets[0].parentElement === self.dom.container ?
5239                    self.dom.container :
5240                    self.dom.targets[0].parentElement;
5241            }
5242
5243            self.origOrder = self.targets;
5244
5245            self.callActions('afterIndexTargets', arguments);
5246        },
5247
5248        initControls: function() {
5249            var self                = this,
5250                definition          = '',
5251                controlElements     = null,
5252                el                  = null,
5253                parent              = null,
5254                delagators          = null,
5255                control             = null,
5256                i                   = -1,
5257                j                   = -1;
5258
5259            self.callActions('beforeInitControls', arguments);
5260
5261            switch (self.config.controls.scope) {
5262                case 'local':
5263                    parent = self.dom.container;
5264
5265                    break;
5266                case 'global':
5267                    parent = self.dom.document;
5268
5269                    break;
5270                default:
5271                    throw new Error(mixitup.messages.errorConfigInvalidControlsScope());
5272            }
5273
5274            for (i = 0; definition = mixitup.controlDefinitions[i]; i++) {
5275                if (self.config.controls.live || definition.live) {
5276                    if (definition.parent) {
5277                        delagators = self.dom[definition.parent];
5278
5279                        if (!delagators || delagators.length < 0) continue;
5280
5281                        if (typeof delagators.length !== 'number') {
5282                            delagators = [delagators];
5283                        }
5284                    } else {
5285                        delagators = [parent];
5286                    }
5287
5288                    for (j = 0; (el = delagators[j]); j++) {
5289                        control = self.getControl(el,  definition.type, definition.selector);
5290
5291                        self.controls.push(control);
5292                    }
5293                } else {
5294                    controlElements = parent.querySelectorAll(self.config.selectors.control + definition.selector);
5295
5296                    for (j = 0; (el = controlElements[j]); j++) {
5297                        control = self.getControl(el, definition.type, '');
5298
5299                        if (!control) continue;
5300
5301                        self.controls.push(control);
5302                    }
5303                }
5304            }
5305
5306            self.callActions('afterInitControls', arguments);
5307        },
5308
5309        /**
5310         * @private
5311         * @instance
5312         * @since   3.0.0
5313         * @param   {HTMLElement} el
5314         * @param   {string}      type
5315         * @param   {string}      selector
5316         * @return  {mixitup.Control|null}
5317         */
5318
5319        getControl: function(el, type, selector) {
5320            var self    = this,
5321                control = null,
5322                i       = -1;
5323
5324            self.callActions('beforeGetControl', arguments);
5325
5326            if (!selector) {
5327                // Static controls only
5328
5329                for (i = 0; control = mixitup.controls[i]; i++) {
5330                    if (control.el === el && control.isBound(self)) {
5331                        // Control already bound to this mixer (as another type).
5332
5333                        // NB: This prevents duplicate controls from being registered where a selector
5334                        // might collide, eg: "[data-filter]" and "[data-filter][data-sort]"
5335
5336                        return self.callFilters('controlGetControl', null, arguments);
5337                    } else if (control.el === el && control.type === type && control.selector === selector) {
5338                        // Another mixer is already using this control, add this mixer as a binding
5339
5340                        control.addBinding(self);
5341
5342                        return self.callFilters('controlGetControl', control, arguments);
5343                    }
5344                }
5345            }
5346
5347            // Create new control
5348
5349            control = new mixitup.Control();
5350
5351            control.init(el, type, selector);
5352
5353            control.classNames.base     = h.getClassname(self.config.classNames, type);
5354            control.classNames.active   = h.getClassname(self.config.classNames, type, self.config.classNames.modifierActive);
5355            control.classNames.disabled = h.getClassname(self.config.classNames, type, self.config.classNames.modifierDisabled);
5356
5357            // Add a reference to this mixer as a binding
5358
5359            control.addBinding(self);
5360
5361            return self.callFilters('controlGetControl', control, arguments);
5362        },
5363
5364        /**
5365         * Creates a compound selector by joining the `toggleArray` value as per the
5366         * defined toggle logic.
5367         *
5368         * @private
5369         * @instance
5370         * @since   3.0.0
5371         * @return  {string}
5372         */
5373
5374        getToggleSelector: function() {
5375            var self            = this,
5376                delineator      = self.config.controls.toggleLogic === 'or' ? ', ' : '',
5377                toggleSelector  = '';
5378
5379            self.callActions('beforeGetToggleSelector', arguments);
5380
5381            self.toggleArray = h.clean(self.toggleArray);
5382
5383            toggleSelector = self.toggleArray.join(delineator);
5384
5385            if (toggleSelector === '') {
5386                toggleSelector = self.config.controls.toggleDefault;
5387            }
5388
5389            return self.callFilters('selectorGetToggleSelector', toggleSelector, arguments);
5390        },
5391
5392        /**
5393         * Breaks compound selector strings in an array of discreet selectors,
5394         * as per the active `controls.toggleLogic` configuration option. Accepts
5395         * either a dynamic command object, or a state object.
5396         *
5397         * @private
5398         * @instance
5399         * @since   2.0.0
5400         * @param   {object}        [command]
5401         * @param   {mixitup.State} [state]
5402         * @return  {void}
5403         */
5404
5405        buildToggleArray: function(command, state) {
5406            var self                    = this,
5407                activeFilterSelector    = '';
5408
5409            self.callActions('beforeBuildToggleArray', arguments);
5410
5411            if (command && command.filter) {
5412                activeFilterSelector = command.filter.selector.replace(/\s/g, '');
5413            } else if (state) {
5414                activeFilterSelector = state.activeFilter.selector.replace(/\s/g, '');
5415            } else {
5416                return;
5417            }
5418
5419            if (activeFilterSelector === self.config.selectors.target || activeFilterSelector === 'all') {
5420                activeFilterSelector = '';
5421            }
5422
5423            if (self.config.controls.toggleLogic === 'or') {
5424                self.toggleArray = activeFilterSelector.split(',');
5425            } else {
5426                self.toggleArray = self.splitCompoundSelector(activeFilterSelector);
5427            }
5428
5429            self.toggleArray = h.clean(self.toggleArray);
5430
5431            self.callActions('afterBuildToggleArray', arguments);
5432        },
5433
5434        /**
5435         * Takes a compound selector (e.g. `.cat-1.cat-2`, `[data-cat="1"][data-cat="2"]`)
5436         * and breaks into its individual selectors.
5437         *
5438         * @private
5439         * @instance
5440         * @since   3.0.0
5441         * @param   {string} compoundSelector
5442         * @return  {string[]}
5443         */
5444
5445        splitCompoundSelector: function(compoundSelector) {
5446            // Break at a `.` or `[`, capturing the delineator
5447
5448            var partials    = compoundSelector.split(/([\.\[])/g),
5449                toggleArray = [],
5450                selector    = '',
5451                i           = -1;
5452
5453            if (partials[0] === '') {
5454                partials.shift();
5455            }
5456
5457            for (i = 0; i < partials.length; i++) {
5458                if (i % 2 === 0) {
5459                    selector = '';
5460                }
5461
5462                selector += partials[i];
5463
5464                if (i % 2 !== 0) {
5465                    toggleArray.push(selector);
5466                }
5467            }
5468
5469            return toggleArray;
5470        },
5471
5472        /**
5473         * Updates controls to their active/inactive state based on the command or
5474         * current state of the mixer.
5475         *
5476         * @private
5477         * @instance
5478         * @since   2.0.0
5479         * @param   {object} command
5480         * @return  {void}
5481         */
5482
5483        updateControls: function(command) {
5484            var self    = this,
5485                control = null,
5486                output  = new mixitup.CommandMultimix(),
5487                i       = -1;
5488
5489            self.callActions('beforeUpdateControls', arguments);
5490
5491            // Sanitise to defaults
5492
5493            if (command.filter) {
5494                output.filter = command.filter.selector;
5495            } else {
5496                output.filter = self.state.activeFilter.selector;
5497            }
5498
5499            if (command.sort) {
5500                output.sort = self.buildSortString(command.sort);
5501            } else {
5502                output.sort = self.buildSortString(self.state.activeSort);
5503            }
5504
5505            if (output.filter === self.config.selectors.target) {
5506                output.filter = 'all';
5507            }
5508
5509            if (output.filter === '') {
5510                output.filter = 'none';
5511            }
5512
5513            h.freeze(output);
5514
5515            for (i = 0; control = self.controls[i]; i++) {
5516                control.update(output, self.toggleArray);
5517            }
5518
5519            self.callActions('afterUpdateControls', arguments);
5520        },
5521
5522        /**
5523         * @private
5524         * @instance
5525         * @since   3.0.0
5526         * @param   {mixitup.CommandSort}   command
5527         * @return  {string}
5528         */
5529
5530        buildSortString: function(command) {
5531            var self    = this;
5532            var output  = '';
5533
5534            output += command.sortString;
5535
5536            if (command.next) {
5537                output += ' ' + self.buildSortString(command.next);
5538            }
5539
5540            return output;
5541        },
5542
5543        /**
5544         * @private
5545         * @instance
5546         * @since   3.0.0
5547         * @param   {object}        command
5548         * @param   {Operation}     operation
5549         * @return  {Promise.<mixitup.State>}
5550         */
5551
5552        insertTargets: function(command, operation) {
5553            var self            = this,
5554                nextSibling     = null,
5555                insertionIndex  = -1,
5556                frag            = null,
5557                target          = null,
5558                el              = null,
5559                i               = -1;
5560
5561            self.callActions('beforeInsertTargets', arguments);
5562
5563            if (typeof command.index === 'undefined') command.index = 0;
5564
5565            nextSibling = self.getNextSibling(command.index, command.sibling, command.position);
5566            frag        = self.dom.document.createDocumentFragment();
5567
5568            if (nextSibling) {
5569                insertionIndex = h.index(nextSibling, self.config.selectors.target);
5570            } else {
5571                insertionIndex = self.targets.length;
5572            }
5573
5574            if (command.collection) {
5575                for (i = 0; el = command.collection[i]; i++) {
5576                    if (self.dom.targets.indexOf(el) > -1) {
5577                        throw new Error(mixitup.messages.errorInsertPreexistingElement());
5578                    }
5579
5580                    // Ensure elements are hidden when they are added to the DOM, so they can
5581                    // be animated in gracefully
5582
5583                    el.style.display = 'none';
5584
5585                    frag.appendChild(el);
5586                    frag.appendChild(self.dom.document.createTextNode(' '));
5587
5588                    if (!h.isElement(el, self.dom.document) || !el.matches(self.config.selectors.target)) continue;
5589
5590                    target = new mixitup.Target();
5591
5592                    target.init(el, self);
5593
5594                    target.isInDom = true;
5595
5596                    self.targets.splice(insertionIndex, 0, target);
5597
5598                    insertionIndex++;
5599                }
5600
5601                self.dom.parent.insertBefore(frag, nextSibling);
5602            }
5603
5604            // Since targets have been added, the original order must be updated
5605
5606            operation.startOrder = self.origOrder = self.targets;
5607
5608            self.callActions('afterInsertTargets', arguments);
5609        },
5610
5611        /**
5612         * @private
5613         * @instance
5614         * @since   3.0.0
5615         * @param   {Number}      [index]
5616         * @param   {Element}     [sibling]
5617         * @param   {string}      [position]
5618         * @return  {Element}
5619         */
5620
5621        getNextSibling: function(index, sibling, position) {
5622            var self    = this,
5623                element = null;
5624
5625            index = Math.max(index, 0);
5626
5627            if (sibling && position === 'before') {
5628                // Explicit sibling
5629
5630                element = sibling;
5631            } else if (sibling && position === 'after') {
5632                // Explicit sibling
5633
5634                element = sibling.nextElementSibling || null;
5635            } else if (self.targets.length > 0 && typeof index !== 'undefined') {
5636                // Index and targets exist
5637
5638                element = (index < self.targets.length || !self.targets.length) ?
5639                    self.targets[index].dom.el :
5640                    self.targets[self.targets.length - 1].dom.el.nextElementSibling;
5641            } else if (self.targets.length === 0 && self.dom.parent.children.length > 0) {
5642                // No targets but other siblings
5643
5644                if (self.config.layout.siblingAfter) {
5645                    element = self.config.layout.siblingAfter;
5646                } else if (self.config.layout.siblingBefore) {
5647                    element = self.config.layout.siblingBefore.nextElementSibling;
5648                } else {
5649                    self.dom.parent.children[0];
5650                }
5651            } else {
5652                element === null;
5653            }
5654
5655            return self.callFilters('elementGetNextSibling', element, arguments);
5656        },
5657
5658        /**
5659         * @private
5660         * @instance
5661         * @since   2.0.0
5662         * @param   {Operation}     operation
5663         * @return  {void}
5664         */
5665
5666        filterOperation: function(operation) {
5667            var self        = this,
5668                testResult  = false,
5669                index       = -1,
5670                action      = '',
5671                target      = null,
5672                i           = -1;
5673
5674            self.callActions('beforeFilterOperation', arguments);
5675
5676            action = operation.newFilter.action;
5677
5678            for (i = 0; target = operation.newOrder[i]; i++) {
5679                if (operation.newFilter.collection) {
5680                    // show via collection
5681
5682                    testResult = operation.newFilter.collection.indexOf(target.dom.el) > -1;
5683                } else {
5684                    // show via selector
5685
5686                    if (operation.newFilter.selector === '') {
5687                        testResult = false;
5688                    } else {
5689                        testResult = target.dom.el.matches(operation.newFilter.selector);
5690                    }
5691                }
5692
5693                self.evaluateHideShow(testResult, target, action, operation);
5694            }
5695
5696            if (operation.toRemove.length) {
5697                for (i = 0; target = operation.show[i]; i++) {
5698                    if (operation.toRemove.indexOf(target) > -1) {
5699                        // If any shown targets should be removed, move them into the toHide array
5700
5701                        operation.show.splice(i, 1);
5702
5703                        if ((index = operation.toShow.indexOf(target)) > -1) {
5704                            operation.toShow.splice(index, 1);
5705                        }
5706
5707                        operation.toHide.push(target);
5708                        operation.hide.push(target);
5709
5710                        i--;
5711                    }
5712                }
5713            }
5714
5715            operation.matching = operation.show.slice();
5716
5717            if (operation.show.length === 0 && operation.newFilter.selector !== '' && self.targets.length !== 0) {
5718                operation.hasFailed = true;
5719            }
5720
5721            self.callActions('afterFilterOperation', arguments);
5722        },
5723
5724        /**
5725         * @private
5726         * @instance
5727         * @since   3.0.0
5728         * @param   {boolean}   testResult
5729         * @param   {Element}   target
5730         * @param   {string}    action
5731         * @param   {Operation} operation
5732         * @return  {void}
5733         */
5734
5735        evaluateHideShow: function(testResult, target, action, operation) {
5736            var self = this,
5737                filteredTestResult = false,
5738                args = Array.prototype.slice.call(arguments, 1);
5739
5740            filteredTestResult = self.callFilters('testResultEvaluateHideShow', testResult, args);
5741
5742            self.callActions('beforeEvaluateHideShow', arguments);
5743
5744            if (
5745                filteredTestResult === true && action === 'show' ||
5746                filteredTestResult === false && action === 'hide'
5747            ) {
5748                operation.show.push(target);
5749
5750                !target.isShown && operation.toShow.push(target);
5751            } else {
5752                operation.hide.push(target);
5753
5754                target.isShown && operation.toHide.push(target);
5755            }
5756
5757            self.callActions('afterEvaluateHideShow', arguments);
5758        },
5759
5760        /**
5761         * @private
5762         * @instance
5763         * @since   2.0.0
5764         * @param   {Operation}     operation
5765         * @return  {void}
5766         */
5767
5768        sortOperation: function(operation) {
5769            var self     = this,
5770                newOrder = [],
5771                target   = null,
5772                el       = null,
5773                i        = -1;
5774
5775            self.callActions('beforeSortOperation', arguments);
5776
5777            operation.startOrder = self.targets;
5778
5779            if (operation.newSort.collection) {
5780                // Sort by collection
5781
5782                newOrder = [];
5783
5784                for (i = 0; (el = operation.newSort.collection[i]); i++) {
5785                    if (self.dom.targets.indexOf(el) < 0) {
5786                        throw new Error(mixitup.messages.errorSortNonExistentElement());
5787                    }
5788
5789                    target = new mixitup.Target();
5790
5791                    target.init(el, self);
5792
5793                    target.isInDom = true;
5794
5795                    newOrder.push(target);
5796                }
5797
5798                operation.newOrder = newOrder;
5799            } else if (operation.newSort.order === 'random') {
5800                // Sort random
5801
5802                operation.newOrder = h.arrayShuffle(operation.startOrder);
5803            } else if (operation.newSort.attribute === '') {
5804                // Sort by default
5805
5806                operation.newOrder = self.origOrder.slice();
5807
5808                if (operation.newSort.order === 'desc') {
5809                    operation.newOrder.reverse();
5810                }
5811            } else {
5812                // Sort by attribute
5813
5814                operation.newOrder = operation.startOrder.slice();
5815
5816                operation.newOrder.sort(function(a, b) {
5817                    return self.compare(a, b, operation.newSort);
5818                });
5819            }
5820
5821            if (h.isEqualArray(operation.newOrder, operation.startOrder)) {
5822                operation.willSort = false;
5823            }
5824
5825            self.callActions('afterSortOperation', arguments);
5826        },
5827
5828        /**
5829         * @private
5830         * @instance
5831         * @since   2.0.0
5832         * @param   {mixitup.Target}        a
5833         * @param   {mixitup.Target}        b
5834         * @param   {mixitup.CommandSort}   command
5835         * @return  {Number}
5836         */
5837
5838        compare: function(a, b, command) {
5839            var self        = this,
5840                order       = command.order,
5841                attrA       = self.getAttributeValue(a, command.attribute),
5842                attrB       = self.getAttributeValue(b, command.attribute);
5843
5844            if (isNaN(attrA * 1) || isNaN(attrB * 1)) {
5845                attrA = attrA.toLowerCase();
5846                attrB = attrB.toLowerCase();
5847            } else {
5848                attrA = attrA * 1;
5849                attrB = attrB * 1;
5850            }
5851
5852            if (attrA < attrB) {
5853                return order === 'asc' ? -1 : 1;
5854            }
5855
5856            if (attrA > attrB) {
5857                return order === 'asc' ? 1 : -1;
5858            }
5859
5860            if (attrA === attrB && command.next) {
5861                return self.compare(a, b, command.next);
5862            }
5863
5864            return 0;
5865        },
5866
5867        /**
5868         * Reads the values of any data attributes present the provided target element
5869         * which match the current sort command.
5870         *
5871         * @private
5872         * @instance
5873         * @since   3.0.0
5874         * @param   {mixitup.Target}    target
5875         * @param   {string}            [attribute]
5876         * @return  {(String|Number)}
5877         */
5878
5879        getAttributeValue: function(target, attribute) {
5880            var self    = this,
5881                value   = '';
5882
5883            value = target.dom.el.getAttribute('data-' + attribute);
5884
5885            if (value === null) {
5886                if (self.config.debug.showWarnings) {
5887                    // Encourage users to assign values to all targets to avoid erroneous sorting
5888                    // when types are mixed
5889
5890                    console.warn(mixitup.messages.warningInconsistentSortingAttributes({
5891                        attribute: 'data-' + attribute
5892                    }));
5893                }
5894            }
5895
5896            // If an attribute is not present, return 0 as a safety value
5897
5898            return self.callFilters('valueGetAttributeValue', value || 0, arguments);
5899        },
5900
5901        /**
5902         * Inserts elements into the DOM in the appropriate
5903         * order using a document fragment for minimal
5904         * DOM thrashing
5905         *
5906         * @private
5907         * @instance
5908         * @since   2.0.0
5909         * @param   {boolean}   isResetting
5910         * @param   {Operation} operation
5911         * @return  {void}
5912         */
5913
5914        printSort: function(isResetting, operation) {
5915            var self        = this,
5916                startOrder  = isResetting ? operation.newOrder : operation.startOrder,
5917                newOrder    = isResetting ? operation.startOrder : operation.newOrder,
5918                nextSibling = startOrder.length ? startOrder[startOrder.length - 1].dom.el.nextElementSibling : null,
5919                frag        = window.document.createDocumentFragment(),
5920                whitespace  = null,
5921                target      = null,
5922                el          = null,
5923                i           = -1;
5924
5925            self.callActions('beforePrintSort', arguments);
5926
5927            // Empty the container
5928
5929            for (i = 0; target = startOrder[i]; i++) {
5930                el = target.dom.el;
5931
5932                if (el.style.position === 'absolute') continue;
5933
5934                h.removeWhitespace(el.previousSibling);
5935
5936                el.parentElement.removeChild(el);
5937            }
5938
5939            whitespace = nextSibling ? nextSibling.previousSibling : self.dom.parent.lastChild;
5940
5941            if (whitespace && whitespace.nodeName === '#text') {
5942                h.removeWhitespace(whitespace);
5943            }
5944
5945            for (i = 0; target = newOrder[i]; i++) {
5946                // Add targets into a document fragment
5947
5948                el = target.dom.el;
5949
5950                if (h.isElement(frag.lastChild)) {
5951                    frag.appendChild(window.document.createTextNode(' '));
5952                }
5953
5954                frag.appendChild(el);
5955            }
5956
5957            // Insert the document fragment into the container
5958            // before any other non-target elements
5959
5960            if (self.dom.parent.firstChild && self.dom.parent.firstChild !== nextSibling) {
5961                frag.insertBefore(window.document.createTextNode(' '), frag.childNodes[0]);
5962            }
5963
5964            if (nextSibling) {
5965                frag.appendChild(window.document.createTextNode(' '));
5966
5967                self.dom.parent.insertBefore(frag, nextSibling);
5968            } else {
5969                self.dom.parent.appendChild(frag);
5970            }
5971
5972            self.callActions('afterPrintSort', arguments);
5973        },
5974
5975        /**
5976         * Parses user-defined sort strings (i.e. `default:asc`) into sort commands objects.
5977         *
5978         * @private
5979         * @instance
5980         * @since   3.0.0
5981         * @param   {string}                sortString
5982         * @param   {mixitup.CommandSort}   command
5983         * @return  {mixitup.CommandSort}
5984         */
5985
5986        parseSortString: function(sortString, command) {
5987            var self        = this,
5988                rules       = sortString.split(' '),
5989                current     = command,
5990                rule        = [],
5991                i           = -1;
5992
5993            // command.sortString = sortString;
5994
5995            for (i = 0; i < rules.length; i++) {
5996                rule = rules[i].split(':');
5997
5998                current.sortString  = rules[i];
5999                current.attribute   = h.dashCase(rule[0]);
6000                current.order       = rule[1] || 'asc';
6001
6002                switch (current.attribute) {
6003                    case 'default':
6004                        // treat "default" as sorting by no attribute
6005
6006                        current.attribute = '';
6007
6008                        break;
6009                    case 'random':
6010                        // treat "random" as an order not an attribute
6011
6012                        current.attribute   = '';
6013                        current.order       = 'random';
6014
6015                        break;
6016                }
6017
6018                if (!current.attribute || current.order === 'random') break;
6019
6020                if (i < rules.length - 1) {
6021                    // Embed reference to the next command
6022
6023                    current.next = new mixitup.CommandSort();
6024
6025                    h.freeze(current);
6026
6027                    current = current.next;
6028                }
6029            }
6030
6031            return self.callFilters('commandsParseSort', command, arguments);
6032        },
6033
6034        /**
6035         * Parses all effects out of the user-defined `animation.effects` string into
6036         * their respective properties and units.
6037         *
6038         * @private
6039         * @instance
6040         * @since   2.0.0
6041         * @return  {void}
6042         */
6043
6044        parseEffects: function() {
6045            var self            = this,
6046                transformName   = '',
6047                effectsIn       = self.config.animation.effectsIn || self.config.animation.effects,
6048                effectsOut      = self.config.animation.effectsOut || self.config.animation.effects;
6049
6050            self.callActions('beforeParseEffects', arguments);
6051
6052            self.effectsIn      = new mixitup.StyleData();
6053            self.effectsOut     = new mixitup.StyleData();
6054            self.transformIn    = [];
6055            self.transformOut   = [];
6056
6057            self.effectsIn.opacity = self.effectsOut.opacity = 1;
6058
6059            self.parseEffect('fade', effectsIn, self.effectsIn, self.transformIn);
6060            self.parseEffect('fade', effectsOut, self.effectsOut, self.transformOut, true);
6061
6062            for (transformName in mixitup.transformDefaults) {
6063                if (!(mixitup.transformDefaults[transformName] instanceof mixitup.TransformData)) {
6064                    continue;
6065                }
6066
6067                self.parseEffect(transformName, effectsIn, self.effectsIn, self.transformIn);
6068                self.parseEffect(transformName, effectsOut, self.effectsOut, self.transformOut, true);
6069            }
6070
6071            self.parseEffect('stagger', effectsIn, self.effectsIn, self.transformIn);
6072            self.parseEffect('stagger', effectsOut, self.effectsOut, self.transformOut, true);
6073
6074            self.callActions('afterParseEffects', arguments);
6075        },
6076
6077        /**
6078         * @private
6079         * @instance
6080         * @since   2.0.0
6081         * @param   {string}    effectName
6082         * @param   {string}    effectString
6083         * @param   {StyleData} effects
6084         * @param   {String[]}  transform
6085         * @param   {boolean}   [isOut]
6086         */
6087
6088        parseEffect: function(effectName, effectString, effects, transform, isOut) {
6089            var self        = this,
6090                re          = /\(([^)]+)\)/,
6091                propIndex   = -1,
6092                str         = '',
6093                match       = [],
6094                val         = '',
6095                units       = ['%', 'px', 'em', 'rem', 'vh', 'vw', 'deg'],
6096                unit        = '',
6097                i           = -1;
6098
6099            self.callActions('beforeParseEffect', arguments);
6100
6101            if (typeof effectString !== 'string') {
6102                throw new TypeError(mixitup.messages.errorConfigInvalidAnimationEffects());
6103            }
6104
6105            if (effectString.indexOf(effectName) < 0) {
6106                // The effect is not present in the effects string
6107
6108                if (effectName === 'stagger') {
6109                    // Reset stagger to 0
6110
6111                    self.staggerDuration = 0;
6112                }
6113
6114                return;
6115            }
6116
6117            // The effect is present
6118
6119            propIndex = effectString.indexOf(effectName + '(');
6120
6121            if (propIndex > -1) {
6122                // The effect has a user defined value in parentheses
6123
6124                // Extract from the first parenthesis to the end of string
6125
6126                str = effectString.substring(propIndex);
6127
6128                // Match any number of characters between "(" and ")"
6129
6130                match = re.exec(str);
6131
6132                val = match[1];
6133            }
6134
6135            switch (effectName) {
6136                case 'fade':
6137                    effects.opacity = val ? parseFloat(val) : 0;
6138
6139                    break;
6140                case 'stagger':
6141                    self.staggerDuration = val ? parseFloat(val) : 100;
6142
6143                    // TODO: Currently stagger must be applied globally, but
6144                    // if seperate values are specified for in/out, this should
6145                    // be respected
6146
6147                    break;
6148                default:
6149                    // All other effects are transforms following the same structure
6150
6151                    if (isOut && self.config.animation.reverseOut && effectName !== 'scale') {
6152                        effects[effectName].value =
6153                            (val ? parseFloat(val) : mixitup.transformDefaults[effectName].value) * -1;
6154                    } else {
6155                        effects[effectName].value =
6156                            (val ? parseFloat(val) : mixitup.transformDefaults[effectName].value);
6157                    }
6158
6159                    if (val) {
6160                        for (i = 0; unit = units[i]; i++) {
6161                            if (val.indexOf(unit) > -1) {
6162                                effects[effectName].unit = unit;
6163
6164                                break;
6165                            }
6166                        }
6167                    } else {
6168                        effects[effectName].unit = mixitup.transformDefaults[effectName].unit;
6169                    }
6170
6171                    transform.push(
6172                        effectName +
6173                        '(' +
6174                        effects[effectName].value +
6175                        effects[effectName].unit +
6176                        ')'
6177                    );
6178            }
6179
6180            self.callActions('afterParseEffect', arguments);
6181        },
6182
6183        /**
6184         * @private
6185         * @instance
6186         * @since   2.0.0
6187         * @param   {Operation}     operation
6188         * @return  {State}
6189         */
6190
6191        buildState: function(operation) {
6192            var self        = this,
6193                state       = new mixitup.State(),
6194                target      = null,
6195                i           = -1;
6196
6197            self.callActions('beforeBuildState', arguments);
6198
6199            // Map target elements into state arrays.
6200            // the real target objects should never be exposed
6201
6202            for (i = 0; target = self.targets[i]; i++) {
6203                if (!operation.toRemove.length || operation.toRemove.indexOf(target) < 0) {
6204                    state.targets.push(target.dom.el);
6205                }
6206            }
6207
6208            for (i = 0; target = operation.matching[i]; i++) {
6209                state.matching.push(target.dom.el);
6210            }
6211
6212            for (i = 0; target = operation.show[i]; i++) {
6213                state.show.push(target.dom.el);
6214            }
6215
6216            for (i = 0; target = operation.hide[i]; i++) {
6217                if (!operation.toRemove.length || operation.toRemove.indexOf(target) < 0) {
6218                    state.hide.push(target.dom.el);
6219                }
6220            }
6221
6222            state.id                        = self.id;
6223            state.container                 = self.dom.container;
6224            state.activeFilter              = operation.newFilter;
6225            state.activeSort                = operation.newSort;
6226            state.activeDataset             = operation.newDataset;
6227            state.activeContainerClassName  = operation.newContainerClassName;
6228            state.hasFailed                 = operation.hasFailed;
6229            state.totalTargets              = self.targets.length;
6230            state.totalShow                 = operation.show.length;
6231            state.totalHide                 = operation.hide.length;
6232            state.totalMatching             = operation.matching.length;
6233            state.triggerElement            = operation.triggerElement;
6234
6235            return self.callFilters('stateBuildState', state, arguments);
6236        },
6237
6238        /**
6239         * @private
6240         * @instance
6241         * @since   2.0.0
6242         * @param   {boolean}   shouldAnimate
6243         * @param   {Operation} operation
6244         * @return  {void}
6245         */
6246
6247        goMix: function(shouldAnimate, operation) {
6248            var self        = this,
6249                deferred    = null;
6250
6251            self.callActions('beforeGoMix', arguments);
6252
6253            // If the animation duration is set to 0ms,
6254            // or no effects specified,
6255            // or the container is hidden
6256            // then abort animation
6257
6258            if (
6259                !self.config.animation.duration || !self.config.animation.effects || !h.isVisible(self.dom.container)
6260            ) {
6261                shouldAnimate = false;
6262            }
6263
6264            if (
6265                !operation.toShow.length &&
6266                !operation.toHide.length &&
6267                !operation.willSort &&
6268                !operation.willChangeLayout
6269            ) {
6270                // If nothing to show or hide, and not sorting or
6271                // changing layout
6272
6273                shouldAnimate = false;
6274            }
6275
6276            if (
6277                !operation.startState.show.length &&
6278                !operation.show.length
6279            ) {
6280                // If nothing currently shown, nothing to show
6281
6282                shouldAnimate = false;
6283            }
6284
6285            mixitup.events.fire('mixStart', self.dom.container, {
6286                state: operation.startState,
6287                futureState: operation.newState,
6288                instance: self
6289            }, self.dom.document);
6290
6291            if (typeof self.config.callbacks.onMixStart === 'function') {
6292                self.config.callbacks.onMixStart.call(
6293                    self.dom.container,
6294                    operation.startState,
6295                    operation.newState,
6296                    self
6297                );
6298            }
6299
6300            h.removeClass(self.dom.container, h.getClassname(self.config.classNames, 'container', self.config.classNames.modifierFailed));
6301
6302            if (!self.userDeferred) {
6303                // Queue empty, no pending operations
6304
6305                deferred = self.userDeferred = h.defer(mixitup.libraries);
6306            } else {
6307                // Use existing deferred
6308
6309                deferred = self.userDeferred;
6310            }
6311
6312            self.isBusy = true;
6313
6314            if (!shouldAnimate || !mixitup.features.has.transitions) {
6315                // Abort
6316
6317                if (self.config.debug.fauxAsync) {
6318                    setTimeout(function() {
6319                        self.cleanUp(operation);
6320                    }, self.config.animation.duration);
6321                } else {
6322                    self.cleanUp(operation);
6323                }
6324
6325                return self.callFilters('promiseGoMix', deferred.promise, arguments);
6326            }
6327
6328            // If we should animate and the platform supports transitions, go for it
6329
6330            if (window.pageYOffset !== operation.docState.scrollTop) {
6331                window.scrollTo(operation.docState.scrollLeft, operation.docState.scrollTop);
6332            }
6333
6334            if (self.config.animation.applyPerspective) {
6335                self.dom.parent.style[mixitup.features.perspectiveProp] =
6336                    self.config.animation.perspectiveDistance;
6337
6338                self.dom.parent.style[mixitup.features.perspectiveOriginProp] =
6339                    self.config.animation.perspectiveOrigin;
6340            }
6341
6342            if (
6343                self.config.animation.animateResizeContainer &&
6344                operation.startHeight !== operation.newHeight &&
6345                operation.viewportDeltaY !== operation.startHeight - operation.newHeight
6346            ) {
6347                self.dom.parent.style.height = operation.startHeight + 'px';
6348            }
6349
6350            if (
6351                self.config.animation.animateResizeContainer &&
6352                operation.startWidth !== operation.newWidth &&
6353                operation.viewportDeltaX !== operation.startWidth - operation.newWidth
6354            ) {
6355                self.dom.parent.style.width = operation.startWidth + 'px';
6356            }
6357
6358            if (operation.startHeight === operation.newHeight) {
6359                self.dom.parent.style.height = operation.startHeight + 'px';
6360            }
6361
6362            if (operation.startWidth === operation.newWidth) {
6363                self.dom.parent.style.width = operation.startWidth + 'px';
6364            }
6365
6366            if (operation.startHeight === operation.newHeight && operation.startWidth === operation.newWidth) {
6367                self.dom.parent.style.overflow = 'hidden';
6368            }
6369
6370            requestAnimationFrame(function() {
6371                self.moveTargets(operation);
6372            });
6373
6374            return self.callFilters('promiseGoMix', deferred.promise, arguments);
6375        },
6376
6377        /**
6378         * @private
6379         * @instance
6380         * @since   2.0.0
6381         * @param   {Operation}     operation
6382         * @return  {void}
6383         */
6384
6385        getStartMixData: function(operation) {
6386            var self        = this,
6387                parentStyle = window.getComputedStyle(self.dom.parent),
6388                parentRect  = self.dom.parent.getBoundingClientRect(),
6389                target      = null,
6390                data        = {},
6391                i           = -1,
6392                boxSizing   = parentStyle[mixitup.features.boxSizingProp];
6393
6394            self.incPadding = (boxSizing === 'border-box');
6395
6396            self.callActions('beforeGetStartMixData', arguments);
6397
6398            for (i = 0; target = operation.show[i]; i++) {
6399                data = target.getPosData();
6400
6401                operation.showPosData[i] = {
6402                    startPosData: data
6403                };
6404            }
6405
6406            for (i = 0; target = operation.toHide[i]; i++) {
6407                data = target.getPosData();
6408
6409                operation.toHidePosData[i] = {
6410                    startPosData: data
6411                };
6412            }
6413
6414            operation.startX = parentRect.left;
6415            operation.startY = parentRect.top;
6416
6417            operation.startHeight = self.incPadding ?
6418                parentRect.height :
6419                parentRect.height -
6420                    parseFloat(parentStyle.paddingTop) -
6421                    parseFloat(parentStyle.paddingBottom) -
6422                    parseFloat(parentStyle.borderTop) -
6423                    parseFloat(parentStyle.borderBottom);
6424
6425            operation.startWidth = self.incPadding ?
6426                parentRect.width :
6427                parentRect.width -
6428                    parseFloat(parentStyle.paddingLeft) -
6429                    parseFloat(parentStyle.paddingRight) -
6430                    parseFloat(parentStyle.borderLeft) -
6431                    parseFloat(parentStyle.borderRight);
6432
6433            self.callActions('afterGetStartMixData', arguments);
6434        },
6435
6436        /**
6437         * @private
6438         * @instance
6439         * @since   2.0.0
6440         * @param   {Operation}     operation
6441         * @return  {void}
6442         */
6443
6444        setInter: function(operation) {
6445            var self    = this,
6446                target  = null,
6447                i       = -1;
6448
6449            self.callActions('beforeSetInter', arguments);
6450
6451            // Prevent scrollbar flicker on non-inertial scroll platforms by clamping height/width
6452
6453            if (self.config.animation.clampHeight) {
6454                self.dom.parent.style.height    = operation.startHeight + 'px';
6455                self.dom.parent.style.overflow  = 'hidden';
6456            }
6457
6458            if (self.config.animation.clampWidth) {
6459                self.dom.parent.style.width     = operation.startWidth + 'px';
6460                self.dom.parent.style.overflow  = 'hidden';
6461            }
6462
6463            for (i = 0; target = operation.toShow[i]; i++) {
6464                target.show();
6465            }
6466
6467            if (operation.willChangeLayout) {
6468                h.removeClass(self.dom.container, operation.startContainerClassName);
6469                h.addClass(self.dom.container, operation.newContainerClassName);
6470            }
6471
6472            self.callActions('afterSetInter', arguments);
6473        },
6474
6475        /**
6476         * @private
6477         * @instance
6478         * @since   2.0.0
6479         * @param   {Operation}     operation
6480         * @return  {void}
6481         */
6482
6483        getInterMixData: function(operation) {
6484            var self    = this,
6485                target  = null,
6486                i       = -1;
6487
6488            self.callActions('beforeGetInterMixData', arguments);
6489
6490            for (i = 0; target = operation.show[i]; i++) {
6491                operation.showPosData[i].interPosData = target.getPosData();
6492            }
6493
6494            for (i = 0; target = operation.toHide[i]; i++) {
6495                operation.toHidePosData[i].interPosData = target.getPosData();
6496            }
6497
6498            self.callActions('afterGetInterMixData', arguments);
6499        },
6500
6501        /**
6502         * @private
6503         * @instance
6504         * @since   2.0.0
6505         * @param   {Operation}     operation
6506         * @return  {void}
6507         */
6508
6509        setFinal: function(operation) {
6510            var self    = this,
6511                target  = null,
6512                i       = -1;
6513
6514            self.callActions('beforeSetFinal', arguments);
6515
6516            operation.willSort && self.printSort(false, operation);
6517
6518            for (i = 0; target = operation.toHide[i]; i++) {
6519                target.hide();
6520            }
6521
6522            self.callActions('afterSetFinal', arguments);
6523        },
6524
6525        /**
6526         * @private
6527         * @instance
6528         * @since   2.0.0
6529         * @param   {Operation}     operation
6530         * @return  {void}
6531         */
6532
6533        getFinalMixData: function(operation) {
6534            var self        = this,
6535                parentStyle = null,
6536                parentRect  = null,
6537                target      = null,
6538                i           = -1;
6539
6540            self.callActions('beforeGetFinalMixData', arguments);
6541
6542            for (i = 0; target = operation.show[i]; i++) {
6543                operation.showPosData[i].finalPosData = target.getPosData();
6544            }
6545
6546            for (i = 0; target = operation.toHide[i]; i++) {
6547                operation.toHidePosData[i].finalPosData = target.getPosData();
6548            }
6549
6550            // Remove clamping
6551
6552            if (self.config.animation.clampHeight || self.config.animation.clampWidth) {
6553                self.dom.parent.style.height    =
6554                self.dom.parent.style.width     =
6555                self.dom.parent.style.overflow  = '';
6556            }
6557
6558            if (!self.incPadding) {
6559                parentStyle = window.getComputedStyle(self.dom.parent);
6560            }
6561
6562            parentRect  = self.dom.parent.getBoundingClientRect();
6563
6564            operation.newX = parentRect.left;
6565            operation.newY = parentRect.top;
6566
6567            operation.newHeight = self.incPadding ?
6568                parentRect.height :
6569                parentRect.height -
6570                    parseFloat(parentStyle.paddingTop) -
6571                    parseFloat(parentStyle.paddingBottom) -
6572                    parseFloat(parentStyle.borderTop) -
6573                    parseFloat(parentStyle.borderBottom);
6574
6575            operation.newWidth = self.incPadding ?
6576                parentRect.width :
6577                parentRect.width -
6578                    parseFloat(parentStyle.paddingLeft) -
6579                    parseFloat(parentStyle.paddingRight) -
6580                    parseFloat(parentStyle.borderLeft) -
6581                    parseFloat(parentStyle.borderRight);
6582
6583            operation.viewportDeltaX = operation.docState.viewportWidth - this.dom.document.documentElement.clientWidth;
6584            operation.viewportDeltaY = operation.docState.viewportHeight - this.dom.document.documentElement.clientHeight;
6585
6586            if (operation.willSort) {
6587                self.printSort(true, operation);
6588            }
6589
6590            for (i = 0; target = operation.toShow[i]; i++) {
6591                target.hide();
6592            }
6593
6594            for (i = 0; target = operation.toHide[i]; i++) {
6595                target.show();
6596            }
6597
6598            if (operation.willChangeLayout) {
6599                h.removeClass(self.dom.container, operation.newContainerClassName);
6600                h.addClass(self.dom.container, self.config.layout.containerClassName);
6601            }
6602
6603            self.callActions('afterGetFinalMixData', arguments);
6604        },
6605
6606        /**
6607         * @private
6608         * @instance
6609         * @since    3.0.0
6610         * @param    {Operation}     operation
6611         */
6612
6613        getTweenData: function(operation) {
6614            var self            = this,
6615                target          = null,
6616                posData         = null,
6617                effectNames     = Object.getOwnPropertyNames(self.effectsIn),
6618                effectName      = '',
6619                effect          = null,
6620                widthChange     = -1,
6621                heightChange    = -1,
6622                i               = -1,
6623                j               = -1;
6624
6625            self.callActions('beforeGetTweenData', arguments);
6626
6627            for (i = 0; target = operation.show[i]; i++) {
6628                posData             = operation.showPosData[i];
6629                posData.posIn       = new mixitup.StyleData();
6630                posData.posOut      = new mixitup.StyleData();
6631                posData.tweenData   = new mixitup.StyleData();
6632
6633                // Process x and y
6634
6635                if (target.isShown) {
6636                    posData.posIn.x = posData.startPosData.x - posData.interPosData.x;
6637                    posData.posIn.y = posData.startPosData.y - posData.interPosData.y;
6638                } else {
6639                    posData.posIn.x = posData.posIn.y = 0;
6640                }
6641
6642                posData.posOut.x = posData.finalPosData.x - posData.interPosData.x;
6643                posData.posOut.y = posData.finalPosData.y - posData.interPosData.y;
6644
6645                // Process opacity
6646
6647                posData.posIn.opacity       = target.isShown ? 1 : self.effectsIn.opacity;
6648                posData.posOut.opacity      = 1;
6649                posData.tweenData.opacity   = posData.posOut.opacity - posData.posIn.opacity;
6650
6651                // Adjust x and y if not nudging
6652
6653                if (!target.isShown && !self.config.animation.nudge) {
6654                    posData.posIn.x = posData.posOut.x;
6655                    posData.posIn.y = posData.posOut.y;
6656                }
6657
6658                posData.tweenData.x = posData.posOut.x - posData.posIn.x;
6659                posData.tweenData.y = posData.posOut.y - posData.posIn.y;
6660
6661                // Process width, height, and margins
6662
6663                if (self.config.animation.animateResizeTargets) {
6664                    posData.posIn.width     = posData.startPosData.width;
6665                    posData.posIn.height    = posData.startPosData.height;
6666
6667                    // "||" Prevents width/height change from including 0 width/height if hiding or showing
6668
6669                    widthChange = (posData.startPosData.width || posData.finalPosData.width) - posData.interPosData.width;
6670
6671                    posData.posIn.marginRight = posData.startPosData.marginRight - widthChange;
6672
6673                    heightChange = (posData.startPosData.height || posData.finalPosData.height) - posData.interPosData.height;
6674
6675                    posData.posIn.marginBottom = posData.startPosData.marginBottom - heightChange;
6676
6677                    posData.posOut.width    = posData.finalPosData.width;
6678                    posData.posOut.height   = posData.finalPosData.height;
6679
6680                    widthChange = (posData.finalPosData.width || posData.startPosData.width) - posData.interPosData.width;
6681
6682                    posData.posOut.marginRight = posData.finalPosData.marginRight - widthChange;
6683
6684                    heightChange = (posData.finalPosData.height || posData.startPosData.height) - posData.interPosData.height;
6685
6686                    posData.posOut.marginBottom = posData.finalPosData.marginBottom - heightChange;
6687
6688                    posData.tweenData.width         = posData.posOut.width - posData.posIn.width;
6689                    posData.tweenData.height        = posData.posOut.height - posData.posIn.height;
6690                    posData.tweenData.marginRight   = posData.posOut.marginRight - posData.posIn.marginRight;
6691                    posData.tweenData.marginBottom  = posData.posOut.marginBottom - posData.posIn.marginBottom;
6692                }
6693
6694                // Process transforms
6695
6696                for (j = 0; effectName = effectNames[j]; j++) {
6697                    effect = self.effectsIn[effectName];
6698
6699                    if (!(effect instanceof mixitup.TransformData) || !effect.value) continue;
6700
6701                    posData.posIn[effectName].value     = effect.value;
6702                    posData.posOut[effectName].value    = 0;
6703
6704                    posData.tweenData[effectName].value =
6705                        posData.posOut[effectName].value - posData.posIn[effectName].value;
6706
6707                    posData.posIn[effectName].unit =
6708                        posData.posOut[effectName].unit =
6709                        posData.tweenData[effectName].unit =
6710                        effect.unit;
6711                }
6712            }
6713
6714            for (i = 0; target = operation.toHide[i]; i++) {
6715                posData             = operation.toHidePosData[i];
6716                posData.posIn       = new mixitup.StyleData();
6717                posData.posOut      = new mixitup.StyleData();
6718                posData.tweenData   = new mixitup.StyleData();
6719
6720                // Process x and y
6721
6722                posData.posIn.x     = target.isShown ? posData.startPosData.x - posData.interPosData.x : 0;
6723                posData.posIn.y     = target.isShown ? posData.startPosData.y - posData.interPosData.y : 0;
6724                posData.posOut.x    = self.config.animation.nudge ? 0 : posData.posIn.x;
6725                posData.posOut.y    = self.config.animation.nudge ? 0 : posData.posIn.y;
6726                posData.tweenData.x = posData.posOut.x - posData.posIn.x;
6727                posData.tweenData.y = posData.posOut.y - posData.posIn.y;
6728
6729                // Process width, height, and margins
6730
6731                if (self.config.animation.animateResizeTargets) {
6732                    posData.posIn.width         = posData.startPosData.width;
6733                    posData.posIn.height        = posData.startPosData.height;
6734
6735                    widthChange = posData.startPosData.width - posData.interPosData.width;
6736
6737                    posData.posIn.marginRight = posData.startPosData.marginRight - widthChange;
6738
6739                    heightChange = posData.startPosData.height - posData.interPosData.height;
6740
6741                    posData.posIn.marginBottom = posData.startPosData.marginBottom - heightChange;
6742                }
6743
6744                // Process opacity
6745
6746                posData.posIn.opacity       = 1;
6747                posData.posOut.opacity      = self.effectsOut.opacity;
6748                posData.tweenData.opacity   = posData.posOut.opacity - posData.posIn.opacity;
6749
6750                // Process transforms
6751
6752                for (j = 0; effectName = effectNames[j]; j++) {
6753                    effect = self.effectsOut[effectName];
6754
6755                    if (!(effect instanceof mixitup.TransformData) || !effect.value) continue;
6756
6757                    posData.posIn[effectName].value     = 0;
6758                    posData.posOut[effectName].value    = effect.value;
6759
6760                    posData.tweenData[effectName].value =
6761                        posData.posOut[effectName].value - posData.posIn[effectName].value;
6762
6763                    posData.posIn[effectName].unit =
6764                        posData.posOut[effectName].unit =
6765                        posData.tweenData[effectName].unit =
6766                        effect.unit;
6767                }
6768            }
6769
6770            self.callActions('afterGetTweenData', arguments);
6771        },
6772
6773        /**
6774         * @private
6775         * @instance
6776         * @since   3.0.0
6777         * @param   {Operation}     operation
6778         * @return  {void}
6779         */
6780
6781        moveTargets: function(operation) {
6782            var self            = this,
6783                target          = null,
6784                moveData        = null,
6785                posData         = null,
6786                statusChange    = '',
6787                willTransition  = false,
6788                staggerIndex    = -1,
6789                i               = -1,
6790                checkProgress   = self.checkProgress.bind(self);
6791
6792            self.callActions('beforeMoveTargets', arguments);
6793
6794            // TODO: this is an extra loop in addition to the calcs
6795            // done in getOperation, could some of this be done there?
6796
6797            for (i = 0; target = operation.show[i]; i++) {
6798                moveData    = new mixitup.IMoveData();
6799                posData     = operation.showPosData[i];
6800
6801                statusChange = target.isShown ? 'none' : 'show';
6802
6803                willTransition = self.willTransition(
6804                    statusChange,
6805                    operation.hasEffect,
6806                    posData.posIn,
6807                    posData.posOut
6808                );
6809
6810                if (willTransition) {
6811                    // Prevent non-transitioning targets from incrementing the staggerIndex
6812
6813                    staggerIndex++;
6814                }
6815
6816                target.show();
6817
6818                moveData.posIn          = posData.posIn;
6819                moveData.posOut         = posData.posOut;
6820                moveData.statusChange   = statusChange;
6821                moveData.staggerIndex   = staggerIndex;
6822                moveData.operation      = operation;
6823                moveData.callback       = willTransition ? checkProgress : null;
6824
6825                target.move(moveData);
6826            }
6827
6828            for (i = 0; target = operation.toHide[i]; i++) {
6829                posData  = operation.toHidePosData[i];
6830                moveData = new mixitup.IMoveData();
6831
6832                statusChange = 'hide';
6833
6834                willTransition = self.willTransition(statusChange, posData.posIn, posData.posOut);
6835
6836                moveData.posIn          = posData.posIn;
6837                moveData.posOut         = posData.posOut;
6838                moveData.statusChange   = statusChange;
6839                moveData.staggerIndex   = i;
6840                moveData.operation      = operation;
6841                moveData.callback       = willTransition ? checkProgress : null;
6842
6843                target.move(moveData);
6844            }
6845
6846            if (self.config.animation.animateResizeContainer) {
6847                self.dom.parent.style[mixitup.features.transitionProp] =
6848                    'height ' + self.config.animation.duration + 'ms ease, ' +
6849                    'width ' + self.config.animation.duration + 'ms ease ';
6850
6851                requestAnimationFrame(function() {
6852                    if (
6853                        operation.startHeight !== operation.newHeight &&
6854                        operation.viewportDeltaY !== operation.startHeight - operation.newHeight
6855                    ) {
6856                        self.dom.parent.style.height = operation.newHeight + 'px';
6857                    }
6858
6859                    if (
6860                        operation.startWidth !== operation.newWidth &&
6861                        operation.viewportDeltaX !== operation.startWidth - operation.newWidth
6862                    ) {
6863                        self.dom.parent.style.width = operation.newWidth + 'px';
6864                    }
6865                });
6866            }
6867
6868            if (operation.willChangeLayout) {
6869                h.removeClass(self.dom.container, self.config.layout.ContainerClassName);
6870                h.addClass(self.dom.container, operation.newContainerClassName);
6871            }
6872
6873            self.callActions('afterMoveTargets', arguments);
6874        },
6875
6876        /**
6877         * @private
6878         * @instance
6879         * @return  {boolean}
6880         */
6881
6882        hasEffect: function() {
6883            var self        = this,
6884                EFFECTABLES = [
6885                    'scale',
6886                    'translateX', 'translateY', 'translateZ',
6887                    'rotateX', 'rotateY', 'rotateZ'
6888                ],
6889                effectName  = '',
6890                effect      = null,
6891                result      = false,
6892                value       = -1,
6893                i           = -1;
6894
6895            if (self.effectsIn.opacity !== 1) {
6896                return self.callFilters('resultHasEffect', true, arguments);
6897            }
6898
6899            for (i = 0; effectName = EFFECTABLES[i]; i++) {
6900                effect  = self.effectsIn[effectName];
6901                value   = (typeof effect && effect.value !== 'undefined') ?
6902                    effect.value : effect;
6903
6904                if (value !== 0) {
6905                    result = true;
6906
6907                    break;
6908                }
6909            }
6910
6911            return self.callFilters('resultHasEffect', result, arguments);
6912        },
6913
6914        /**
6915         * Determines if a target element will transition in
6916         * some fasion and therefore requires binding of
6917         * transitionEnd
6918         *
6919         * @private
6920         * @instance
6921         * @since   3.0.0
6922         * @param   {string}        statusChange
6923         * @param   {boolean}       hasEffect
6924         * @param   {StyleData}     posIn
6925         * @param   {StyleData}     posOut
6926         * @return  {boolean}
6927         */
6928
6929        willTransition: function(statusChange, hasEffect, posIn, posOut) {
6930            var self    = this,
6931                result  = false;
6932
6933            if (!h.isVisible(self.dom.container)) {
6934                // If the container is not visible, the transitionEnd
6935                // event will not occur and MixItUp will hang
6936
6937                result = false;
6938            } else if (
6939                (statusChange !== 'none' && hasEffect) ||
6940                posIn.x !== posOut.x ||
6941                posIn.y !== posOut.y
6942            ) {
6943                // If opacity and/or translate will change
6944
6945                result = true;
6946            } else if (self.config.animation.animateResizeTargets) {
6947                // Check if width, height or margins will change
6948
6949                result = (
6950                    posIn.width !== posOut.width ||
6951                    posIn.height !== posOut.height ||
6952                    posIn.marginRight !== posOut.marginRight ||
6953                    posIn.marginTop !== posOut.marginTop
6954                );
6955            } else {
6956                result = false;
6957            }
6958
6959            return self.callFilters('resultWillTransition', result, arguments);
6960        },
6961
6962        /**
6963         * @private
6964         * @instance
6965         * @since   2.0.0
6966         * @param   {Operation}     operation
6967         * @return  {void}
6968         */
6969
6970        checkProgress: function(operation) {
6971            var self = this;
6972
6973            self.targetsDone++;
6974
6975            if (self.targetsBound === self.targetsDone) {
6976                self.cleanUp(operation);
6977            }
6978        },
6979
6980        /**
6981         * @private
6982         * @instance
6983         * @since   2.0.0
6984         * @param   {Operation}     operation
6985         * @return  {void}
6986         */
6987
6988        cleanUp: function(operation) {
6989            var self                = this,
6990                target              = null,
6991                whitespaceBefore    = null,
6992                whitespaceAfter     = null,
6993                nextInQueue         = null,
6994                i                   = -1;
6995
6996            self.callActions('beforeCleanUp', arguments);
6997
6998            self.targetsMoved          =
6999                self.targetsImmovable  =
7000                self.targetsBound      =
7001                self.targetsDone       = 0;
7002
7003            for (i = 0; target = operation.show[i]; i++) {
7004                target.cleanUp();
7005
7006                target.show();
7007            }
7008
7009            for (i = 0; target = operation.toHide[i]; i++) {
7010                target.cleanUp();
7011
7012                target.hide();
7013            }
7014
7015            if (operation.willSort) {
7016                self.printSort(false, operation);
7017            }
7018
7019            // Remove any styles applied to the parent container
7020
7021            self.dom.parent.style[mixitup.features.transitionProp]             =
7022                self.dom.parent.style.height                                   =
7023                self.dom.parent.style.width                                    =
7024                self.dom.parent.style.overflow                                 =
7025                self.dom.parent.style[mixitup.features.perspectiveProp]        =
7026                self.dom.parent.style[mixitup.features.perspectiveOriginProp]  = '';
7027
7028            if (operation.willChangeLayout) {
7029                h.removeClass(self.dom.container, operation.startContainerClassName);
7030                h.addClass(self.dom.container, operation.newContainerClassName);
7031            }
7032
7033            if (operation.toRemove.length) {
7034                for (i = 0; target = self.targets[i]; i++) {
7035                    if (operation.toRemove.indexOf(target) > -1) {
7036                        if (
7037                            (whitespaceBefore = target.dom.el.previousSibling) && whitespaceBefore.nodeName === '#text' &&
7038                            (whitespaceAfter = target.dom.el.nextSibling) && whitespaceAfter.nodeName === '#text'
7039                        ) {
7040                            h.removeWhitespace(whitespaceBefore);
7041                        }
7042
7043                        if (!operation.willSort) {
7044                            // NB: Sorting will remove targets as a bi-product of `printSort()`
7045
7046                            self.dom.parent.removeChild(target.dom.el);
7047                        }
7048
7049                        self.targets.splice(i, 1);
7050
7051                        target.isInDom = false;
7052
7053                        i--;
7054                    }
7055                }
7056
7057                // Since targets have been removed, the original order must be updated
7058
7059                self.origOrder = self.targets;
7060            }
7061
7062            if (operation.willSort) {
7063                self.targets = operation.newOrder;
7064            }
7065
7066            self.state = operation.newState;
7067            self.lastOperation = operation;
7068
7069            self.dom.targets = self.state.targets;
7070
7071            // mixEnd
7072
7073            mixitup.events.fire('mixEnd', self.dom.container, {
7074                state: self.state,
7075                instance: self
7076            }, self.dom.document);
7077
7078            if (typeof self.config.callbacks.onMixEnd === 'function') {
7079                self.config.callbacks.onMixEnd.call(self.dom.container, self.state, self);
7080            }
7081
7082            if (operation.hasFailed) {
7083                // mixFail
7084
7085                mixitup.events.fire('mixFail', self.dom.container, {
7086                    state: self.state,
7087                    instance: self
7088                }, self.dom.document);
7089
7090                if (typeof self.config.callbacks.onMixFail === 'function') {
7091                    self.config.callbacks.onMixFail.call(self.dom.container, self.state, self);
7092                }
7093
7094                h.addClass(self.dom.container, h.getClassname(self.config.classNames, 'container', self.config.classNames.modifierFailed));
7095            }
7096
7097            // User-defined callback function
7098
7099            if (typeof self.userCallback === 'function') {
7100                self.userCallback.call(self.dom.container, self.state, self);
7101            }
7102
7103            if (typeof self.userDeferred.resolve === 'function') {
7104                self.userDeferred.resolve(self.state);
7105            }
7106
7107            self.userCallback  = null;
7108            self.userDeferred  = null;
7109            self.lastClicked   = null;
7110            self.isToggling    = false;
7111            self.isBusy        = false;
7112
7113            if (self.queue.length) {
7114                self.callActions('beforeReadQueueCleanUp', arguments);
7115
7116                nextInQueue = self.queue.shift();
7117
7118                // Update non-public API properties stored in queue
7119
7120                self.userDeferred  = nextInQueue.deferred;
7121                self.isToggling    = nextInQueue.isToggling;
7122                self.lastClicked   = nextInQueue.triggerElement;
7123
7124                if (nextInQueue.instruction.command instanceof mixitup.CommandMultimix) {
7125                    self.multimix.apply(self, nextInQueue.args);
7126                } else {
7127                    self.dataset.apply(self, nextInQueue.args);
7128                }
7129            }
7130
7131            self.callActions('afterCleanUp', arguments);
7132        },
7133
7134        /**
7135         * @private
7136         * @instance
7137         * @since   2.0.0
7138         * @param   {Array<*>}  args
7139         * @return  {mixitup.UserInstruction}
7140         */
7141
7142        parseMultimixArgs: function(args) {
7143            var self        = this,
7144                instruction = new mixitup.UserInstruction(),
7145                arg         = null,
7146                i           = -1;
7147
7148            instruction.animate = self.config.animation.enable;
7149            instruction.command = new mixitup.CommandMultimix();
7150
7151            for (i = 0; i < args.length; i++) {
7152                arg = args[i];
7153
7154                if (arg === null) continue;
7155
7156                if (typeof arg === 'object') {
7157                    h.extend(instruction.command, arg);
7158                } else if (typeof arg === 'boolean') {
7159                    instruction.animate = arg;
7160                } else if (typeof arg === 'function') {
7161                    instruction.callback = arg;
7162                }
7163            }
7164
7165            // Coerce arbitrary command arguments into typed command objects
7166
7167            if (instruction.command.insert && !(instruction.command.insert instanceof mixitup.CommandInsert)) {
7168                instruction.command.insert = self.parseInsertArgs([instruction.command.insert]).command;
7169            }
7170
7171            if (instruction.command.remove && !(instruction.command.remove instanceof mixitup.CommandRemove)) {
7172                instruction.command.remove = self.parseRemoveArgs([instruction.command.remove]).command;
7173            }
7174
7175            if (instruction.command.filter && !(instruction.command.filter instanceof mixitup.CommandFilter)) {
7176                instruction.command.filter = self.parseFilterArgs([instruction.command.filter]).command;
7177            }
7178
7179            if (instruction.command.sort && !(instruction.command.sort instanceof mixitup.CommandSort)) {
7180                instruction.command.sort = self.parseSortArgs([instruction.command.sort]).command;
7181            }
7182
7183            if (instruction.command.changeLayout && !(instruction.command.changeLayout instanceof mixitup.CommandChangeLayout)) {
7184                instruction.command.changeLayout = self.parseChangeLayoutArgs([instruction.command.changeLayout]).command;
7185            }
7186
7187            instruction = self.callFilters('instructionParseMultimixArgs', instruction, arguments);
7188
7189            h.freeze(instruction);
7190
7191            return instruction;
7192        },
7193
7194        /**
7195         * @private
7196         * @instance
7197         * @since   2.0.0
7198         * @param   {Array<*>}  args
7199         * @return  {mixitup.UserInstruction}
7200         */
7201
7202        parseFilterArgs: function(args) {
7203            var self        = this,
7204                instruction = new mixitup.UserInstruction(),
7205                arg         = null,
7206                i           = -1;
7207
7208            instruction.animate = self.config.animation.enable;
7209            instruction.command = new mixitup.CommandFilter();
7210
7211            for (i = 0; i < args.length; i++) {
7212                arg = args[i];
7213
7214                if (typeof arg === 'string') {
7215                    // Selector
7216
7217                    instruction.command.selector = arg;
7218                } else if (arg === null) {
7219                    instruction.command.collection = [];
7220                } else if (typeof arg === 'object' && h.isElement(arg, self.dom.document)) {
7221                    // Single element
7222
7223                    instruction.command.collection = [arg];
7224                } else if (typeof arg === 'object' && typeof arg.length !== 'undefined') {
7225                    // Multiple elements in array, NodeList or jQuery collection
7226
7227                    instruction.command.collection = h.arrayFromList(arg);
7228                } else if (typeof arg === 'object') {
7229                    // Filter command
7230
7231                    h.extend(instruction.command, arg);
7232                } else if (typeof arg === 'boolean') {
7233                    instruction.animate = arg;
7234                } else if (typeof arg === 'function') {
7235                    instruction.callback = arg;
7236                }
7237            }
7238
7239            if (instruction.command.selector && instruction.command.collection) {
7240                throw new Error(mixitup.messages.errorFilterInvalidArguments());
7241            }
7242
7243            instruction = self.callFilters('instructionParseFilterArgs', instruction, arguments);
7244
7245            h.freeze(instruction);
7246
7247            return instruction;
7248        },
7249
7250        parseSortArgs: function(args) {
7251            var self        = this,
7252                instruction = new mixitup.UserInstruction(),
7253                arg         = null,
7254                sortString  = '',
7255                i           = -1;
7256
7257            instruction.animate = self.config.animation.enable;
7258            instruction.command = new mixitup.CommandSort();
7259
7260            for (i = 0; i < args.length; i++) {
7261                arg = args[i];
7262
7263                if (arg === null) continue;
7264
7265                switch (typeof arg) {
7266                    case 'string':
7267                        // Sort string
7268
7269                        sortString = arg;
7270
7271                        break;
7272                    case 'object':
7273                        // Array of element references
7274
7275                        if (arg.length) {
7276                            instruction.command.collection = h.arrayFromList(arg);
7277                        }
7278
7279                        break;
7280                    case 'boolean':
7281                        instruction.animate = arg;
7282
7283                        break;
7284                    case 'function':
7285                        instruction.callback = arg;
7286
7287                        break;
7288                }
7289            }
7290
7291            if (sortString) {
7292                instruction.command = self.parseSortString(sortString, instruction.command);
7293            }
7294
7295            instruction = self.callFilters('instructionParseSortArgs', instruction, arguments);
7296
7297            h.freeze(instruction);
7298
7299            return instruction;
7300        },
7301
7302        /**
7303         * @private
7304         * @instance
7305         * @since   2.0.0
7306         * @param   {Array<*>}  args
7307         * @return  {mixitup.UserInstruction}
7308         */
7309
7310        parseInsertArgs: function(args) {
7311            var self        = this,
7312                instruction = new mixitup.UserInstruction(),
7313                arg         = null,
7314                i           = -1;
7315
7316            instruction.animate = self.config.animation.enable;
7317            instruction.command = new mixitup.CommandInsert();
7318
7319            for (i = 0; i < args.length; i++) {
7320                arg = args[i];
7321
7322                if (arg === null) continue;
7323
7324                if (typeof arg === 'number') {
7325                    // Insert index
7326
7327                    instruction.command.index = arg;
7328                } else if (typeof arg === 'string' && ['before', 'after'].indexOf(arg) > -1) {
7329                    // 'before'/'after'
7330
7331                    instruction.command.position = arg;
7332                } else if (typeof arg === 'string') {
7333                    // Markup
7334
7335                    instruction.command.collection =
7336                        h.arrayFromList(h.createElement(arg).childNodes);
7337                } else if (typeof arg === 'object' && h.isElement(arg, self.dom.document)) {
7338                    // Single element
7339
7340                    !instruction.command.collection.length ?
7341                        (instruction.command.collection = [arg]) :
7342                        (instruction.command.sibling = arg);
7343                } else if (typeof arg === 'object' && arg.length) {
7344                    // Multiple elements in array or jQuery collection
7345
7346                    !instruction.command.collection.length ?
7347                        (instruction.command.collection = arg) :
7348                        instruction.command.sibling = arg[0];
7349                } else if (typeof arg === 'object' && arg.childNodes && arg.childNodes.length) {
7350                    // Document fragment
7351
7352                    !instruction.command.collection.length ?
7353                        instruction.command.collection = h.arrayFromList(arg.childNodes) :
7354                        instruction.command.sibling = arg.childNodes[0];
7355                } else if (typeof arg === 'object') {
7356                    // Insert command
7357
7358                    h.extend(instruction.command, arg);
7359                } else if (typeof arg === 'boolean') {
7360                    instruction.animate = arg;
7361                } else if (typeof arg === 'function') {
7362                    instruction.callback = arg;
7363                }
7364            }
7365
7366            if (instruction.command.index && instruction.command.sibling) {
7367                throw new Error(mixitup.messages.errorInsertInvalidArguments());
7368            }
7369
7370            if (!instruction.command.collection.length && self.config.debug.showWarnings) {
7371                console.warn(mixitup.messages.warningInsertNoElements());
7372            }
7373
7374            instruction = self.callFilters('instructionParseInsertArgs', instruction, arguments);
7375
7376            h.freeze(instruction);
7377
7378            return instruction;
7379        },
7380
7381        /**
7382         * @private
7383         * @instance
7384         * @since   3.0.0
7385         * @param   {Array<*>}  args
7386         * @return  {mixitup.UserInstruction}
7387         */
7388
7389        parseRemoveArgs: function(args) {
7390            var self        = this,
7391                instruction = new mixitup.UserInstruction(),
7392                target      = null,
7393                arg         = null,
7394                i           = -1;
7395
7396            instruction.animate = self.config.animation.enable;
7397            instruction.command = new mixitup.CommandRemove();
7398
7399            for (i = 0; i < args.length; i++) {
7400                arg = args[i];
7401
7402                if (arg === null) continue;
7403
7404                switch (typeof arg) {
7405                    case 'number':
7406                        if (self.targets[arg]) {
7407                            instruction.command.targets[0] = self.targets[arg];
7408                        }
7409
7410                        break;
7411                    case 'string':
7412                        instruction.command.collection = h.arrayFromList(self.dom.parent.querySelectorAll(arg));
7413
7414                        break;
7415                    case 'object':
7416                        if (arg && arg.length) {
7417                            instruction.command.collection = arg;
7418                        } else if (h.isElement(arg, self.dom.document)) {
7419                            instruction.command.collection = [arg];
7420                        } else {
7421                            // Remove command
7422
7423                            h.extend(instruction.command, arg);
7424                        }
7425
7426                        break;
7427                    case 'boolean':
7428                        instruction.animate = arg;
7429
7430                        break;
7431                    case 'function':
7432                        instruction.callback = arg;
7433
7434                        break;
7435                }
7436            }
7437
7438            if (instruction.command.collection.length) {
7439                for (i = 0; target = self.targets[i]; i++) {
7440                    if (instruction.command.collection.indexOf(target.dom.el) > -1) {
7441                        instruction.command.targets.push(target);
7442                    }
7443                }
7444            }
7445
7446            if (!instruction.command.targets.length && self.config.debug.showWarnings) {
7447                console.warn(mixitup.messages.warningRemoveNoElements());
7448            }
7449
7450            h.freeze(instruction);
7451
7452            return instruction;
7453        },
7454
7455        /**
7456         * @private
7457         * @instance
7458         * @since   3.0.0
7459         * @param   {Array<*>}  args
7460         * @return  {mixitup.UserInstruction}
7461         */
7462
7463        parseDatasetArgs: function(args) {
7464            var self        = this,
7465                instruction = new mixitup.UserInstruction(),
7466                arg         = null,
7467                i           = -1;
7468
7469            instruction.animate = self.config.animation.enable;
7470            instruction.command = new mixitup.CommandDataset();
7471
7472            for (i = 0; i < args.length; i++) {
7473                arg = args[i];
7474
7475                if (arg === null) continue;
7476
7477                switch (typeof arg) {
7478                    case 'object':
7479                        if (Array.isArray(arg) || typeof arg.length === 'number') {
7480                            instruction.command.dataset = arg;
7481                        } else {
7482                            // Change layout command
7483
7484                            h.extend(instruction.command, arg);
7485                        }
7486
7487                        break;
7488                    case 'boolean':
7489                        instruction.animate = arg;
7490
7491                        break;
7492                    case 'function':
7493                        instruction.callback = arg;
7494
7495                        break;
7496                }
7497            }
7498
7499            h.freeze(instruction);
7500
7501            return instruction;
7502        },
7503
7504        /**
7505         * @private
7506         * @instance
7507         * @since   3.0.0
7508         * @param   {Array<*>}  args
7509         * @return  {mixitup.UserInstruction}
7510         */
7511
7512        parseChangeLayoutArgs: function(args) {
7513            var self        = this,
7514                instruction = new mixitup.UserInstruction(),
7515                arg         = null,
7516                i           = -1;
7517
7518            instruction.animate = self.config.animation.enable;
7519            instruction.command = new mixitup.CommandChangeLayout();
7520
7521            for (i = 0; i < args.length; i++) {
7522                arg = args[i];
7523
7524                if (arg === null) continue;
7525
7526                switch (typeof arg) {
7527                    case 'string':
7528                        instruction.command.containerClassName = arg;
7529
7530                        break;
7531                    case 'object':
7532                        // Change layout command
7533
7534                        h.extend(instruction.command, arg);
7535
7536                        break;
7537                    case 'boolean':
7538                        instruction.animate = arg;
7539
7540                        break;
7541                    case 'function':
7542                        instruction.callback = arg;
7543
7544                        break;
7545                }
7546            }
7547
7548            h.freeze(instruction);
7549
7550            return instruction;
7551        },
7552
7553        /**
7554         * @private
7555         * @instance
7556         * @since       3.0.0
7557         * @param       {mixitup.QueueItem}         queueItem
7558         * @return      {Promise.<mixitup.State>}
7559         */
7560
7561        queueMix: function(queueItem) {
7562            var self            = this,
7563                deferred        = null,
7564                toggleSelector  = '';
7565
7566            self.callActions('beforeQueueMix', arguments);
7567
7568            deferred = h.defer(mixitup.libraries);
7569
7570            if (self.config.animation.queue && self.queue.length < self.config.animation.queueLimit) {
7571                queueItem.deferred = deferred;
7572
7573                self.queue.push(queueItem);
7574
7575                // Keep controls in sync with user interactions. Mixer will catch up as it drains the queue.
7576
7577                if (self.config.controls.enable) {
7578                    if (self.isToggling) {
7579                        self.buildToggleArray(queueItem.instruction.command);
7580
7581                        toggleSelector = self.getToggleSelector();
7582
7583                        self.updateControls({
7584                            filter: {
7585                                selector: toggleSelector
7586                            }
7587                        });
7588                    } else {
7589                        self.updateControls(queueItem.instruction.command);
7590                    }
7591                }
7592            } else {
7593                if (self.config.debug.showWarnings) {
7594                    console.warn(mixitup.messages.warningMultimixInstanceQueueFull());
7595                }
7596
7597                deferred.resolve(self.state);
7598
7599                mixitup.events.fire('mixBusy', self.dom.container, {
7600                    state: self.state,
7601                    instance: self
7602                }, self.dom.document);
7603
7604                if (typeof self.config.callbacks.onMixBusy === 'function') {
7605                    self.config.callbacks.onMixBusy.call(self.dom.container, self.state, self);
7606                }
7607            }
7608
7609            return self.callFilters('promiseQueueMix', deferred.promise, arguments);
7610        },
7611
7612        /**
7613         * @private
7614         * @instance
7615         * @since   3.0.0
7616         * @param   {Array.<object>}    newDataset
7617         * @return  {Operation}
7618         */
7619
7620        getDataOperation: function(newDataset) {
7621            var self                = this,
7622                operation           = new mixitup.Operation(),
7623                startDataset        = [];
7624
7625            operation = self.callFilters('operationUnmappedGetDataOperation', operation, arguments);
7626
7627            if (self.dom.targets.length && !(startDataset = (self.state.activeDataset || [])).length) {
7628                throw new Error(mixitup.messages.errorDatasetNotSet());
7629            }
7630
7631            operation.id            = h.randomHex();
7632            operation.startState    = self.state;
7633            operation.startDataset  = startDataset;
7634            operation.newDataset    = newDataset.slice();
7635
7636            self.diffDatasets(operation);
7637
7638            operation.startOrder = self.targets;
7639            operation.newOrder = operation.show;
7640
7641            if (self.config.animation.enable) {
7642                self.getStartMixData(operation);
7643                self.setInter(operation);
7644
7645                operation.docState = h.getDocumentState(self.dom.document);
7646
7647                self.getInterMixData(operation);
7648                self.setFinal(operation);
7649                self.getFinalMixData(operation);
7650
7651                self.parseEffects();
7652
7653                operation.hasEffect = self.hasEffect();
7654
7655                self.getTweenData(operation);
7656            }
7657
7658            self.targets = operation.show.slice();
7659
7660            operation.newState = self.buildState(operation);
7661
7662            // NB: Targets to be removed must be included in `self.targets` for removal during clean up,
7663            // but are added after state is built so that state is accurate
7664
7665            Array.prototype.push.apply(self.targets, operation.toRemove);
7666
7667            operation = self.callFilters('operationMappedGetDataOperation', operation, arguments);
7668
7669            return operation;
7670        },
7671
7672        /**
7673         * @private
7674         * @instance
7675         * @since   3.0.0
7676         * @param   {mixitup.Operation} operation
7677         * @return  {void}
7678         */
7679
7680        diffDatasets: function(operation) {
7681            var self                = this,
7682                persistantStartIds  = [],
7683                persistantNewIds    = [],
7684                insertedTargets     = [],
7685                data                = null,
7686                target              = null,
7687                el                  = null,
7688                frag                = null,
7689                nextEl              = null,
7690                uids                = {},
7691                id                  = '',
7692                i                   = -1;
7693
7694            self.callActions('beforeDiffDatasets', arguments);
7695
7696            for (i = 0; data = operation.newDataset[i]; i++) {
7697                if (typeof (id = data[self.config.data.uidKey]) === 'undefined' || id.toString().length < 1) {
7698                    throw new TypeError(mixitup.messages.errorDatasetInvalidUidKey({
7699                        uidKey: self.config.data.uidKey
7700                    }));
7701                }
7702
7703                if (!uids[id]) {
7704                    uids[id] = true;
7705                } else {
7706                    throw new Error(mixitup.messages.errorDatasetDuplicateUid({
7707                        uid: id
7708                    }));
7709                }
7710
7711                if ((target = self.cache[id]) instanceof mixitup.Target) {
7712                    // Already in cache
7713
7714                    if (self.config.data.dirtyCheck && !h.deepEquals(data, target.data)) {
7715                        // change detected
7716
7717                        el = target.render(data);
7718
7719                        target.data = data;
7720
7721                        if (el !== target.dom.el) {
7722                            // Update target element reference
7723
7724                            if (target.isInDom) {
7725                                target.unbindEvents();
7726
7727                                self.dom.parent.replaceChild(el, target.dom.el);
7728                            }
7729
7730                            if (!target.isShown) {
7731                                el.style.display = 'none';
7732                            }
7733
7734                            target.dom.el = el;
7735
7736                            if (target.isInDom) {
7737                                target.bindEvents();
7738                            }
7739                        }
7740                    }
7741
7742                    el = target.dom.el;
7743                } else {
7744                    // New target
7745
7746                    target = new mixitup.Target();
7747
7748                    target.init(null, self, data);
7749
7750                    target.hide();
7751                }
7752
7753                if (!target.isInDom) {
7754                    // Adding to DOM
7755
7756                    if (!frag) {
7757                        // Open frag
7758
7759                        frag = self.dom.document.createDocumentFragment();
7760                    }
7761
7762                    if (frag.lastElementChild) {
7763                        frag.appendChild(self.dom.document.createTextNode(' '));
7764                    }
7765
7766                    frag.appendChild(target.dom.el);
7767
7768                    target.isInDom = true;
7769
7770                    target.unbindEvents();
7771                    target.bindEvents();
7772                    target.hide();
7773
7774                    operation.toShow.push(target);
7775
7776                    insertedTargets.push(target);
7777                } else {
7778                    // Already in DOM
7779
7780                    nextEl = target.dom.el.nextElementSibling;
7781
7782                    persistantNewIds.push(id);
7783
7784                    if (frag) {
7785                        // Close and insert previously opened frag
7786
7787                        if (frag.lastElementChild) {
7788                            frag.appendChild(self.dom.document.createTextNode(' '));
7789                        }
7790
7791                        self.insertDatasetFrag(frag, target.dom.el, insertedTargets);
7792
7793                        frag = null;
7794                    }
7795                }
7796
7797                operation.show.push(target);
7798            }
7799
7800            if (frag) {
7801                // Unclosed frag remaining
7802
7803                nextEl = nextEl || self.config.layout.siblingAfter;
7804
7805                if (nextEl) {
7806                    frag.appendChild(self.dom.document.createTextNode(' '));
7807                }
7808
7809                self.insertDatasetFrag(frag, nextEl, insertedTargets);
7810            }
7811
7812            for (i = 0; data = operation.startDataset[i]; i++) {
7813                id = data[self.config.data.uidKey];
7814
7815                target = self.cache[id];
7816
7817                if (operation.show.indexOf(target) < 0) {
7818                    // Previously shown but now absent
7819
7820                    operation.hide.push(target);
7821                    operation.toHide.push(target);
7822                    operation.toRemove.push(target);
7823                } else {
7824                    persistantStartIds.push(id);
7825                }
7826            }
7827
7828            if (!h.isEqualArray(persistantStartIds, persistantNewIds)) {
7829                operation.willSort = true;
7830            }
7831
7832            self.callActions('afterDiffDatasets', arguments);
7833        },
7834
7835        /**
7836         * @private
7837         * @instance
7838         * @since   3.1.5
7839         * @param   {DocumentFragment}          frag
7840         * @param   {(HTMLElement|null)}        nextEl
7841         * @param   {Array.<mixitup.Target>}    targets
7842         * @return  {void}
7843         */
7844
7845        insertDatasetFrag: function(frag, nextEl, targets) {
7846            var self = this;
7847            var insertAt = nextEl ? h.arrayFromList(self.dom.parent.children).indexOf(nextEl) : self.targets.length;
7848
7849            self.dom.parent.insertBefore(frag, nextEl);
7850
7851            while (targets.length) {
7852                self.targets.splice(insertAt, 0, targets.shift());
7853
7854                insertAt++;
7855            }
7856        },
7857
7858        /**
7859         * @private
7860         * @instance
7861         * @since   3.0.0
7862         * @param   {mixitup.CommandSort} sortCommandA
7863         * @param   {mixitup.CommandSort} sortCommandB
7864         * @return  {boolean}
7865         */
7866
7867        willSort: function(sortCommandA, sortCommandB) {
7868            var self    = this,
7869                result  = false;
7870
7871            if (
7872                self.config.behavior.liveSort ||
7873                sortCommandA.order       === 'random' ||
7874                sortCommandA.attribute   !== sortCommandB.attribute ||
7875                sortCommandA.order       !== sortCommandB.order ||
7876                sortCommandA.collection  !== sortCommandB.collection ||
7877                (sortCommandA.next === null && sortCommandB.next) ||
7878                (sortCommandA.next && sortCommandB.next === null)
7879            ) {
7880                result = true;
7881            } else if (sortCommandA.next && sortCommandB.next) {
7882                result = self.willSort(sortCommandA.next, sortCommandB.next);
7883            } else {
7884                result = false;
7885            }
7886
7887            return self.callFilters('resultWillSort', result, arguments);
7888        },
7889
7890        /**
7891         * A shorthand method for `.filter('all')`. Shows all targets in the container.
7892         *
7893         * @example
7894         *
7895         * .show()
7896         *
7897         * @example <caption>Example: Showing all targets</caption>
7898         *
7899         * mixer.show()
7900         *     .then(function(state) {
7901         *         console.log(state.totalShow === state.totalTargets); // true
7902         *     });
7903         *
7904         * @public
7905         * @instance
7906         * @since       3.0.0
7907         * @return      {Promise.<mixitup.State>}
7908         */
7909
7910        show: function() {
7911            var self = this;
7912
7913            return self.filter('all');
7914        },
7915
7916        /**
7917         * A shorthand method for `.filter('none')`. Hides all targets in the container.
7918         *
7919         * @example
7920         *
7921         * .hide()
7922         *
7923         * @example <caption>Example: Hiding all targets</caption>
7924         *
7925         * mixer.hide()
7926         *     .then(function(state) {
7927         *         console.log(state.totalShow === 0); // true
7928         *         console.log(state.totalHide === state.totalTargets); // true
7929         *     });
7930         *
7931         * @public
7932         * @instance
7933         * @since       3.0.0
7934         * @return      {Promise.<mixitup.State>}
7935         */
7936
7937        hide: function() {
7938            var self = this;
7939
7940            return self.filter('none');
7941        },
7942
7943        /**
7944         * Returns a boolean indicating whether or not a MixItUp operation is
7945         * currently in progress.
7946         *
7947         * @example
7948         *
7949         * .isMixing()
7950         *
7951         * @example <caption>Example: Checking the status of a mixer</caption>
7952         *
7953         * mixer.sort('random', function() {
7954         *     console.log(mixer.isMixing()) // false
7955         * });
7956         *
7957         * console.log(mixer.isMixing()) // true
7958         *
7959         * @public
7960         * @instance
7961         * @since   2.0.0
7962         * @return  {boolean}
7963         */
7964
7965        isMixing: function() {
7966            var self = this;
7967
7968            return self.isBusy;
7969        },
7970
7971        /**
7972         * Filters all targets in the container by a provided selector string, or the values `'all'`
7973         * or `'none'`. Only targets matching the selector will be shown.
7974         *
7975         * @example
7976         *
7977         * .filter(selector [, animate] [, callback])
7978         *
7979         * @example <caption>Example 1: Filtering targets by a class selector</caption>
7980         *
7981         * mixer.filter('.category-a')
7982         *     .then(function(state) {
7983         *         console.log(state.totalShow === containerEl.querySelectorAll('.category-a').length); // true
7984         *     });
7985         *
7986         * @example <caption>Example 2: Filtering targets by an attribute selector</caption>
7987         *
7988         * mixer.filter('[data-category~="a"]')
7989         *     .then(function(state) {
7990         *         console.log(state.totalShow === containerEl.querySelectorAll('[data-category~="a"]').length); // true
7991         *     });
7992         *
7993         * @example <caption>Example 3: Filtering targets by a compound selector</caption>
7994         *
7995         * // Show only those targets with the classes 'category-a' AND 'category-b'
7996         *
7997         * mixer.filter('.category-a.category-c')
7998         *     .then(function(state) {
7999         *         console.log(state.totalShow === containerEl.querySelectorAll('.category-a.category-c').length); // true
8000         *     });
8001         *
8002         * @example <caption>Example 4: Filtering via an element collection</caption>
8003         *
8004         * var collection = Array.from(container.querySelectorAll('.mix'));
8005         *
8006         * console.log(collection.length); // 34
8007         *
8008         * // Filter the collection manually using Array.prototype.filter
8009         *
8010         * var filtered = collection.filter(function(target) {
8011         *    return parseInt(target.getAttribute('data-price')) > 10;
8012         * });
8013         *
8014         * console.log(filtered.length); // 22
8015         *
8016         * // Pass the filtered collection to MixItUp
8017         *
8018         * mixer.filter(filtered)
8019         *    .then(function(state) {
8020         *        console.log(state.activeFilter.collection.length === 22); // true
8021         *    });
8022         *
8023         * @public
8024         * @instance
8025         * @since       2.0.0
8026         * @param       {(string|HTMLElement|Array.<HTMLElement>)} selector
8027         *      Any valid CSS selector (i.e. `'.category-a'`), or the values `'all'` or `'none'`. The filter method also accepts a reference to single target element or a collection of target elements to show.
8028         * @param       {boolean}   [animate=true]
8029         *      An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default.
8030         * @param       {function}  [callback=null]
8031         *      An optional callback function to be invoked after the operation has completed.
8032         * @return      {Promise.<mixitup.State>}
8033         *      A promise resolving with the current state object.
8034         */
8035
8036        filter: function() {
8037            var self        = this,
8038                instruction = self.parseFilterArgs(arguments);
8039
8040            return self.multimix({
8041                filter: instruction.command
8042            }, instruction.animate, instruction.callback);
8043        },
8044
8045        /**
8046         * Adds an additional selector to the currently active filter selector, concatenating
8047         * as per the logic defined in `controls.toggleLogic`.
8048         *
8049         * @example
8050         *
8051         * .toggleOn(selector [, animate] [, callback])
8052         *
8053         * @example <caption>Example: Toggling on a filter selector</caption>
8054         *
8055         * console.log(mixer.getState().activeFilter.selector); // '.category-a'
8056         *
8057         * mixer.toggleOn('.category-b')
8058         *     .then(function(state) {
8059         *         console.log(state.activeFilter.selector); // '.category-a, .category-b'
8060         *     });
8061         *
8062         * @public
8063         * @instance
8064         * @since       3.0.0
8065         * @param       {string}    selector
8066         *      Any valid CSS selector (i.e. `'.category-a'`)
8067         * @param       {boolean}   [animate=true]
8068         *      An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default.
8069         * @param       {function}  [callback=null]
8070         *      An optional callback function to be invoked after the operation has completed.
8071         * @return      {Promise.<mixitup.State>}
8072         *      A promise resolving with the current state object.
8073         */
8074
8075        toggleOn: function() {
8076            var self            = this,
8077                instruction     = self.parseFilterArgs(arguments),
8078                selector        = instruction.command.selector,
8079                toggleSelector  = '';
8080
8081            self.isToggling = true;
8082
8083            if (self.toggleArray.indexOf(selector) < 0) {
8084                self.toggleArray.push(selector);
8085            }
8086
8087            toggleSelector = self.getToggleSelector();
8088
8089            return self.multimix({
8090                filter: toggleSelector
8091            }, instruction.animate, instruction.callback);
8092        },
8093
8094        /**
8095         * Removes a selector from the active filter selector.
8096         *
8097         * @example
8098         *
8099         * .toggleOff(selector [, animate] [, callback])
8100         *
8101         * @example <caption>Example: Toggling off a filter selector</caption>
8102         *
8103         * console.log(mixer.getState().activeFilter.selector); // '.category-a, .category-b'
8104         *
8105         * mixer.toggleOff('.category-b')
8106         *     .then(function(state) {
8107         *         console.log(state.activeFilter.selector); // '.category-a'
8108         *     });
8109         *
8110         * @public
8111         * @instance
8112         * @since       3.0.0
8113         * @param       {string}    selector
8114         *      Any valid CSS selector (i.e. `'.category-a'`)
8115         * @param       {boolean}   [animate=true]
8116         *      An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default.
8117         * @param       {function}  [callback=null]
8118         *      An optional callback function to be invoked after the operation has completed.
8119         * @return      {Promise.<mixitup.State>}
8120         *      A promise resolving with the current state object.
8121         */
8122
8123        toggleOff: function() {
8124            var self            = this,
8125                instruction     = self.parseFilterArgs(arguments),
8126                selector        = instruction.command.selector,
8127                selectorIndex   = self.toggleArray.indexOf(selector),
8128                toggleSelector  = '';
8129
8130            self.isToggling = true;
8131
8132            if (selectorIndex > -1) {
8133                self.toggleArray.splice(selectorIndex, 1);
8134            }
8135
8136            toggleSelector = self.getToggleSelector();
8137
8138            return self.multimix({
8139                filter: toggleSelector
8140            }, instruction.animate, instruction.callback);
8141        },
8142
8143        /**
8144         * Sorts all targets in the container according to a provided sort string.
8145         *
8146         * @example
8147         *
8148         * .sort(sortString [, animate] [, callback])
8149         *
8150         * @example <caption>Example 1: Sorting by the default DOM order</caption>
8151         *
8152         * // Reverse the default order of the targets
8153         *
8154         * mixer.sort('default:desc')
8155         *     .then(function(state) {
8156         *         console.log(state.activeSort.attribute === 'default'); // true
8157         *         console.log(state.activeSort.order === 'desc'); // true
8158         *     });
8159         *
8160         * @example <caption>Example 2: Sorting by a custom data-attribute</caption>
8161         *
8162         * // Sort the targets by the value of a `data-published-date` attribute
8163         *
8164         * mixer.sort('published-date:asc')
8165         *     .then(function(state) {
8166         *         console.log(state.activeSort.attribute === 'published-date'); // true
8167         *         console.log(state.activeSort.order === 'asc'); // true
8168         *     });
8169         *
8170         * @example <caption>Example 3: Sorting by multiple attributes</caption>
8171         *
8172         * // Sort the targets by the value of a `data-published-date` attribute, then by `data-title`
8173         *
8174         * mixer.sort('published-date:desc data-title:asc')
8175         *     .then(function(state) {
8176         *         console.log(state.activeSort.attribute === 'published-date'); // true
8177         *         console.log(state.activeSort.order === 'desc'); // true
8178         *
8179         *         console.log(state.activeSort.next.attribute === 'title'); // true
8180         *         console.log(state.activeSort.next.order === 'asc'); // true
8181         *     });
8182         *
8183         * @example <caption>Example 4: Sorting by random</caption>
8184         *
8185         * mixer.sort('random')
8186         *     .then(function(state) {
8187         *         console.log(state.activeSort.order === 'random') // true
8188         *     });
8189         *
8190         * @example <caption>Example 5: Sorting via an element collection</caption>
8191         *
8192         * var collection = Array.from(container.querySelectorAll('.mix'));
8193         *
8194         * // Swap the position of two elements in the collection:
8195         *
8196         * var temp = collection[1];
8197         *
8198         * collection[1] = collection[0];
8199         * collection[0] = temp;
8200         *
8201         * // Pass the sorted collection to MixItUp
8202         *
8203         * mixer.sort(collection)
8204         *     .then(function(state) {
8205         *         console.log(state.targets[0] === collection[0]); // true
8206         *     });
8207         *
8208         * @public
8209         * @instance
8210         * @since       2.0.0
8211         * @param       {(string|Array.<HTMLElement>)}    sortString
8212         *      A valid sort string (e.g. `'default'`, `'published-date:asc'`, or `'random'`). The sort method also accepts an array of all target elements in a user-defined order.
8213         * @param       {boolean}   [animate=true]
8214         *      An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default.
8215         * @param       {function}  [callback=null]
8216         *      An optional callback function to be invoked after the operation has completed.
8217         * @return      {Promise.<mixitup.State>}
8218         *      A promise resolving with the current state object.
8219         */
8220
8221        sort: function() {
8222            var self        = this,
8223                instruction = self.parseSortArgs(arguments);
8224
8225            return self.multimix({
8226                sort: instruction.command
8227            }, instruction.animate, instruction.callback);
8228        },
8229
8230        /**
8231         * Changes the layout of the container by adding, removing or updating a
8232         * layout-specific class name. If `animation.animateResizetargets` is
8233         * enabled, MixItUp will attempt to gracefully animate the width, height,
8234         * and position of targets between layout states.
8235         *
8236         * @example
8237         *
8238         * .changeLayout(containerClassName [, animate] [, callback])
8239         *
8240         * @example <caption>Example 1: Adding a new class name to the container</caption>
8241         *
8242         * mixer.changeLayout('container-list')
8243         *      .then(function(state) {
8244         *          console.log(state.activeContainerClass === 'container-list'); // true
8245         *      });
8246         *
8247         * @example <caption>Example 2: Removing a previously added class name from the container</caption>
8248         *
8249         * mixer.changeLayout('')
8250         *      .then(function(state) {
8251         *          console.log(state.activeContainerClass === ''); // true
8252         *      });
8253         *
8254         * @public
8255         * @instance
8256         * @since       2.0.0
8257         * @param       {string}    containerClassName
8258         *      A layout-specific class name to add to the container.
8259         * @param       {boolean}   [animate=true]
8260         *      An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default.
8261         * @param       {function}  [callback=null]
8262         *      An optional callback function to be invoked after the operation has completed.
8263         * @return      {Promise.<mixitup.State>}
8264         *      A promise resolving with the current state object.
8265         */
8266
8267        changeLayout: function() {
8268            var self        = this,
8269                instruction = self.parseChangeLayoutArgs(arguments);
8270
8271            return self.multimix({
8272                changeLayout: instruction.command
8273            }, instruction.animate, instruction.callback);
8274        },
8275
8276        /**
8277         * Updates the contents and order of the container to reflect the provided dataset,
8278         * if the dataset API is in use.
8279         *
8280         * The dataset API is designed for use in API-driven JavaScript applications, and
8281         * can be used instead of DOM-based methods such as `.filter()`, `.sort()`,
8282         * `.insert()`, etc. When used, insertion, removal, sorting and pagination can be
8283         * achieved purely via changes to your data model, without the uglyness of having
8284         * to interact with or query the DOM directly.
8285         *
8286         * @example
8287         *
8288         * .dataset(dataset [, animate] [, callback])
8289         *
8290         * @example <caption>Example 1: Rendering a dataset</caption>
8291         *
8292         * var myDataset = [
8293         *     {id: 1, ...},
8294         *     {id: 2, ...},
8295         *     {id: 3, ...}
8296         * ];
8297         *
8298         * mixer.dataset(myDataset)
8299         *     .then(function(state) {
8300         *         console.log(state.totalShow === 3); // true
8301         *     });
8302         *
8303         * @example <caption>Example 2: Sorting a dataset</caption>
8304         *
8305         * // Create a new dataset in reverse order
8306         *
8307         * var newDataset = myDataset.slice().reverse();
8308         *
8309         * mixer.dataset(newDataset)
8310         *     .then(function(state) {
8311         *         console.log(state.activeDataset[0] === myDataset[2]); // true
8312         *     });
8313         *
8314         * @example <caption>Example 3: Removing an item from the dataset</caption>
8315         *
8316         * console.log(myDataset.length); // 3
8317         *
8318         * // Create a new dataset with the last item removed.
8319         *
8320         * var newDataset = myDataset.slice().pop();
8321         *
8322         * mixer.dataset(newDataset)
8323         *     .then(function(state) {
8324         *         console.log(state.totalShow === 2); // true
8325         *     });
8326         *
8327         * @public
8328         * @instance
8329         * @since       3.0.0
8330         * @param       {Array.<object>}    dataset
8331         *      An array of objects, each one representing the underlying data model of a target to be rendered.
8332         * @param       {boolean}           [animate=true]
8333         *      An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default.
8334         * @param       {function}          [callback=null]
8335         *      An optional callback function to be invoked after the operation has completed.
8336         * @return      {Promise.<mixitup.State>}
8337         *      A promise resolving with the current state object.
8338         */
8339
8340        dataset: function() {
8341            var self        = this,
8342                instruction = self.parseDatasetArgs(arguments),
8343                operation   = null,
8344                queueItem   = null,
8345                animate     = false;
8346
8347            self.callActions('beforeDataset', arguments);
8348
8349            if (!self.isBusy) {
8350                if (instruction.callback) self.userCallback = instruction.callback;
8351
8352                animate = (instruction.animate ^ self.config.animation.enable) ? instruction.animate : self.config.animation.enable;
8353
8354                operation = self.getDataOperation(instruction.command.dataset);
8355
8356                return self.goMix(animate, operation);
8357            } else {
8358                queueItem = new mixitup.QueueItem();
8359
8360                queueItem.args          = arguments;
8361                queueItem.instruction   = instruction;
8362
8363                return self.queueMix(queueItem);
8364            }
8365        },
8366
8367        /**
8368         * Performs simultaneous `filter`, `sort`, `insert`, `remove` and `changeLayout`
8369         * operations as requested.
8370         *
8371         * @example
8372         *
8373         * .multimix(multimixCommand [, animate] [, callback])
8374         *
8375         * @example <caption>Example 1: Performing simultaneous filtering and sorting</caption>
8376         *
8377         * mixer.multimix({
8378         *     filter: '.category-b',
8379         *     sort: 'published-date:desc'
8380         * })
8381         *     .then(function(state) {
8382         *         console.log(state.activeFilter.selector === '.category-b'); // true
8383         *         console.log(state.activeSort.attribute === 'published-date'); // true
8384         *     });
8385         *
8386         * @example <caption>Example 2: Performing simultaneous sorting, insertion, and removal</caption>
8387         *
8388         * console.log(mixer.getState().totalShow); // 6
8389         *
8390         * // NB: When inserting via `multimix()`, an object should be provided as the value
8391         * // for the `insert` portion of the command, allowing for a collection of elements
8392         * // and an insertion index to be specified.
8393         *
8394         * mixer.multimix({
8395         *     sort: 'published-date:desc', // Sort the container, including any new elements
8396         *     insert: {
8397         *         collection: [newElementReferenceA, newElementReferenceB], // Add 2 new elements at index 5
8398         *         index: 5
8399         *     },
8400         *     remove: existingElementReference // Remove 1 existing element
8401         * })
8402         *     .then(function(state) {
8403         *         console.log(state.activeSort.attribute === 'published-date'); // true
8404         *         console.log(state.totalShow === 7); // true
8405         *     });
8406         *
8407         * @public
8408         * @instance
8409         * @since       2.0.0
8410         * @param       {object}    multimixCommand
8411         *      An object containing one or more things to do
8412         * @param       {boolean}   [animate=true]
8413         *      An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default.
8414         * @param       {function}  [callback=null]
8415         *      An optional callback function to be invoked after the operation has completed.
8416         * @return      {Promise.<mixitup.State>}
8417         *      A promise resolving with the current state object.
8418         */
8419
8420        multimix: function() {
8421            var self        = this,
8422                operation   = null,
8423                animate     = false,
8424                queueItem   = null,
8425                instruction = self.parseMultimixArgs(arguments);
8426
8427            self.callActions('beforeMultimix', arguments);
8428
8429            if (!self.isBusy) {
8430                operation = self.getOperation(instruction.command);
8431
8432                if (self.config.controls.enable) {
8433                    // Update controls for API calls
8434
8435                    if (instruction.command.filter && !self.isToggling) {
8436                        // As we are not toggling, reset the toggle array
8437                        // so new filter overrides existing toggles
8438
8439                        self.toggleArray.length = 0;
8440                        self.buildToggleArray(operation.command);
8441                    }
8442
8443                    if (self.queue.length < 1) {
8444                        self.updateControls(operation.command);
8445                    }
8446                }
8447
8448                if (instruction.callback) self.userCallback = instruction.callback;
8449
8450                // Always allow the instruction to override the instance setting
8451
8452                animate = (instruction.animate ^ self.config.animation.enable) ?
8453                    instruction.animate :
8454                    self.config.animation.enable;
8455
8456                self.callFilters('operationMultimix', operation, arguments);
8457
8458                return self.goMix(animate, operation);
8459            } else {
8460                queueItem = new mixitup.QueueItem();
8461
8462                queueItem.args           = arguments;
8463                queueItem.instruction    = instruction;
8464                queueItem.triggerElement = self.lastClicked;
8465                queueItem.isToggling     = self.isToggling;
8466
8467                return self.queueMix(queueItem);
8468            }
8469        },
8470
8471        /**
8472         * @private
8473         * @instance
8474         * @since   3.0.0
8475         * @param   {object}            multimixCommand
8476         * @param   {boolean}           [isPreFetch]
8477         *      An optional boolean indicating that the operation is being pre-fetched for execution at a later time.
8478         * @return  {Operation|null}
8479         */
8480
8481        getOperation: function(multimixCommand) {
8482            var self                = this,
8483                sortCommand         = multimixCommand.sort,
8484                filterCommand       = multimixCommand.filter,
8485                changeLayoutCommand = multimixCommand.changeLayout,
8486                removeCommand       = multimixCommand.remove,
8487                insertCommand       = multimixCommand.insert,
8488                operation           = new mixitup.Operation();
8489
8490            operation = self.callFilters('operationUnmappedGetOperation', operation, arguments);
8491
8492            operation.id                = h.randomHex();
8493            operation.command           = multimixCommand;
8494            operation.startState        = self.state;
8495            operation.triggerElement    = self.lastClicked;
8496
8497            if (self.isBusy) {
8498                if (self.config.debug.showWarnings) {
8499                    console.warn(mixitup.messages.warningGetOperationInstanceBusy());
8500                }
8501
8502                return null;
8503            }
8504
8505            if (insertCommand) {
8506                self.insertTargets(insertCommand, operation);
8507            }
8508
8509            if (removeCommand) {
8510                operation.toRemove = removeCommand.targets;
8511            }
8512
8513            operation.startSort = operation.newSort = operation.startState.activeSort;
8514            operation.startOrder = operation.newOrder = self.targets;
8515
8516            if (sortCommand) {
8517                operation.startSort = operation.startState.activeSort;
8518                operation.newSort   = sortCommand;
8519
8520                operation.willSort = self.willSort(sortCommand, operation.startState.activeSort);
8521
8522                if (operation.willSort) {
8523                    self.sortOperation(operation);
8524                }
8525            }
8526
8527            operation.startFilter = operation.startState.activeFilter;
8528
8529            if (filterCommand) {
8530                operation.newFilter = filterCommand;
8531            } else {
8532                operation.newFilter = h.extend(new mixitup.CommandFilter(), operation.startFilter);
8533            }
8534
8535            if (operation.newFilter.selector === 'all') {
8536                operation.newFilter.selector = self.config.selectors.target;
8537            } else if (operation.newFilter.selector === 'none') {
8538                operation.newFilter.selector = '';
8539            }
8540
8541            self.filterOperation(operation);
8542
8543            operation.startContainerClassName = operation.startState.activeContainerClassName;
8544
8545            if (changeLayoutCommand) {
8546                operation.newContainerClassName = changeLayoutCommand.containerClassName;
8547
8548                if (operation.newContainerClassName !== operation.startContainerClassName) {
8549                    operation.willChangeLayout = true;
8550                }
8551            } else {
8552                operation.newContainerClassName = operation.startContainerClassName;
8553            }
8554
8555            if (self.config.animation.enable) {
8556                // Populate the operation's position data
8557
8558                self.getStartMixData(operation);
8559                self.setInter(operation);
8560
8561                operation.docState = h.getDocumentState(self.dom.document);
8562
8563                self.getInterMixData(operation);
8564                self.setFinal(operation);
8565                self.getFinalMixData(operation);
8566
8567                self.parseEffects();
8568
8569                operation.hasEffect = self.hasEffect();
8570
8571                self.getTweenData(operation);
8572            }
8573
8574            if (operation.willSort) {
8575                self.targets = operation.newOrder;
8576            }
8577
8578            operation.newState = self.buildState(operation);
8579
8580            return self.callFilters('operationMappedGetOperation', operation, arguments);
8581        },
8582
8583        /**
8584         * Renders a previously created operation at a specific point in its path, as
8585         * determined by a multiplier between 0 and 1.
8586         *
8587         * @example
8588         * .tween(operation, multiplier)
8589         *
8590         * @private
8591         * @instance
8592         * @since   3.0.0
8593         * @param   {mixitup.Operation}     operation
8594         *      An operation object created via the `getOperation` method
8595         *
8596         * @param   {Float}                 multiplier
8597         *      Any number between 0 and 1 representing the percentage complete of the operation
8598         * @return  {void}
8599         */
8600
8601        tween: function(operation, multiplier) {
8602            var target          = null,
8603                posData         = null,
8604                toHideIndex     = -1,
8605                i               = -1;
8606
8607            multiplier = Math.min(multiplier, 1);
8608            multiplier = Math.max(multiplier, 0);
8609
8610            for (i = 0; target = operation.show[i]; i++) {
8611                posData = operation.showPosData[i];
8612
8613                target.applyTween(posData, multiplier);
8614            }
8615
8616            for (i = 0; target = operation.hide[i]; i++) {
8617                if (target.isShown) {
8618                    target.hide();
8619                }
8620
8621                if ((toHideIndex = operation.toHide.indexOf(target)) > -1) {
8622                    posData = operation.toHidePosData[toHideIndex];
8623
8624                    if (!target.isShown) {
8625                        target.show();
8626                    }
8627
8628                    target.applyTween(posData, multiplier);
8629                }
8630            }
8631        },
8632
8633        /**
8634         * Inserts one or more new target elements into the container at a specified
8635         * index.
8636         *
8637         * To be indexed as targets, new elements must match the `selectors.target`
8638         * selector (`'.mix'` by default).
8639         *
8640         * @example
8641         *
8642         * .insert(newElements [, index] [, animate], [, callback])
8643         *
8644         * @example <caption>Example 1: Inserting a single element via reference</caption>
8645         *
8646         * console.log(mixer.getState().totalShow); // 0
8647         *
8648         * // Create a new element
8649         *
8650         * var newElement = document.createElement('div');
8651         * newElement.classList.add('mix');
8652         *
8653         * mixer.insert(newElement)
8654         *     .then(function(state) {
8655         *         console.log(state.totalShow === 1); // true
8656         *     });
8657         *
8658         * @example <caption>Example 2: Inserting a single element via HTML string</caption>
8659         *
8660         * console.log(mixer.getState().totalShow); // 1
8661         *
8662         * // Create a new element via reference
8663         *
8664         * var newElementHtml = '&lt;div class="mix"&gt;&lt;/div&gt;';
8665         *
8666         * // Create and insert the new element at index 1
8667         *
8668         * mixer.insert(newElementHtml, 1)
8669         *     .then(function(state) {
8670         *         console.log(state.totalShow === 2); // true
8671         *         console.log(state.show[1].outerHTML === newElementHtml); // true
8672         *     });
8673         *
8674         * @example <caption>Example 3: Inserting multiple elements via reference</caption>
8675         *
8676         * console.log(mixer.getState().totalShow); // 2
8677         *
8678         * // Create an array of new elements to insert.
8679         *
8680         * var newElement1 = document.createElement('div');
8681         * var newElement2 = document.createElement('div');
8682         *
8683         * newElement1.classList.add('mix');
8684         * newElement2.classList.add('mix');
8685         *
8686         * var newElementsCollection = [newElement1, newElement2];
8687         *
8688         * // Insert the new elements starting at index 1
8689         *
8690         * mixer.insert(newElementsCollection, 1)
8691         *     .then(function(state) {
8692         *         console.log(state.totalShow === 4); // true
8693         *         console.log(state.show[1] === newElement1); // true
8694         *         console.log(state.show[2] === newElement2); // true
8695         *     });
8696         *
8697         * @example <caption>Example 4: Inserting a jQuery collection object containing one or more elements</caption>
8698         *
8699         * console.log(mixer.getState().totalShow); // 4
8700         *
8701         * var $newElement = $('&lt;div class="mix"&gt;&lt;/div&gt;');
8702         *
8703         * // Insert the new elements starting at index 3
8704         *
8705         * mixer.insert($newElement, 3)
8706         *     .then(function(state) {
8707         *         console.log(state.totalShow === 5); // true
8708         *         console.log(state.show[3] === $newElement[0]); // true
8709         *     });
8710         *
8711         * @public
8712         * @instance
8713         * @since       2.0.0
8714         * @param       {(HTMLElement|Array.<HTMLElement>|string)}    newElements
8715         *      A reference to a single element to insert, an array-like collection of elements, or an HTML string representing a single element.
8716         * @param       {number}    index=0
8717         *      The index at which to insert the new element(s). `0` by default.
8718         * @param       {boolean}   [animate=true]
8719         *      An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default.
8720         * @param       {function}  [callback=null]
8721         *      An optional callback function to be invoked after the operation has completed.
8722         * @return      {Promise.<mixitup.State>}
8723         *      A promise resolving with the current state object.
8724         */
8725
8726        insert: function() {
8727            var self = this,
8728                args = self.parseInsertArgs(arguments);
8729
8730            return self.multimix({
8731                insert: args.command
8732            }, args.animate, args.callback);
8733        },
8734
8735        /**
8736         * Inserts one or more new elements before a provided reference element.
8737         *
8738         * @example
8739         *
8740         * .insertBefore(newElements, referenceElement [, animate] [, callback])
8741         *
8742         * @example <caption>Example: Inserting a new element before a reference element</caption>
8743         *
8744         * // An existing reference element is chosen at index 2
8745         *
8746         * var referenceElement = mixer.getState().show[2];
8747         *
8748         * // Create a new element
8749         *
8750         * var newElement = document.createElement('div');
8751         * newElement.classList.add('mix');
8752         *
8753         * mixer.insertBefore(newElement, referenceElement)
8754         *     .then(function(state) {
8755         *         // The new element is inserted into the container at index 2, before the reference element
8756         *
8757         *         console.log(state.show[2] === newElement); // true
8758         *
8759         *         // The reference element is now at index 3
8760         *
8761         *         console.log(state.show[3] === referenceElement); // true
8762         *     });
8763         *
8764         * @public
8765         * @instance
8766         * @since       3.0.0
8767         * @param       {(HTMLElement|Array.<HTMLElement>|string)}    newElements
8768         *      A reference to a single element to insert, an array-like collection of elements, or an HTML string representing a single element.
8769         * @param       {HTMLElement}    referenceElement
8770         *      A reference to an existing element in the container to insert new elements before.
8771         *@param       {boolean}   [animate=true]
8772         *      An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default.
8773         * @param       {function}  [callback=null]
8774         *      An optional callback function to be invoked after the operation has completed.
8775         * @return      {Promise.<mixitup.State>}
8776         *      A promise resolving with the current state object.
8777         */
8778
8779        insertBefore: function() {
8780            var self = this,
8781                args = self.parseInsertArgs(arguments);
8782
8783            return self.insert(args.command.collection, 'before', args.command.sibling, args.animate, args.callback);
8784        },
8785
8786        /**
8787         * Inserts one or more new elements after a provided reference element.
8788         *
8789         * @example
8790         *
8791         * .insertAfter(newElements, referenceElement [, animate] [, callback])
8792         *
8793         * @example <caption>Example: Inserting a new element after a reference element</caption>
8794         *
8795         * // An existing reference element is chosen at index 2
8796         *
8797         * var referenceElement = mixer.getState().show[2];
8798         *
8799         * // Create a new element
8800         *
8801         * var newElement = document.createElement('div');
8802         * newElement.classList.add('mix');
8803         *
8804         * mixer.insertAfter(newElement, referenceElement)
8805         *     .then(function(state) {
8806         *         // The new element is inserted into the container at index 3, after the reference element
8807         *
8808         *         console.log(state.show[3] === newElement); // true
8809         *     });
8810         *
8811         * @public
8812         * @instance
8813         * @since       3.0.0
8814         * @param       {(HTMLElement|Array.<HTMLElement>|string)}    newElements
8815         *      A reference to a single element to insert, an array-like collection of elements, or an HTML string representing a single element.
8816         * @param       {HTMLElement}    referenceElement
8817         *      A reference to an existing element in the container to insert new elements after.
8818         * @param       {boolean}   [animate=true]
8819         *      An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default.
8820         * @param       {function}  [callback=null]
8821         *      An optional callback function to be invoked after the operation has completed.
8822         * @return      {Promise.<mixitup.State>}
8823         *      A promise resolving with the current state object.
8824         */
8825
8826        insertAfter: function() {
8827            var self = this,
8828                args = self.parseInsertArgs(arguments);
8829
8830            return self.insert(args.command.collection, 'after', args.command.sibling, args.animate, args.callback);
8831        },
8832
8833        /**
8834         * Inserts one or more new elements into the container before all existing targets.
8835         *
8836         * @example
8837         *
8838         * .prepend(newElements [,animate] [,callback])
8839         *
8840         * @example <caption>Example: Prepending a new element</caption>
8841         *
8842         * // Create a new element
8843         *
8844         * var newElement = document.createElement('div');
8845         * newElement.classList.add('mix');
8846         *
8847         * // Insert the element into the container
8848         *
8849         * mixer.prepend(newElement)
8850         *     .then(function(state) {
8851         *         console.log(state.show[0] === newElement); // true
8852         *     });
8853         *
8854         * @public
8855         * @instance
8856         * @since       3.0.0
8857         * @param       {(HTMLElement|Array.<HTMLElement>|string)}    newElements
8858         *      A reference to a single element to insert, an array-like collection of elements, or an HTML string representing a single element.
8859         * @param       {boolean}   [animate=true]
8860         *      An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default.
8861         * @param       {function}  [callback=null]
8862         *      An optional callback function to be invoked after the operation has completed.
8863         * @return      {Promise.<mixitup.State>}
8864         *      A promise resolving with the current state object.
8865         */
8866
8867        prepend: function() {
8868            var self = this,
8869                args = self.parseInsertArgs(arguments);
8870
8871            return self.insert(0, args.command.collection, args.animate, args.callback);
8872        },
8873
8874        /**
8875         * Inserts one or more new elements into the container after all existing targets.
8876         *
8877         * @example
8878         *
8879         * .append(newElements [,animate] [,callback])
8880         *
8881         * @example <caption>Example: Appending a new element</caption>
8882         *
8883         * // Create a new element
8884         *
8885         * var newElement = document.createElement('div');
8886         * newElement.classList.add('mix');
8887         *
8888         * // Insert the element into the container
8889         *
8890         * mixer.append(newElement)
8891         *     .then(function(state) {
8892         *         console.log(state.show[state.show.length - 1] === newElement); // true
8893         *     });
8894         *
8895         * @public
8896         * @instance
8897         * @since       3.0.0
8898         * @param       {(HTMLElement|Array.<HTMLElement>|string)}    newElements
8899         *      A reference to a single element to insert, an array-like collection of elements, or an HTML string representing a single element.
8900         * @param       {boolean}   [animate=true]
8901         *      An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default.
8902         * @param       {function}  [callback=null]
8903         *      An optional callback function to be invoked after the operation has completed.
8904         * @return      {Promise.<mixitup.State>}
8905         *      A promise resolving with the current state object.
8906         */
8907
8908        append: function() {
8909            var self = this,
8910                args = self.parseInsertArgs(arguments);
8911
8912            return self.insert(self.state.totalTargets, args.command.collection, args.animate, args.callback);
8913        },
8914
8915        /**
8916         * Removes one or more existing target elements from the container.
8917         *
8918         * @example
8919         *
8920         * .remove(elements [, animate] [, callback])
8921         *
8922         * @example <caption>Example 1: Removing an element by reference</caption>
8923         *
8924         * var elementToRemove = containerEl.firstElementChild;
8925         *
8926         * mixer.remove(elementToRemove)
8927         *      .then(function(state) {
8928         *          console.log(state.targets.indexOf(elementToRemove) === -1); // true
8929         *      });
8930         *
8931         * @example <caption>Example 2: Removing a collection of elements by reference</caption>
8932         *
8933         * var elementsToRemove = containerEl.querySelectorAll('.category-a');
8934         *
8935         * console.log(elementsToRemove.length) // 3
8936         *
8937         * mixer.remove(elementsToRemove)
8938         *      .then(function() {
8939         *          console.log(containerEl.querySelectorAll('.category-a').length); // 0
8940         *      });
8941         *
8942         * @example <caption>Example 3: Removing one or more elements by selector</caption>
8943         *
8944         * mixer.remove('.category-a')
8945         *      .then(function() {
8946         *          console.log(containerEl.querySelectorAll('.category-a').length); // 0
8947         *      });
8948         *
8949         * @example <caption>Example 4: Removing an element by index</caption>
8950         *
8951         * console.log(mixer.getState.totalShow); // 4
8952         *
8953         * // Remove the element at index 3
8954         *
8955         * mixer.remove(3)
8956         *      .then(function(state) {
8957         *          console.log(state.totalShow); // 3
8958         *          console.log(state.show[3]); // undefined
8959         *      });
8960         *
8961         *
8962         * @public
8963         * @instance
8964         * @since       3.0.0
8965         * @param       {(HTMLElement|Array.<HTMLElement>|string|number)}    elements
8966         *      A reference to a single element to remove, an array-like collection of elements, a selector string, or the index of an element to remove.
8967         * @param       {boolean}   [animate=true]
8968         *      An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default.
8969         * @param       {function}  [callback=null]
8970         *      An optional callback function to be invoked after the operation has completed.
8971         * @return      {Promise.<mixitup.State>}
8972         *      A promise resolving with the current state object.
8973         */
8974
8975        remove: function() {
8976            var self = this,
8977                args = self.parseRemoveArgs(arguments);
8978
8979            return self.multimix({
8980                remove: args.command
8981            }, args.animate, args.callback);
8982        },
8983
8984        /**
8985         * Retrieves the the value of any property or sub-object within the current
8986         * mixitup configuration, or the whole configuration object.
8987         *
8988         * @example
8989         *
8990         * .getConfig([stringKey])
8991         *
8992         * @example <caption>Example 1: retrieve the entire configuration object</caption>
8993         *
8994         * var config = mixer.getConfig(); // Config { ... }
8995         *
8996         * @example <caption>Example 2: retrieve a named sub-object of configuration object</caption>
8997         *
8998         * var animation = mixer.getConfig('animation'); // ConfigAnimation { ... }
8999         *
9000         * @example <caption>Example 3: retrieve a value of configuration object via a dot-notation string key</caption>
9001         *
9002         * var effects = mixer.getConfig('animation.effects'); // 'fade scale'
9003         *
9004         * @public
9005         * @instance
9006         * @since       2.0.0
9007         * @param       {string}    [stringKey]    A "dot-notation" string key
9008         * @return      {*}
9009         */
9010
9011        getConfig: function(stringKey) {
9012            var self    = this,
9013                value   = null;
9014
9015            if (!stringKey) {
9016                value = self.config;
9017            } else {
9018                value = h.getProperty(self.config, stringKey);
9019            }
9020
9021            return self.callFilters('valueGetConfig', value, arguments);
9022        },
9023
9024        /**
9025         * Updates the configuration of the mixer, after it has been instantiated.
9026         *
9027         * See the Configuration Object documentation for a full list of avilable
9028         * configuration options.
9029         *
9030         * @example
9031         *
9032         * .configure(config)
9033         *
9034         * @example <caption>Example 1: Updating animation options</caption>
9035         *
9036         * mixer.configure({
9037         *     animation: {
9038         *         effects: 'fade translateX(-100%)',
9039         *         duration: 300
9040         *     }
9041         * });
9042         *
9043         * @example <caption>Example 2: Removing a callback after it has been set</caption>
9044         *
9045         * var mixer;
9046         *
9047         * function handleMixEndOnce() {
9048         *     // Do something ..
9049         *
9050         *     // Then nullify the callback
9051         *
9052         *     mixer.configure({
9053         *         callbacks: {
9054         *             onMixEnd: null
9055         *         }
9056         *     });
9057         * };
9058         *
9059         * // Instantiate a mixer with a callback defined
9060         *
9061         * mixer = mixitup(containerEl, {
9062         *     callbacks: {
9063         *         onMixEnd: handleMixEndOnce
9064         *     }
9065         * });
9066         *
9067         * @public
9068         * @instance
9069         * @since       3.0.0
9070         * @param       {object}    config
9071         *      An object containing one of more configuration options.
9072         * @return      {void}
9073         */
9074
9075        configure: function(config) {
9076            var self = this;
9077
9078            self.callActions('beforeConfigure', arguments);
9079
9080            h.extend(self.config, config, true, true);
9081
9082            self.callActions('afterConfigure', arguments);
9083        },
9084
9085        /**
9086         * Returns an object containing information about the current state of the
9087         * mixer. See the State Object documentation for more information.
9088         *
9089         * NB: State objects are immutable and should therefore be regenerated
9090         * after any operation.
9091         *
9092         * @example
9093         *
9094         * .getState();
9095         *
9096         * @example <caption>Example: Retrieving a state object</caption>
9097         *
9098         * var state = mixer.getState();
9099         *
9100         * console.log(state.totalShow + 'targets are currently shown');
9101         *
9102         * @public
9103         * @instance
9104         * @since       2.0.0
9105         * @return      {mixitup.State} An object reflecting the current state of the mixer.
9106         */
9107
9108        getState: function() {
9109            var self    = this,
9110                state   = null;
9111
9112            state = new mixitup.State();
9113
9114            h.extend(state, self.state);
9115
9116            h.freeze(state);
9117
9118            return self.callFilters('stateGetState', state, arguments);
9119        },
9120
9121        /**
9122         * Forces the re-indexing all targets within the container.
9123         *
9124         * This should only be used if some other piece of code in your application
9125         * has manipulated the contents of your container, which should be avoided.
9126         *
9127         * If you need to add or remove target elements from the container, use
9128         * the built-in `.insert()` or `.remove()` methods, and MixItUp will keep
9129         * itself up to date.
9130         *
9131         * @example
9132         *
9133         * .forceRefresh()
9134         *
9135         * @example <caption>Example: Force refreshing the mixer after external DOM manipulation</caption>
9136         *
9137         * console.log(mixer.getState().totalShow); // 3
9138         *
9139         * // An element is removed from the container via some external DOM manipulation code:
9140         *
9141         * containerEl.removeChild(containerEl.firstElementChild);
9142         *
9143         * // The mixer does not know that the number of targets has changed:
9144         *
9145         * console.log(mixer.getState().totalShow); // 3
9146         *
9147         * mixer.forceRefresh();
9148         *
9149         * // After forceRefresh, the mixer is in sync again:
9150         *
9151         * console.log(mixer.getState().totalShow); // 2
9152         *
9153         * @public
9154         * @instance
9155         * @since 2.1.2
9156         * @return {void}
9157         */
9158
9159        forceRefresh: function() {
9160            var self = this;
9161
9162            self.indexTargets();
9163        },
9164
9165        /**
9166         * Forces the re-rendering of all targets when using the Dataset API.
9167         *
9168         * By default, targets are only re-rendered when `data.dirtyCheck` is
9169         * enabled, and an item's data has changed when `dataset()` is called.
9170         *
9171         * The `forceRender()` method allows for the re-rendering of all targets
9172         * in response to some arbitrary event, such as the changing of the target
9173         * render function.
9174         *
9175         * Targets are rendered against their existing data.
9176         *
9177         * @example
9178         *
9179         * .forceRender()
9180         *
9181         * @example <caption>Example: Force render targets after changing the target render function</caption>
9182         *
9183         * console.log(container.innerHTML); // ... &lt;span class="mix"&gt;Foo&lt;/span&gt; ...
9184         *
9185         * mixer.configure({
9186         *     render: {
9187         *         target: (item) => `&lt;a href="/${item.slug}/" class="mix"&gt;${item.title}&lt;/a&gt;`
9188         *     }
9189         * });
9190         *
9191         * mixer.forceRender();
9192         *
9193         * console.log(container.innerHTML); // ... &lt;a href="/foo/" class="mix"&gt;Foo&lt;/a&gt; ...
9194         *
9195         * @public
9196         * @instance
9197         * @since 3.2.1
9198         * @return {void}
9199         */
9200
9201        forceRender: function() {
9202            var self    = this,
9203                target  = null,
9204                el      = null,
9205                id      = '';
9206
9207            for (id in self.cache) {
9208                target = self.cache[id];
9209
9210                el = target.render(target.data);
9211
9212                if (el !== target.dom.el) {
9213                    // Update target element reference
9214
9215                    if (target.isInDom) {
9216                        target.unbindEvents();
9217
9218                        self.dom.parent.replaceChild(el, target.dom.el);
9219                    }
9220
9221                    if (!target.isShown) {
9222                        el.style.display = 'none';
9223                    }
9224
9225                    target.dom.el = el;
9226
9227                    if (target.isInDom) {
9228                        target.bindEvents();
9229                    }
9230                }
9231            }
9232
9233            self.state = self.buildState(self.lastOperation);
9234        },
9235
9236        /**
9237         * Removes mixitup functionality from the container, unbinds all control
9238         * event handlers, and deletes the mixer instance from MixItUp's internal
9239         * cache.
9240         *
9241         * This should be performed whenever a mixer's container is removed from
9242         * the DOM, such as during a page change in a single page application,
9243         * or React's `componentWillUnmount()`.
9244         *
9245         * @example
9246         *
9247         * .destroy([cleanUp])
9248         *
9249         * @example <caption>Example: Destroying the mixer before removing its container element</caption>
9250         *
9251         * mixer.destroy();
9252         *
9253         * containerEl.parentElement.removeChild(containerEl);
9254         *
9255         * @public
9256         * @instance
9257         * @since   2.0.0
9258         * @param   {boolean}   [cleanUp=false]
9259         *     An optional boolean dictating whether or not to clean up any inline `display: none;` styling applied to hidden targets.
9260         * @return  {void}
9261         */
9262
9263        destroy: function(cleanUp) {
9264            var self    = this,
9265                control = null,
9266                target  = null,
9267                i       = 0;
9268
9269            self.callActions('beforeDestroy', arguments);
9270
9271            for (i = 0; control = self.controls[i]; i++) {
9272                control.removeBinding(self);
9273            }
9274
9275            for (i = 0; target = self.targets[i]; i++) {
9276                if (cleanUp) {
9277                    target.show();
9278                }
9279
9280                target.unbindEvents();
9281            }
9282
9283            if (self.dom.container.id.match(/^MixItUp/)) {
9284                self.dom.container.removeAttribute('id');
9285            }
9286
9287            delete mixitup.instances[self.id];
9288
9289            self.callActions('afterDestroy', arguments);
9290        }
9291    });
9292
9293    /**
9294     * @constructor
9295     * @memberof    mixitup
9296     * @private
9297     * @since       3.0.0
9298     */
9299
9300    mixitup.IMoveData = function() {
9301        mixitup.Base.call(this);
9302
9303        this.callActions('beforeConstruct');
9304
9305        this.posIn          = null;
9306        this.posOut         = null;
9307        this.operation      = null;
9308        this.callback       = null;
9309        this.statusChange   = '';
9310        this.duration       = -1;
9311        this.staggerIndex   = -1;
9312
9313        this.callActions('afterConstruct');
9314
9315        h.seal(this);
9316    };
9317
9318    mixitup.BaseStatic.call(mixitup.IMoveData);
9319
9320    mixitup.IMoveData.prototype = Object.create(mixitup.Base.prototype);
9321
9322    mixitup.IMoveData.prototype.constructor = mixitup.IMoveData;
9323
9324    /**
9325     * @constructor
9326     * @memberof    mixitup
9327     * @private
9328     * @since       3.0.0
9329     */
9330
9331    mixitup.TargetDom = function() {
9332        mixitup.Base.call(this);
9333
9334        this.callActions('beforeConstruct');
9335
9336        this.el = null;
9337
9338        this.callActions('afterConstruct');
9339
9340        h.seal(this);
9341    };
9342
9343    mixitup.BaseStatic.call(mixitup.TargetDom);
9344
9345    mixitup.TargetDom.prototype = Object.create(mixitup.Base.prototype);
9346
9347    mixitup.TargetDom.prototype.constructor = mixitup.TargetDom;
9348
9349    /**
9350     * @constructor
9351     * @namespace
9352     * @memberof    mixitup
9353     * @private
9354     * @since       3.0.0
9355     */
9356
9357    mixitup.Target = function() {
9358        mixitup.Base.call(this);
9359
9360        this.callActions('beforeConstruct');
9361
9362        this.id         = '';
9363        this.sortString = '';
9364        this.mixer      = null;
9365        this.callback   = null;
9366        this.isShown    = false;
9367        this.isBound    = false;
9368        this.isExcluded = false;
9369        this.isInDom    = false;
9370        this.handler    = null;
9371        this.operation  = null;
9372        this.data       = null;
9373        this.dom        = new mixitup.TargetDom();
9374
9375        this.callActions('afterConstruct');
9376
9377        h.seal(this);
9378    };
9379
9380    mixitup.BaseStatic.call(mixitup.Target);
9381
9382    mixitup.Target.prototype = Object.create(mixitup.Base.prototype);
9383
9384    h.extend(mixitup.Target.prototype, {
9385        constructor: mixitup.Target,
9386
9387        /**
9388         * Initialises a newly instantiated Target.
9389         *
9390         * @private
9391         * @instance
9392         * @since   3.0.0
9393         * @param   {(Element|null)}    el
9394         * @param   {object}            mixer
9395         * @param   {object}            [data]
9396         * @return  {void}
9397         */
9398
9399        init: function(el, mixer, data) {
9400            var self = this,
9401                id   = '';
9402
9403            self.callActions('beforeInit', arguments);
9404
9405            self.mixer = mixer;
9406
9407            if (!el) {
9408                // If no element is provided, render it
9409
9410                el = self.render(data);
9411            }
9412
9413            self.cacheDom(el);
9414
9415            self.bindEvents();
9416
9417            if (self.dom.el.style.display !== 'none') {
9418                self.isShown = true;
9419            }
9420
9421            if (data && mixer.config.data.uidKey) {
9422                if (typeof (id = data[mixer.config.data.uidKey]) === 'undefined' || id.toString().length < 1) {
9423                    throw new TypeError(mixitup.messages.errorDatasetInvalidUidKey({
9424                        uidKey: mixer.config.data.uidKey
9425                    }));
9426                }
9427
9428                self.id     = id;
9429                self.data   = data;
9430
9431                mixer.cache[id] = self;
9432            }
9433
9434            self.callActions('afterInit', arguments);
9435        },
9436
9437        /**
9438         * Renders the target element using a user-defined renderer function.
9439         *
9440         * @private
9441         * @instance
9442         * @since   3.1.4
9443         * @param   {object} data
9444         * @return  {void}
9445         */
9446
9447        render: function(data) {
9448            var self    = this,
9449                render  = null,
9450                el      = null,
9451                temp    = null,
9452                output  = '';
9453
9454            self.callActions('beforeRender', arguments);
9455
9456            render = self.callFilters('renderRender', self.mixer.config.render.target, arguments);
9457
9458            if (typeof render !== 'function') {
9459                throw new TypeError(mixitup.messages.errorDatasetRendererNotSet());
9460            }
9461
9462            output = render(data);
9463
9464            if (output && typeof output === 'object' && h.isElement(output)) {
9465                el = output;
9466            } else if (typeof output === 'string') {
9467                temp = document.createElement('div');
9468                temp.innerHTML = output;
9469
9470                el = temp.firstElementChild;
9471            }
9472
9473            return self.callFilters('elRender', el, arguments);
9474        },
9475
9476        /**
9477         * Caches references of DOM elements neccessary for the target's functionality.
9478         *
9479         * @private
9480         * @instance
9481         * @since   3.0.0
9482         * @param   {Element} el
9483         * @return  {void}
9484         */
9485
9486        cacheDom: function(el) {
9487            var self = this;
9488
9489            self.callActions('beforeCacheDom', arguments);
9490
9491            self.dom.el = el;
9492
9493            self.callActions('afterCacheDom', arguments);
9494        },
9495
9496        /**
9497         * @private
9498         * @instance
9499         * @since   3.0.0
9500         * @param   {string}    attributeName
9501         * @return  {void}
9502         */
9503
9504        getSortString: function(attributeName) {
9505            var self    = this,
9506                value   = self.dom.el.getAttribute('data-' + attributeName) || '';
9507
9508            self.callActions('beforeGetSortString', arguments);
9509
9510            value = isNaN(value * 1) ?
9511                value.toLowerCase() :
9512                value * 1;
9513
9514            self.sortString = value;
9515
9516            self.callActions('afterGetSortString', arguments);
9517        },
9518
9519        /**
9520         * @private
9521         * @instance
9522         * @since   3.0.0
9523         * @return  {void}
9524         */
9525
9526        show: function() {
9527            var self = this;
9528
9529            self.callActions('beforeShow', arguments);
9530
9531            if (!self.isShown) {
9532                self.dom.el.style.display = '';
9533
9534                self.isShown = true;
9535            }
9536
9537            self.callActions('afterShow', arguments);
9538        },
9539
9540        /**
9541         * @private
9542         * @instance
9543         * @since   3.0.0
9544         * @return  {void}
9545         */
9546
9547        hide: function() {
9548            var self = this;
9549
9550            self.callActions('beforeHide', arguments);
9551
9552            if (self.isShown) {
9553                self.dom.el.style.display = 'none';
9554
9555                self.isShown = false;
9556            }
9557
9558            self.callActions('afterHide', arguments);
9559        },
9560
9561        /**
9562         * @private
9563         * @instance
9564         * @since   3.0.0
9565         * @param   {mixitup.IMoveData} moveData
9566         * @return  {void}
9567         */
9568
9569        move: function(moveData) {
9570            var self = this;
9571
9572            self.callActions('beforeMove', arguments);
9573
9574            if (!self.isExcluded) {
9575                self.mixer.targetsMoved++;
9576            }
9577
9578            self.applyStylesIn(moveData);
9579
9580            requestAnimationFrame(function() {
9581                self.applyStylesOut(moveData);
9582            });
9583
9584            self.callActions('afterMove', arguments);
9585        },
9586
9587        /**
9588         * @private
9589         * @instance
9590         * @since   3.0.0
9591         * @param   {object}    posData
9592         * @param   {number}    multiplier
9593         * @return  {void}
9594         */
9595
9596        applyTween: function(posData, multiplier) {
9597            var self                    = this,
9598                propertyName            = '',
9599                tweenData               = null,
9600                posIn                   = posData.posIn,
9601                currentTransformValues  = [],
9602                currentValues           = new mixitup.StyleData(),
9603                i                       = -1;
9604
9605            self.callActions('beforeApplyTween', arguments);
9606
9607            currentValues.x     = posIn.x;
9608            currentValues.y     = posIn.y;
9609
9610            if (multiplier === 0) {
9611                self.hide();
9612            } else if (!self.isShown) {
9613                self.show();
9614            }
9615
9616            for (i = 0; propertyName = mixitup.features.TWEENABLE[i]; i++) {
9617                tweenData = posData.tweenData[propertyName];
9618
9619                if (propertyName === 'x') {
9620                    if (!tweenData) continue;
9621
9622                    currentValues.x = posIn.x + (tweenData * multiplier);
9623                } else if (propertyName === 'y') {
9624                    if (!tweenData) continue;
9625
9626                    currentValues.y = posIn.y + (tweenData * multiplier);
9627                } else if (tweenData instanceof mixitup.TransformData) {
9628                    if (!tweenData.value) continue;
9629
9630                    currentValues[propertyName].value =
9631                        posIn[propertyName].value + (tweenData.value * multiplier);
9632
9633                    currentValues[propertyName].unit  = tweenData.unit;
9634
9635                    currentTransformValues.push(
9636                        propertyName + '(' + currentValues[propertyName].value + tweenData.unit + ')'
9637                    );
9638                } else {
9639                    if (!tweenData) continue;
9640
9641                    currentValues[propertyName] = posIn[propertyName] + (tweenData * multiplier);
9642
9643                    self.dom.el.style[propertyName] = currentValues[propertyName];
9644                }
9645            }
9646
9647            if (currentValues.x || currentValues.y) {
9648                currentTransformValues.unshift('translate(' + currentValues.x + 'px, ' + currentValues.y + 'px)');
9649            }
9650
9651            if (currentTransformValues.length) {
9652                self.dom.el.style[mixitup.features.transformProp] = currentTransformValues.join(' ');
9653            }
9654
9655            self.callActions('afterApplyTween', arguments);
9656        },
9657
9658        /**
9659         * Applies the initial styling to a target element before any transition
9660         * is applied.
9661         *
9662         * @private
9663         * @instance
9664         * @param   {mixitup.IMoveData} moveData
9665         * @return  {void}
9666         */
9667
9668        applyStylesIn: function(moveData) {
9669            var self            = this,
9670                posIn           = moveData.posIn,
9671                isFading        = self.mixer.effectsIn.opacity !== 1,
9672                transformValues = [];
9673
9674            self.callActions('beforeApplyStylesIn', arguments);
9675
9676            transformValues.push('translate(' + posIn.x + 'px, ' + posIn.y + 'px)');
9677
9678            if (self.mixer.config.animation.animateResizeTargets) {
9679                if (moveData.statusChange !== 'show') {
9680                    // Don't apply posIn width or height or showing, as will be 0
9681
9682                    self.dom.el.style.width  = posIn.width + 'px';
9683                    self.dom.el.style.height = posIn.height + 'px';
9684                }
9685
9686                self.dom.el.style.marginRight  = posIn.marginRight + 'px';
9687                self.dom.el.style.marginBottom = posIn.marginBottom + 'px';
9688            }
9689
9690            isFading && (self.dom.el.style.opacity = posIn.opacity);
9691
9692            if (moveData.statusChange === 'show') {
9693                transformValues = transformValues.concat(self.mixer.transformIn);
9694            }
9695
9696            self.dom.el.style[mixitup.features.transformProp] = transformValues.join(' ');
9697
9698            self.callActions('afterApplyStylesIn', arguments);
9699        },
9700
9701        /**
9702         * Applies a transition followed by the final styles for the element to
9703         * transition towards.
9704         *
9705         * @private
9706         * @instance
9707         * @param   {mixitup.IMoveData} moveData
9708         * @return  {void}
9709         */
9710
9711        applyStylesOut: function(moveData) {
9712            var self            = this,
9713                transitionRules = [],
9714                transformValues = [],
9715                isResizing      = self.mixer.config.animation.animateResizeTargets,
9716                isFading        = typeof self.mixer.effectsIn.opacity !== 'undefined';
9717
9718            self.callActions('beforeApplyStylesOut', arguments);
9719
9720            // Build the transition rules
9721
9722            transitionRules.push(self.writeTransitionRule(
9723                mixitup.features.transformRule,
9724                moveData.staggerIndex
9725            ));
9726
9727            if (moveData.statusChange !== 'none') {
9728                transitionRules.push(self.writeTransitionRule(
9729                    'opacity',
9730                    moveData.staggerIndex,
9731                    moveData.duration
9732                ));
9733            }
9734
9735            if (isResizing) {
9736                transitionRules.push(self.writeTransitionRule(
9737                    'width',
9738                    moveData.staggerIndex,
9739                    moveData.duration
9740                ));
9741
9742                transitionRules.push(self.writeTransitionRule(
9743                    'height',
9744                    moveData.staggerIndex,
9745                    moveData.duration
9746                ));
9747
9748                transitionRules.push(self.writeTransitionRule(
9749                    'margin',
9750                    moveData.staggerIndex,
9751                    moveData.duration
9752                ));
9753            }
9754
9755            // If no callback was provided, the element will
9756            // not transition in any way so tag it as "immovable"
9757
9758            if (!moveData.callback) {
9759                self.mixer.targetsImmovable++;
9760
9761                if (self.mixer.targetsMoved === self.mixer.targetsImmovable) {
9762                    // If the total targets moved is equal to the
9763                    // number of immovable targets, the operation
9764                    // should be considered finished
9765
9766                    self.mixer.cleanUp(moveData.operation);
9767                }
9768
9769                return;
9770            }
9771
9772            // If the target will transition in some fasion,
9773            // assign a callback function
9774
9775            self.operation = moveData.operation;
9776            self.callback = moveData.callback;
9777
9778            // As long as the target is not excluded, increment
9779            // the total number of targets bound
9780
9781            !self.isExcluded && self.mixer.targetsBound++;
9782
9783            // Tag the target as bound to differentiate from transitionEnd
9784            // events that may come from stylesheet driven effects
9785
9786            self.isBound = true;
9787
9788            // Apply the transition
9789
9790            self.applyTransition(transitionRules);
9791
9792            // Apply width, height and margin negation
9793
9794            if (isResizing && moveData.posOut.width > 0 && moveData.posOut.height > 0) {
9795                self.dom.el.style.width        = moveData.posOut.width + 'px';
9796                self.dom.el.style.height       = moveData.posOut.height + 'px';
9797                self.dom.el.style.marginRight  = moveData.posOut.marginRight + 'px';
9798                self.dom.el.style.marginBottom = moveData.posOut.marginBottom + 'px';
9799            }
9800
9801            if (!self.mixer.config.animation.nudge && moveData.statusChange === 'hide') {
9802                // If we're not nudging, the translation should be
9803                // applied before any other transforms to prevent
9804                // lateral movement
9805
9806                transformValues.push('translate(' + moveData.posOut.x + 'px, ' + moveData.posOut.y + 'px)');
9807            }
9808
9809            // Apply fade
9810
9811            switch (moveData.statusChange) {
9812                case 'hide':
9813                    isFading && (self.dom.el.style.opacity = self.mixer.effectsOut.opacity);
9814
9815                    transformValues = transformValues.concat(self.mixer.transformOut);
9816
9817                    break;
9818                case 'show':
9819                    isFading && (self.dom.el.style.opacity = 1);
9820            }
9821
9822            if (
9823                self.mixer.config.animation.nudge ||
9824                (!self.mixer.config.animation.nudge && moveData.statusChange !== 'hide')
9825            ) {
9826                // Opposite of above - apply translate after
9827                // other transform
9828
9829                transformValues.push('translate(' + moveData.posOut.x + 'px, ' + moveData.posOut.y + 'px)');
9830            }
9831
9832            // Apply transforms
9833
9834            self.dom.el.style[mixitup.features.transformProp] = transformValues.join(' ');
9835
9836            self.callActions('afterApplyStylesOut', arguments);
9837        },
9838
9839        /**
9840         * Combines the name of a CSS property with the appropriate duration and delay
9841         * values to created a valid transition rule.
9842         *
9843         * @private
9844         * @instance
9845         * @since   3.0.0
9846         * @param   {string}    property
9847         * @param   {number}    staggerIndex
9848         * @param   {number}    duration
9849         * @return  {string}
9850         */
9851
9852        writeTransitionRule: function(property, staggerIndex, duration) {
9853            var self  = this,
9854                delay = self.getDelay(staggerIndex),
9855                rule  = '';
9856
9857            rule = property + ' ' +
9858                (duration > 0 ? duration : self.mixer.config.animation.duration) + 'ms ' +
9859                delay + 'ms ' +
9860                (property === 'opacity' ? 'linear' : self.mixer.config.animation.easing);
9861
9862            return self.callFilters('ruleWriteTransitionRule', rule, arguments);
9863        },
9864
9865        /**
9866         * Calculates the transition delay for each target element based on its index, if
9867         * staggering is applied. If defined, A custom `animation.staggerSeqeuence`
9868         * function can be used to manipulate the order of indices to produce custom
9869         * stagger effects (e.g. for use in a grid with irregular row lengths).
9870         *
9871         * @private
9872         * @instance
9873         * @since   2.0.0
9874         * @param   {number}    index
9875         * @return  {number}
9876         */
9877
9878        getDelay: function(index) {
9879            var self    = this,
9880                delay   = -1;
9881
9882            if (typeof self.mixer.config.animation.staggerSequence === 'function') {
9883                index = self.mixer.config.animation.staggerSequence.call(self, index, self.state);
9884            }
9885
9886            delay = !!self.mixer.staggerDuration ? index * self.mixer.staggerDuration : 0;
9887
9888            return self.callFilters('delayGetDelay', delay, arguments);
9889        },
9890
9891        /**
9892         * @private
9893         * @instance
9894         * @since   3.0.0
9895         * @param   {string[]}  rules
9896         * @return  {void}
9897         */
9898
9899        applyTransition: function(rules) {
9900            var self                = this,
9901                transitionString    = rules.join(', ');
9902
9903            self.callActions('beforeApplyTransition', arguments);
9904
9905            self.dom.el.style[mixitup.features.transitionProp] = transitionString;
9906
9907            self.callActions('afterApplyTransition', arguments);
9908        },
9909
9910        /**
9911         * @private
9912         * @instance
9913         * @since   3.0.0
9914         * @param   {Event} e
9915         * @return  {void}
9916         */
9917
9918        handleTransitionEnd: function(e) {
9919            var self        = this,
9920                propName    = e.propertyName,
9921                canResize   = self.mixer.config.animation.animateResizeTargets;
9922
9923            self.callActions('beforeHandleTransitionEnd', arguments);
9924
9925            if (
9926                self.isBound &&
9927                e.target.matches(self.mixer.config.selectors.target) &&
9928                (
9929                    propName.indexOf('transform') > -1 ||
9930                    propName.indexOf('opacity') > -1 ||
9931                    canResize && propName.indexOf('height') > -1 ||
9932                    canResize && propName.indexOf('width') > -1 ||
9933                    canResize && propName.indexOf('margin') > -1
9934                )
9935            ) {
9936                self.callback.call(self, self.operation);
9937
9938                self.isBound = false;
9939                self.callback = null;
9940                self.operation = null;
9941            }
9942
9943            self.callActions('afterHandleTransitionEnd', arguments);
9944        },
9945
9946        /**
9947         * @private
9948         * @instance
9949         * @since   3.0.0
9950         * @param   {Event}     e
9951         * @return  {void}
9952         */
9953
9954        eventBus: function(e) {
9955            var self = this;
9956
9957            self.callActions('beforeEventBus', arguments);
9958
9959            switch (e.type) {
9960                case 'webkitTransitionEnd':
9961                case 'transitionend':
9962                    self.handleTransitionEnd(e);
9963            }
9964
9965            self.callActions('afterEventBus', arguments);
9966        },
9967
9968        /**
9969         * @private
9970         * @instance
9971         * @since   3.0.0
9972         * @return  {void}
9973         */
9974
9975        unbindEvents: function() {
9976            var self = this;
9977
9978            self.callActions('beforeUnbindEvents', arguments);
9979
9980            h.off(self.dom.el, 'webkitTransitionEnd', self.handler);
9981            h.off(self.dom.el, 'transitionend', self.handler);
9982
9983            self.callActions('afterUnbindEvents', arguments);
9984        },
9985
9986        /**
9987         * @private
9988         * @instance
9989         * @since   3.0.0
9990         * @return  {void}
9991         */
9992
9993        bindEvents: function() {
9994            var self                = this,
9995                transitionEndEvent  = '';
9996
9997            self.callActions('beforeBindEvents', arguments);
9998
9999            transitionEndEvent = mixitup.features.transitionPrefix === 'webkit' ? 'webkitTransitionEnd' : 'transitionend';
10000
10001            self.handler = function(e) {
10002                return self.eventBus(e);
10003            };
10004
10005            h.on(self.dom.el, transitionEndEvent, self.handler);
10006
10007            self.callActions('afterBindEvents', arguments);
10008        },
10009
10010        /**
10011         * @private
10012         * @instance
10013         * @since   3.0.0
10014         * @param   {boolean}   [getBox]
10015         * @return  {PosData}
10016         */
10017
10018        getPosData: function(getBox) {
10019            var self    = this,
10020                styles  = {},
10021                rect    = null,
10022                posData = new mixitup.StyleData();
10023
10024            self.callActions('beforeGetPosData', arguments);
10025
10026            posData.x = self.dom.el.offsetLeft;
10027            posData.y = self.dom.el.offsetTop;
10028
10029            if (self.mixer.config.animation.animateResizeTargets || getBox) {
10030                rect = self.dom.el.getBoundingClientRect();
10031
10032                posData.top     = rect.top;
10033                posData.right   = rect.right;
10034                posData.bottom  = rect.bottom;
10035                posData.left    = rect.left;
10036
10037                posData.width  = rect.width;
10038                posData.height = rect.height;
10039            }
10040
10041            if (self.mixer.config.animation.animateResizeTargets) {
10042                styles = window.getComputedStyle(self.dom.el);
10043
10044                posData.marginBottom = parseFloat(styles.marginBottom);
10045                posData.marginRight  = parseFloat(styles.marginRight);
10046            }
10047
10048            return self.callFilters('posDataGetPosData', posData, arguments);
10049        },
10050
10051        /**
10052         * @private
10053         * @instance
10054         * @since       3.0.0
10055         * @return      {void}
10056         */
10057
10058        cleanUp: function() {
10059            var self = this;
10060
10061            self.callActions('beforeCleanUp', arguments);
10062
10063            self.dom.el.style[mixitup.features.transformProp]  = '';
10064            self.dom.el.style[mixitup.features.transitionProp] = '';
10065            self.dom.el.style.opacity                          = '';
10066
10067            if (self.mixer.config.animation.animateResizeTargets) {
10068                self.dom.el.style.width        = '';
10069                self.dom.el.style.height       = '';
10070                self.dom.el.style.marginRight  = '';
10071                self.dom.el.style.marginBottom = '';
10072            }
10073
10074            self.callActions('afterCleanUp', arguments);
10075        }
10076    });
10077
10078    /**
10079     * A jQuery-collection-like wrapper around one or more `mixitup.Mixer` instances
10080     * allowing simultaneous control of said instances similar to the MixItUp 2 API.
10081     *
10082     * @example
10083     * new mixitup.Collection(instances)
10084     *
10085     * @constructor
10086     * @namespace
10087     * @memberof    mixitup
10088     * @private
10089     * @since       3.0.0
10090     * @param       {mixitup.Mixer[]}   instances
10091     */
10092
10093    mixitup.Collection = function(instances) {
10094        var instance    = null,
10095            i           = -1;
10096
10097        this.callActions('beforeConstruct');
10098
10099        for (i = 0; instance = instances[i]; i++) {
10100            this[i] = instance;
10101        }
10102
10103        this.length = instances.length;
10104
10105        this.callActions('afterConstruct');
10106
10107        h.freeze(this);
10108    };
10109
10110    mixitup.BaseStatic.call(mixitup.Collection);
10111
10112    mixitup.Collection.prototype = Object.create(mixitup.Base.prototype);
10113
10114    h.extend(mixitup.Collection.prototype,
10115    /** @lends mixitup.Collection */
10116    {
10117        constructor: mixitup.Collection,
10118
10119        /**
10120         * Calls a method on all instances in the collection by passing the method
10121         * name as a string followed by any applicable parameters to be curried into
10122         * to the method.
10123         *
10124         * @example
10125         * .mixitup(methodName[,arg1][,arg2..]);
10126         *
10127         * @example
10128         * var collection = new Collection([mixer1, mixer2]);
10129         *
10130         * return collection.mixitup('filter', '.category-a')
10131         *     .then(function(states) {
10132         *         state.forEach(function(state) {
10133         *             console.log(state.activeFilter.selector); // .category-a
10134         *         });
10135         *     });
10136         *
10137         * @public
10138         * @instance
10139         * @since       3.0.0
10140         * @param       {string}  methodName
10141         * @return      {Promise<Array<mixitup.State>>}
10142         */
10143
10144        mixitup: function(methodName) {
10145            var self        = this,
10146                instance    = null,
10147                args        = Array.prototype.slice.call(arguments),
10148                tasks       = [],
10149                i           = -1;
10150
10151            this.callActions('beforeMixitup');
10152
10153            args.shift();
10154
10155            for (i = 0; instance = self[i]; i++) {
10156                tasks.push(instance[methodName].apply(instance, args));
10157            }
10158
10159            return self.callFilters('promiseMixitup', h.all(tasks, mixitup.libraries), arguments);
10160        }
10161    });
10162
10163    /**
10164     * `mixitup.Operation` objects contain all data neccessary to describe the full
10165     * lifecycle of any MixItUp operation. They can be used to compute and store an
10166     * operation for use at a later time (e.g. programmatic tweening).
10167     *
10168     * @constructor
10169     * @namespace
10170     * @memberof    mixitup
10171     * @private
10172     * @since       3.0.0
10173     */
10174
10175    mixitup.Operation = function() {
10176        mixitup.Base.call(this);
10177
10178        this.callActions('beforeConstruct');
10179
10180        this.id                      = '';
10181
10182        this.args                    = [];
10183        this.command                 = null;
10184        this.showPosData             = [];
10185        this.toHidePosData           = [];
10186
10187        this.startState              = null;
10188        this.newState                = null;
10189        this.docState                = null;
10190
10191        this.willSort                = false;
10192        this.willChangeLayout        = false;
10193        this.hasEffect               = false;
10194        this.hasFailed               = false;
10195
10196        this.triggerElement          = null;
10197
10198        this.show                    = [];
10199        this.hide                    = [];
10200        this.matching                = [];
10201        this.toShow                  = [];
10202        this.toHide                  = [];
10203        this.toMove                  = [];
10204        this.toRemove                = [];
10205        this.startOrder              = [];
10206        this.newOrder                = [];
10207        this.startSort               = null;
10208        this.newSort                 = null;
10209        this.startFilter             = null;
10210        this.newFilter               = null;
10211        this.startDataset            = null;
10212        this.newDataset              = null;
10213        this.viewportDeltaX          = 0;
10214        this.viewportDeltaY          = 0;
10215        this.startX                  = 0;
10216        this.startY                  = 0;
10217        this.startHeight             = 0;
10218        this.startWidth              = 0;
10219        this.newX                    = 0;
10220        this.newY                    = 0;
10221        this.newHeight               = 0;
10222        this.newWidth                = 0;
10223        this.startContainerClassName = '';
10224        this.startDisplay            = '';
10225        this.newContainerClassName   = '';
10226        this.newDisplay              = '';
10227
10228        this.callActions('afterConstruct');
10229
10230        h.seal(this);
10231    };
10232
10233    mixitup.BaseStatic.call(mixitup.Operation);
10234
10235    mixitup.Operation.prototype = Object.create(mixitup.Base.prototype);
10236
10237    mixitup.Operation.prototype.constructor = mixitup.Operation;
10238
10239    /**
10240     * `mixitup.State` objects expose various pieces of data detailing the state of
10241     * a MixItUp instance. They are provided at the start and end of any operation via
10242     * callbacks and events, with the most recent state stored between operations
10243     * for retrieval at any time via the API.
10244     *
10245     * @constructor
10246     * @namespace
10247     * @memberof    mixitup
10248     * @public
10249     * @since       3.0.0
10250     */
10251
10252    mixitup.State = function() {
10253        mixitup.Base.call(this);
10254
10255        this.callActions('beforeConstruct');
10256
10257        /**
10258         * The ID of the mixer instance.
10259         *
10260         * @name        id
10261         * @memberof    mixitup.State
10262         * @instance
10263         * @type        {string}
10264         * @default     ''
10265         */
10266
10267        this.id = '';
10268
10269        /**
10270         * The currently active filter command as set by a control click or API call.
10271         *
10272         * @name        activeFilter
10273         * @memberof    mixitup.State
10274         * @instance
10275         * @type        {mixitup.CommandFilter}
10276         * @default     null
10277         */
10278
10279        this.activeFilter = null;
10280
10281        /**
10282         * The currently active sort command as set by a control click or API call.
10283         *
10284         * @name        activeSort
10285         * @memberof    mixitup.State
10286         * @instance
10287         * @type        {mixitup.CommandSort}
10288         * @default     null
10289         */
10290
10291        this.activeSort = null;
10292
10293        /**
10294         * The current layout-specific container class name, if applied.
10295         *
10296         * @name        activeContainerClassName
10297         * @memberof    mixitup.State
10298         * @instance
10299         * @type        {string}
10300         * @default     ''
10301         */
10302
10303        this.activeContainerClassName = '';
10304
10305        /**
10306         * A reference to the container element that the mixer is instantiated on.
10307         *
10308         * @name        container
10309         * @memberof    mixitup.State
10310         * @instance
10311         * @type        {Element}
10312         * @default     null
10313         */
10314
10315        this.container = null;
10316
10317        /**
10318         * An array of all target elements indexed by the mixer.
10319         *
10320         * @name        targets
10321         * @memberof    mixitup.State
10322         * @instance
10323         * @type        {Array.<Element>}
10324         * @default     []
10325         */
10326
10327        this.targets = [];
10328
10329        /**
10330         * An array of all target elements not matching the current filter.
10331         *
10332         * @name        hide
10333         * @memberof    mixitup.State
10334         * @instance
10335         * @type        {Array.<Element>}
10336         * @default     []
10337         */
10338
10339        this.hide = [];
10340
10341        /**
10342         * An array of all target elements matching the current filter and any additional
10343         * limits applied such as pagination.
10344         *
10345         * @name        show
10346         * @memberof    mixitup.State
10347         * @instance
10348         * @type        {Array.<Element>}
10349         * @default     []
10350         */
10351
10352        this.show = [];
10353
10354        /**
10355         * An array of all target elements matching the current filter irrespective of
10356         * any additional limits applied such as pagination.
10357         *
10358         * @name        matching
10359         * @memberof    mixitup.State
10360         * @instance
10361         * @type        {Array.<Element>}
10362         * @default     []
10363         */
10364
10365        this.matching = [];
10366
10367        /**
10368         * An integer representing the total number of target elements indexed by the
10369         * mixer. Equivalent to `state.targets.length`.
10370         *
10371         * @name        totalTargets
10372         * @memberof    mixitup.State
10373         * @instance
10374         * @type        {number}
10375         * @default     -1
10376         */
10377
10378        this.totalTargets = -1;
10379
10380        /**
10381         * An integer representing the total number of target elements matching the
10382         * current filter and any additional limits applied such as pagination.
10383         * Equivalent to `state.show.length`.
10384         *
10385         * @name        totalShow
10386         * @memberof    mixitup.State
10387         * @instance
10388         * @type        {number}
10389         * @default     -1
10390         */
10391
10392        this.totalShow = -1;
10393
10394        /**
10395         * An integer representing the total number of target elements not matching
10396         * the current filter. Equivalent to `state.hide.length`.
10397         *
10398         * @name        totalHide
10399         * @memberof    mixitup.State
10400         * @instance
10401         * @type        {number}
10402         * @default     -1
10403         */
10404
10405        this.totalHide = -1;
10406
10407        /**
10408         * An integer representing the total number of target elements matching the
10409         * current filter irrespective of any other limits applied such as pagination.
10410         * Equivalent to `state.matching.length`.
10411         *
10412         * @name        totalMatching
10413         * @memberof    mixitup.State
10414         * @instance
10415         * @type        {number}
10416         * @default     -1
10417         */
10418
10419        this.totalMatching = -1;
10420
10421        /**
10422         * A boolean indicating whether the last operation "failed", i.e. no targets
10423         * could be found matching the filter.
10424         *
10425         * @name        hasFailed
10426         * @memberof    mixitup.State
10427         * @instance
10428         * @type        {boolean}
10429         * @default     false
10430         */
10431
10432        this.hasFailed = false;
10433
10434        /**
10435         * The DOM element that was clicked if the last operation was triggered by the
10436         * clicking of a control and not an API call.
10437         *
10438         * @name        triggerElement
10439         * @memberof    mixitup.State
10440         * @instance
10441         * @type        {Element|null}
10442         * @default     null
10443         */
10444
10445        this.triggerElement = null;
10446
10447        /**
10448         * The currently active dataset underlying the rendered targets, if the
10449         * dataset API is in use.
10450         *
10451         * @name        activeDataset
10452         * @memberof    mixitup.State
10453         * @instance
10454         * @type        {Array.<object>}
10455         * @default     null
10456         */
10457
10458        this.activeDataset = null;
10459
10460        this.callActions('afterConstruct');
10461
10462        h.seal(this);
10463    };
10464
10465    mixitup.BaseStatic.call(mixitup.State);
10466
10467    mixitup.State.prototype = Object.create(mixitup.Base.prototype);
10468
10469    mixitup.State.prototype.constructor = mixitup.State;
10470
10471    /**
10472     * @constructor
10473     * @memberof    mixitup
10474     * @private
10475     * @since       3.0.0
10476     */
10477
10478    mixitup.UserInstruction = function() {
10479        mixitup.Base.call(this);
10480
10481        this.callActions('beforeConstruct');
10482
10483        this.command    = {};
10484        this.animate    = false;
10485        this.callback   = null;
10486
10487        this.callActions('afterConstruct');
10488
10489        h.seal(this);
10490    };
10491
10492    mixitup.BaseStatic.call(mixitup.UserInstruction);
10493
10494    mixitup.UserInstruction.prototype = Object.create(mixitup.Base.prototype);
10495
10496    mixitup.UserInstruction.prototype.constructor = mixitup.UserInstruction;
10497
10498    /**
10499     * @constructor
10500     * @memberof    mixitup
10501     * @private
10502     * @since       3.0.0
10503     */
10504
10505    mixitup.Messages = function() {
10506        mixitup.Base.call(this);
10507
10508        this.callActions('beforeConstruct');
10509
10510        /* Errors
10511        ----------------------------------------------------------------------------- */
10512
10513        this.ERROR_FACTORY_INVALID_CONTAINER =
10514            '[MixItUp] An invalid selector or element reference was passed to the mixitup factory function';
10515
10516        this.ERROR_FACTORY_CONTAINER_NOT_FOUND =
10517            '[MixItUp] The provided selector yielded no container element';
10518
10519        this.ERROR_CONFIG_INVALID_ANIMATION_EFFECTS =
10520            '[MixItUp] Invalid value for `animation.effects`';
10521
10522        this.ERROR_CONFIG_INVALID_CONTROLS_SCOPE =
10523            '[MixItUp] Invalid value for `controls.scope`';
10524
10525        this.ERROR_CONFIG_INVALID_PROPERTY =
10526            '[MixitUp] Invalid configuration object property "${erroneous}"${suggestion}';
10527
10528        this.ERROR_CONFIG_INVALID_PROPERTY_SUGGESTION =
10529            '. Did you mean "${probableMatch}"?';
10530
10531        this.ERROR_CONFIG_DATA_UID_KEY_NOT_SET =
10532            '[MixItUp] To use the dataset API, a UID key must be specified using `data.uidKey`';
10533
10534        this.ERROR_DATASET_INVALID_UID_KEY =
10535            '[MixItUp] The specified UID key "${uidKey}" is not present on one or more dataset items';
10536
10537        this.ERROR_DATASET_DUPLICATE_UID =
10538            '[MixItUp] The UID "${uid}" was found on two or more dataset items. UIDs must be unique.';
10539
10540        this.ERROR_INSERT_INVALID_ARGUMENTS =
10541            '[MixItUp] Please provider either an index or a sibling and position to insert, not both';
10542
10543        this.ERROR_INSERT_PREEXISTING_ELEMENT =
10544            '[MixItUp] An element to be inserted already exists in the container';
10545
10546        this.ERROR_FILTER_INVALID_ARGUMENTS =
10547            '[MixItUp] Please provide either a selector or collection `.filter()`, not both';
10548
10549        this.ERROR_DATASET_NOT_SET =
10550            '[MixItUp] To use the dataset API with pre-rendered targets, a starting dataset must be set using `load.dataset`';
10551
10552        this.ERROR_DATASET_PRERENDERED_MISMATCH =
10553            '[MixItUp] `load.dataset` does not match pre-rendered targets';
10554
10555        this.ERROR_DATASET_RENDERER_NOT_SET =
10556            '[MixItUp] To insert an element via the dataset API, a target renderer function must be provided to `render.target`';
10557
10558        this.ERROR_SORT_NON_EXISTENT_ELEMENT =
10559            '[MixItUp] An element to be sorted does not already exist in the container';
10560
10561        /* Warnings
10562        ----------------------------------------------------------------------------- */
10563
10564        this.WARNING_FACTORY_PREEXISTING_INSTANCE =
10565            '[MixItUp] WARNING: This element already has an active MixItUp instance. The provided configuration object will be ignored.' +
10566            ' If you wish to perform additional methods on this instance, please create a reference.';
10567
10568        this.WARNING_INSERT_NO_ELEMENTS =
10569            '[MixItUp] WARNING: No valid elements were passed to `.insert()`';
10570
10571        this.WARNING_REMOVE_NO_ELEMENTS =
10572            '[MixItUp] WARNING: No valid elements were passed to `.remove()`';
10573
10574        this.WARNING_MULTIMIX_INSTANCE_QUEUE_FULL =
10575            '[MixItUp] WARNING: An operation was requested but the MixItUp instance was busy. The operation was rejected because the ' +
10576            'queue is full or queuing is disabled.';
10577
10578        this.WARNING_GET_OPERATION_INSTANCE_BUSY =
10579            '[MixItUp] WARNING: Operations can be be created while the MixItUp instance is busy.';
10580
10581        this.WARNING_NO_PROMISE_IMPLEMENTATION =
10582            '[MixItUp] WARNING: No Promise implementations could be found. If you wish to use promises with MixItUp please install' +
10583            ' an ES6 Promise polyfill.';
10584
10585        this.WARNING_INCONSISTENT_SORTING_ATTRIBUTES =
10586            '[MixItUp] WARNING: The requested sorting data attribute "${attribute}" was not present on one or more target elements' +
10587            ' which may product unexpected sort output';
10588
10589        this.callActions('afterConstruct');
10590
10591        this.compileTemplates();
10592
10593        h.seal(this);
10594    };
10595
10596    mixitup.BaseStatic.call(mixitup.Messages);
10597
10598    mixitup.Messages.prototype = Object.create(mixitup.Base.prototype);
10599
10600    mixitup.Messages.prototype.constructor = mixitup.Messages;
10601
10602    /**
10603     * @return {void}
10604     */
10605
10606    mixitup.Messages.prototype.compileTemplates = function() {
10607        var errorKey        = '';
10608        var errorMessage    = '';
10609
10610        for (errorKey in this) {
10611            if (typeof (errorMessage = this[errorKey]) !== 'string') continue;
10612
10613            this[h.camelCase(errorKey)] = h.template(errorMessage);
10614        }
10615    };
10616
10617    mixitup.messages = new mixitup.Messages();
10618
10619    /**
10620     * @constructor
10621     * @memberof    mixitup
10622     * @private
10623     * @since       3.0.0
10624     * @param       {mixitup.Mixer} mixer
10625     */
10626
10627    mixitup.Facade = function Mixer(mixer) {
10628        mixitup.Base.call(this);
10629
10630        this.callActions('beforeConstruct', arguments);
10631
10632        this.configure          = mixer.configure.bind(mixer);
10633        this.show               = mixer.show.bind(mixer);
10634        this.hide               = mixer.hide.bind(mixer);
10635        this.filter             = mixer.filter.bind(mixer);
10636        this.toggleOn           = mixer.toggleOn.bind(mixer);
10637        this.toggleOff          = mixer.toggleOff.bind(mixer);
10638        this.sort               = mixer.sort.bind(mixer);
10639        this.changeLayout       = mixer.changeLayout.bind(mixer);
10640        this.multimix           = mixer.multimix.bind(mixer);
10641        this.dataset            = mixer.dataset.bind(mixer);
10642        this.tween              = mixer.tween.bind(mixer);
10643        this.insert             = mixer.insert.bind(mixer);
10644        this.insertBefore       = mixer.insertBefore.bind(mixer);
10645        this.insertAfter        = mixer.insertAfter.bind(mixer);
10646        this.prepend            = mixer.prepend.bind(mixer);
10647        this.append             = mixer.append.bind(mixer);
10648        this.remove             = mixer.remove.bind(mixer);
10649        this.destroy            = mixer.destroy.bind(mixer);
10650        this.forceRefresh       = mixer.forceRefresh.bind(mixer);
10651        this.forceRender        = mixer.forceRender.bind(mixer);
10652        this.isMixing           = mixer.isMixing.bind(mixer);
10653        this.getOperation       = mixer.getOperation.bind(mixer);
10654        this.getConfig          = mixer.getConfig.bind(mixer);
10655        this.getState           = mixer.getState.bind(mixer);
10656
10657        this.callActions('afterConstruct', arguments);
10658
10659        h.freeze(this);
10660        h.seal(this);
10661    };
10662
10663    mixitup.BaseStatic.call(mixitup.Facade);
10664
10665    mixitup.Facade.prototype = Object.create(mixitup.Base.prototype);
10666
10667    mixitup.Facade.prototype.constructor = mixitup.Facade;
10668
10669    if (typeof exports === 'object' && typeof module === 'object') {
10670        module.exports = mixitup;
10671    } else if (typeof define === 'function' && define.amd) {
10672        define(function() {
10673            return mixitup;
10674        });
10675    } else if (typeof window.mixitup === 'undefined' || typeof window.mixitup !== 'function') {
10676        window.mixitup = mixitup;
10677    }
10678    mixitup.BaseStatic.call(mixitup.constructor);
10679
10680    mixitup.NAME = 'mixitup';
10681    mixitup.CORE_VERSION = '3.3.
106811';
10682})(window);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.