PageSourceSearch

https://academy-of-visual-arts.com/wordpress/wp-content/themes/ava…/build/js/mixitup-multifilter.js?ver=7.1.3

js academy-of-visual-arts.com collected 2026-10-09 22:39:45 UTC 48,615 bytes, 1,256 lines download raw bytes

1/**!
2 * MixItUp MultiFilter v3.3.6
3 * A UI-builder for powerful multidimensional filtering
4 * Build 293e0dda-087e-4a76-aadf-e3e8b311b81f
5 *
6 * Requires mixitup.js >= v^3.1.2
7 *
8 * @copyright Copyright 2014-2020 KunkaLabs Limited.
9 * @author    KunkaLabs Limited.
10 * @link      https://www.kunkalabs.com/mixitup-multifilter/
11 *
12 * @license   Commercial use requires a commercial license.
13 *            https://www.kunkalabs.com/mixitup-multifilter/licenses/
14 *
15 *            Non-commercial use permitted under same terms as  license.
16 *            http://creativecommons.org/licenses/by-nc/3.0/
vendor: 47,485 bytes, lines 16-1243
16
17 */
18(function(window) {
19    'use strict';
20
21    var mixitupMultifilter = function(mixitup) {
22        var h = mixitup.h;
23        var diacriticsMap;
24
25        diacriticsMap = [
26            ['A', /[\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F]/g],
27            ['AA', /[\uA732]/g],
28            ['AE', /[\u00C6\u01FC\u01E2]/g],
29            ['AO', /[\uA734]/g],
30            ['AU', /[\uA736]/g],
31            ['AV', /[\uA738\uA73A]/g],
32            ['AY', /[\uA73C]/g],
33            ['B', /[\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181]/g],
34            ['C', /[\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E]/g],
35            ['D', /[\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779]/g],
36            ['DZ', /[\u01F1\u01C4]/g],
37            ['Dz', /[\u01F2\u01C5]/g],
38            ['E', /[\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E]/g],
39            ['F', /[\u0046\u24BB\uFF26\u1E1E\u0191\uA77B]/g],
40            ['G', /[\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E]/g],
41            ['H', /[\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D]/g],
42            ['I', /[\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197]/g],
43            ['J', /[\u004A\u24BF\uFF2A\u0134\u0248]/g],
44            ['K', /[\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2]/g],
45            ['L', /[\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780]/g],
46            ['LJ', /[\u01C7]/g],
47            ['Lj', /[\u01C8]/g],
48            ['M', /[\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C]/g],
49            ['N', /[\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4]/g],
50            ['NJ', /[\u01CA]/g],
51            ['Nj', /[\u01CB]/g],
52            ['O', /[\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C]/g],
53            ['OI', /[\u01A2]/g],
54            ['OO', /[\uA74E]/g],
55            ['OU', /[\u0222]/g],
56            ['P', /[\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754]/g],
57            ['Q', /[\u0051\u24C6\uFF31\uA756\uA758\u024A]/g],
58            ['R', /[\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782]/g],
59            ['S', /[\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784]/g],
60            ['T', /[\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786]/g],
61            ['TZ', /[\uA728]/g],
62            ['U', /[\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244]/g],
63            ['V', /[\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245]/g],
64            ['VY', /[\uA760]/g],
65            ['W', /[\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72]/g],
66            ['X', /[\u0058\u24CD\uFF38\u1E8A\u1E8C]/g],
67            ['Y', /[\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE]/g],
68            ['Z', /[\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762]/g],
69            ['a', /[\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250]/g],
70            ['aa', /[\uA733]/g],
71            ['ae', /[\u00E6\u01FD\u01E3]/g],
72            ['ao', /[\uA735]/g],
73            ['au', /[\uA737]/g],
74            ['av', /[\uA739\uA73B]/g],
75            ['ay', /[\uA73D]/g],
76            ['b', /[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253]/g],
77            ['c', /[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184]/g],
78            ['d', /[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A]/g],
79            ['dz', /[\u01F3\u01C6]/g],
80            ['e', /[\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD]/g],
81            ['f', /[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C]/g],
82            ['g', /[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F]/g],
83            ['h', /[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265]/g],
84            ['hv', /[\u0195]/g],
85            ['i', /[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131]/g],
86            ['j', /[\u006A\u24D9\uFF4A\u0135\u01F0\u0249]/g],
87            ['k', /[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3]/g],
88            ['l', /[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747]/g],
89            ['lj', /[\u01C9]/g],
90            ['m', /[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F]/g],
91            ['n', /[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5]/g],
92            ['nj', /[\u01CC]/g],
93            ['o', /[\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275]/g],
94            ['oi', /[\u01A3]/g],
95            ['ou', /[\u0223]/g],
96            ['oo', /[\uA74F]/g],
97            ['p', /[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755]/g],
98            ['q', /[\u0071\u24E0\uFF51\u024B\uA757\uA759]/g],
99            ['r', /[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783]/g],
100            ['s', /[\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B]/g],
101            ['t', /[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787]/g],
102            ['tz', /[\uA729]/g],
103            ['u', /[\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289]/g],
104            ['v', /[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C]/g],
105            ['vy', /[\uA761]/g],
106            ['w', /[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73]/g],
107            ['x', /[\u0078\u24E7\uFF58\u1E8B\u1E8D]/g],
108            ['y', /[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF]/g],
109            ['z', /[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763]/g]
110        ];
111
112        if (
113            !mixitup.CORE_VERSION ||
114            !h.compareVersions(mixitupMultifilter.REQUIRE_CORE_VERSION, mixitup.CORE_VERSION)
115        ) {
116            throw new Error(
117                '[MixItUp Multifilter] MixItUp Multifilter v' +
118                mixitupMultifilter.EXTENSION_VERSION +
119                ' requires at least MixItUp v' +
120                mixitupMultifilter.REQUIRE_CORE_VERSION
121            );
122        }
123
124        /**
125         * A group of optional callback functions to be invoked at various
126         * points within the lifecycle of a mixer operation.
127         *
128         * @constructor
129         * @memberof    mixitup.Config
130         * @name        callbacks
131         * @namespace
132         * @public
133         * @since       3.0.0
134         */
135
136        mixitup.ConfigCallbacks.registerAction('afterConstruct', 'multifilter', function() {
137            /**
138             * A callback function invoked whenever MultiFilter filter groups
139             * are parsed. This occurs whenever the user interacts with filter
140             * group UI, or when the `parseFilterGroups()` API method is called,
141             * but before the resulting filter operation has been triggered.
142             *
143             * By default, this generates the appropriate compound selector and
144             * filters the mixer using a `multimix()` API call internally. This
145             * callback can be used to transform the multimix command object sent
146             * to this API call.
147             *
148             * This is particularly useful when additional behavior such as sorting
149             * or pagination must be taken into account when using the MultiFilter API.
150             *
151             * The callback receives the generated multimix command object, and must
152             * also return a valid multimix command object.
153             *
154             * @example <caption>Example: Overriding the default filtering behavior with `onParseFilterGroups`</caption>
155             * var mixer = mixitup(containerEl, {
156             *     callbacks: {
157             *         onParseFilterGroups: function(command) {
158             *             command.paginate = 3;
159             *             command.sort = 'default:desc';
160             *
161             *             return command;
162             *         }
163             *     }
164             * });
165             *
166             * @name        onParseFilterGroups
167             * @memberof    mixitup.Config.callbacks
168             * @instance
169             * @type        {function}
170             * @default     null
171             */
172
173            this.onParseFilterGroups = null;
174        });
175
176        /**
177         * A group of properties defining the behavior of your multifilter UI.
178         *
179         * @constructor
180         * @memberof    mixitup.Config
181         * @name        multifilter
182         * @namespace
183         * @public
184         * @since       3.0.0
185         */
186
187        mixitup.ConfigMultifilter = function() {
188
189            /**
190             * A boolean dictating whether or not to enable multifilter functionality.
191             *
192             * If `true`, MixItUp will query the DOM for any elements with a
193             * `data-filter-group` attribute present on instantiation.
194             *
195             * @name        enable
196             * @memberof    mixitup.Config.multifilter
197             * @instance
198             * @type        {boolean}
199             * @default     false
200             */
201
202            this.enable = false;
203
204            /**
205             * A string dictating the logic to use when concatenating selectors within
206             * individual filter groups.
207             *
208             * If set to `'or'` (default), targets will be shown if they match any
209             * active filter in the group.
210             *
211             * If set to `'and'`, targets will be shown only if they match
212             * all active filters in the group.
213             *
214             * @name        logicWithinGroup
215             * @memberof    mixitup.Config.multifilter
216             * @instance
217             * @type        {string}
218             * @default     'or'
219             */
220
221            this.logicWithinGroup = 'or';
222
223            /**
224             * A string dictating the logic to use when concatenating each group's
225             * selectors into one single selector.
226             *
227             * If set to `'and'` (default), targets will be shown only if they match
228             * the combined active selectors of all groups.
229             *
230             * If set to `'or'`, targets will be shown if they match the active selectors
231             * of any individual group.
232             *
233             * @name        logicBetweenGroups
234             * @memberof    mixitup.Config.multifilter
235             * @instance
236             * @type        {string}
237             * @default     'and'
238             */
239
240            this.logicBetweenGroups = 'and';
241
242            /**
243             * An integer dictating the minimum number of characters at which the value
244             * of a text input will be included as a multifilter. This prevents short or
245             * incomplete words with many potential matches from triggering
246             * filter operations.
247             *
248             * @name        minSearchLength
249             * @memberof    mixitup.Config.multifilter
250             * @instance
251             * @type        {number}
252             * @default     3
253             */
254
255            this.minSearchLength = 3;
256
257            /**
258             * A string dictating when the parsing of filter groups should occur.
259             *
260             * If set to `'change'` (default), the mixer will be filtered whenever the
261             * filtering UI is interacted with. The mode provides real-time filtering with
262             * instant feedback.
263             *
264             * If set to `'submit'`, the mixer will only be filtered when a submit button is
265             * clicked (if using a `<form>` element as a parent). This enables the user to firstly
266             * make their selection, and then trigger filtering once they have
267             * finished making their selection.
268             *
269             * Alternatively, the `mixer.parseFilterGroups()` method can be called via the API at any
270             * time to trigger the parsing of filter groups and filter the mixer.
271             *
272             * @name        parseOn
273             * @memberof    mixitup.Config.multifilter
274             * @instance
275             * @type        {string}
276             * @default     'change'
277             */
278
279            this.parseOn = 'change';
280
281            /**
282             * An integer dictating the duration in ms that must elapse between keyup
283             * events in order to trigger a change.
284             *
285             * Setting a comfortable delay of ~350ms prevents the mixer from being
286             * thrashed while typing occurs.
287             *
288             * @name        keyupThrottleDuration
289             * @memberof    mixitup.Config.multifilter
290             * @instance
291             * @type        {number}
292             * @default     350
293             */
294
295            this.keyupThrottleDuration = 350;
296
297            h.seal(this);
298        };
299
300        /**
301         * The MixItUp configuration object is extended with properties relating to
302         * the MultiFilter extension.
303         *
304         * For the full list of configuration options, please refer to the MixItUp
305         * core documentation.
306         *
307         * @constructor
308         * @memberof    mixitup
309         * @name        Config
310         * @namespace
311         * @public
312         * @since       2.0.0
313         */
314
315        mixitup.Config.registerAction('beforeConstruct', 'multifilter', function() {
316            this.multifilter = new mixitup.ConfigMultifilter();
317        });
318
319        mixitup.MultifilterFormEventTracker = function() {
320            this.form           = null;
321            this.totalBound     = 0;
322            this.totalHandled   = 0;
323
324            h.seal(this);
325        };
326
327        mixitup.FilterGroupDom = function() {
328            this.el     = null;
329            this.form   = null;
330
331            h.seal(this);
332        };
333
334        mixitup.FilterGroup = function() {
335            this.name               = '';
336            this.dom                = new mixitup.FilterGroupDom();
337            this.activeSelectors    = [];
338            this.activeFilters      = [];
339            this.activeToggles      = [];
340            this.handler            = null;
341            this.mixer              = null;
342            this.logic              = 'or';
343            this.parseOn            = 'change';
344            this.keyupTimeout       = -1;
345
346            h.seal(this);
347        };
348
349        h.extend(mixitup.FilterGroup.prototype, {
350
351            /**
352             * @private
353             * @param {HTMLELement}     el
354             * @param {mixitup.Mixer}   mixer
355             * @return {void}
356             */
357
358            init: function(el, mixer) {
359                var self  = this,
360                    logic = el.getAttribute('data-logic');
361
362                self.dom.el = el;
363
364                this.name = self.dom.el.getAttribute('data-filter-group') || '';
365
366                self.cacheDom();
367
368                if (self.dom.form) {
369                    self.enableButtons();
370                }
371
372                self.mixer = mixer;
373
374                if ((logic && logic.toLowerCase() === 'and') || mixer.config.multifilter.logicWithinGroup === 'and') {
375                    // override default group logic
376
377                    self.logic = 'and';
378
379                }
380
381                self.bindEvents();
382            },
383
384            /**
385             * @private
386             * @return {void}
387             */
388
389            cacheDom: function() {
390                var self = this;
391
392                self.dom.form = h.closestParent(self.dom.el, 'form', true);
393            },
394
395            enableButtons: function() {
396                var self    = this,
397                    buttons = self.dom.form.querySelectorAll('button[type="submit"]:disabled'),
398                    button  = null,
399                    i       = -1;
400
401                for (i = 0; button = buttons[i]; i++) {
402                    if (button.disabled) {
403                        button.disabled = false;
404                    }
405                }
406            },
407
408            /**
409             * @private
410             * @return {void}
411             */
412
413            bindEvents: function() {
414                var self = this;
415
416                self.handler = function(e) {
417                    switch (e.type) {
418                        case 'reset':
419                        case 'submit':
420                            self.handleFormEvent(e);
421
422                            break;
423                        default:
424                            self['handle' + h.pascalCase(e.type)](e);
425                    }
426                };
427
428                h.on(self.dom.el, 'click', self.handler);
429                h.on(self.dom.el, 'change', self.handler);
430                h.on(self.dom.el, 'keyup', self.handler);
431
432                if (self.dom.form) {
433                    h.on(self.dom.form, 'reset', self.handler);
434                    h.on(self.dom.form, 'submit', self.handler);
435                }
436            },
437
438            /**
439             * @private
440             * @return {void}
441             */
442
443            unbindEvents: function() {
444                var self = this;
445
446                h.off(self.dom.el, 'click', self.handler);
447                h.off(self.dom.el, 'change', self.handler);
448                h.off(self.dom.el, 'keyup', self.handler);
449
450                if (self.dom.form) {
451                    h.off(self.dom.form, 'reset', self.handler);
452                    h.off(self.dom.form, 'submit', self.handler);
453                }
454
455                self.handler = null;
456            },
457
458            /**
459             * @private
460             * @param   {MouseEvent} e
461             * @return  {void}
462             */
463
464            handleClick: function(e) {
465                var self            = this,
466                    mixer           = self.mixer,
467                    returnValue     = null,
468                    controlEl       = h.closestParent(e.target, '[data-filter], [data-toggle]', true),
469                    controlSelector = '',
470                    index           = -1,
471                    selector        = '';
472
473                if (!controlEl) return;
474
475                if ((controlSelector = self.mixer.config.selectors.control) && !controlEl.matches(controlSelector)) {
476                    return;
477                }
478
479                e.stopPropagation();
480
481                if (!mixer.lastClicked) {
482                    mixer.lastClicked = controlEl;
483                }
484
485                if (typeof mixer.config.callbacks.onMixClick === 'function') {
486                    returnValue = mixer.config.callbacks.onMixClick.call(mixer.lastClicked, mixer.state, e, self);
487
488                    if (returnValue === false) {
489                        // User has returned `false` from the callback, so do not handle click
490
491                        return;
492                    }
493                }
494
495                if (controlEl.matches('[data-filter]')) {
496                    selector = controlEl.getAttribute('data-filter');
497
498                    self.activeToggles = [];
499                    self.activeSelectors = self.activeFilters = [selector];
500                } else if (controlEl.matches('[data-toggle]')) {
501                    selector = controlEl.getAttribute('data-toggle');
502
503                    self.activeFilters = [];
504
505                    if ((index = self.activeToggles.indexOf(selector)) > -1) {
506                        self.activeToggles.splice(index, 1);
507                    } else {
508                        self.activeToggles.push(selector);
509                    }
510
511                    if (self.logic === 'and') {
512                        // Compress into single node
513
514                        self.activeSelectors = [self.activeToggles];
515                    } else {
516                        self.activeSelectors = self.activeToggles;
517                    }
518                }
519
520                self.updateControls();
521
522                if (self.mixer.config.multifilter.parseOn === 'change') {
523                    self.mixer.parseFilterGroups();
524                }
525            },
526
527            /**
528             * @private
529             * @param   {Event} e
530             * @return  {void}
531             */
532
533            handleChange: function(e) {
534                var self    = this,
535                    input   = e.target;
536
537                e.stopPropagation();
538
539                switch(input.type) {
540                    case 'text':
541                    case 'search':
542                    case 'email':
543                    case 'select-one':
544                    case 'radio':
545                        self.getSingleValue(input);
546
547                        break;
548                    case 'checkbox':
549                    case 'select-multiple':
550                        self.getMultipleValues(input);
551
552                        break;
553                }
554
555                if (self.mixer.config.multifilter.parseOn === 'change') {
556                    self.mixer.parseFilterGroups();
557                }
558            },
559
560            handleKeyup: function(e) {
561                var self    = this,
562                    input   = e.target;
563
564                // NB: Selects can fire keyup events (e.g. multiselect, textual search)
565
566                if (['text', 'search', 'email'].indexOf(input.type) < 0) return;
567
568                if (self.mixer.config.multifilter.parseOn !== 'change') {
569                    self.mixer.getSingleValue(input);
570
571                    return;
572                }
573
574                clearTimeout(self.keyupTimeout);
575
576                self.keyupTimeout = setTimeout(function() {
577                    self.getSingleValue(input);
578                    self.mixer.parseFilterGroups();
579                }, self.mixer.config.multifilter.keyupThrottleDuration);
580            },
581
582            /**
583             * @private
584             * @param   {Event} e
585             * @return  {void}
586             */
587
588            handleFormEvent: function(e) {
589                var self            = this,
590                    tracker         = null,
591                    group           = null,
592                    i               = -1;
593
594                if (e.type === 'submit') {
595                    e.preventDefault();
596                }
597
598                if (e.type === 'reset') {
599                    self.activeFilters    =
600                    self.activeToggles   =
601                    self.activeSelectors = [];
602
603                    self.updateControls();
604                }
605
606                if (!self.mixer.multifilterFormEventTracker) {
607                    tracker = self.mixer.multifilterFormEventTracker = new mixitup.MultifilterFormEventTracker();
608
609                    tracker.form = e.target;
610
611                    for (i = 0; group = self.mixer.filterGroups[i]; i++) {
612                        if (group.dom.form !== e.target) continue;
613
614                        tracker.totalBound++;
615                    }
616                } else {
617                    tracker = self.mixer.multifilterFormEventTracker;
618                }
619
620                if (e.target === tracker.form) {
621                    tracker.totalHandled++;
622
623                    if (tracker.totalHandled === tracker.totalBound) {
624                        self.mixer.multifilterFormEventTracker = null;
625
626                        if (e.type === 'submit' || self.mixer.config.multifilter.parseOn === 'change') {
627                            self.mixer.parseFilterGroups();
628                        }
629                    }
630                }
631            },
632
633            /**
634             * @private
635             * @param   {HTMLELement} input
636             * @return  {void}
637             */
638
639            getSingleValue: function(input) {
640                var self            = this,
641                    diacriticMap    = null,
642                    attributeName   = '',
643                    selector        = '',
644                    value           = '',
645                    i               = -1;
646
647                if (input.type.match(/text|search|email/g)) {
648                    attributeName = input.getAttribute('data-search-attribute');
649
650                    if (!attributeName) {
651                        throw new Error('[MixItUp MultiFilter] A valid `data-search-attribute` must be present on text inputs');
652                    }
653
654                    if (input.value.length < self.mixer.config.multifilter.minSearchLength) {
655                        self.activeSelectors = self.activeFilters = self.activeToggles = [''];
656
657                        return;
658                    }
659
660                    // Lowercase and trim
661
662                    value = input.value.toLowerCase().trim();
663
664                    // Replace diacritics
665
666                    for (i = 0; (diacriticMap = diacriticsMap[i]); i++) {
667                        value = value.replace(diacriticMap[1], diacriticMap[0]);
668                    }
669
670                    // Strip non-word characters
671
672                    value = value.replace(/\W+/g, ' ');
673
674                    selector = '[' + attributeName + '*="' + value + '"]';
675                } else {
676                    selector = input.value;
677                }
678
679                if (typeof input.value === 'string') {
680                    self.activeSelectors =
681                    self.activeToggles =
682                    self.activeFilters =
683                            selector ? [selector] : [];
684                }
685            },
686
687            /**
688             * @private
689             * @param   {HTMLELement} input
690             * @return  {void}
691             */
692
693            getMultipleValues: function(input) {
694                var self            = this,
695                    activeToggles   = [],
696                    query           = '',
697                    item            = null,
698                    items           = null,
699                    i               = -1;
700
701                switch (input.type) {
702                    case 'checkbox':
703                        query = 'input[type="checkbox"]';
704
705                        break;
706                    case 'select-multiple':
707                        query = 'option';
708                }
709
710                items = self.dom.el.querySelectorAll(query);
711
712                for (i = 0; item = items[i]; i++) {
713                    if ((item.checked || item.selected) && item.value) {
714                        activeToggles.push(item.value);
715                    }
716                }
717
718                self.activeFilters = [];
719                self.activeToggles = activeToggles;
720
721                if (self.logic === 'and') {
722                    // Compress into single node
723
724                    self.activeSelectors = [activeToggles];
725                } else {
726                    self.activeSelectors = activeToggles;
727                }
728            },
729
730            /**
731             * @private
732             * @param   {Array.<HTMLELement>} [controlEls]
733             * @return  {void}
734             */
735
736            updateControls: function(controlEls) {
737                var self             = this,
738                    controlEl        = null,
739                    controlSelector  = '',
740                    controlsSelector = '',
741                    type             = '',
742                    i                = -1;
743
744                controlSelector = self.mixer.config.selectors.control.trim();
745
746                controlsSelector = [
747                    '[data-filter]' + controlSelector,
748                    '[data-toggle]' + controlSelector
749                ].join(', ');
750
751                controlEls = controlEls || self.dom.el.querySelectorAll(controlsSelector);
752
753                for (i = 0; controlEl = controlEls[i]; i++) {
754                    type = Boolean(controlEl.getAttribute('data-toggle')) ? 'toggle' : 'filter';
755
756                    self.updateControl(controlEl, type);
757                }
758            },
759
760            /**
761             * @private
762             * @param   {HTMLELement}   controlEl
763             * @param   {string}        type
764             * @return  {void}
765             */
766
767            updateControl: function(controlEl, type) {
768                var self            = this,
769                    selector        = controlEl.getAttribute('data-' + type),
770                    activeControls  = self.activeToggles.concat(self.activeFilters),
771                    activeClassName = '';
772
773                activeClassName = h.getClassname(self.mixer.config.classNames, type, self.mixer.config.classNames.modifierActive);
774
775                if (activeControls.indexOf(selector) > -1) {
776                    h.addClass(controlEl, activeClassName);
777                } else {
778                    h.removeClass(controlEl, activeClassName);
779                }
780            },
781
782            /**
783             * @private
784             */
785
786            updateUi: function() {
787                var self           = this,
788                    controlEls     = self.dom.el.querySelectorAll('[data-filter], [data-toggle]'),
789                    inputEls       = self.dom.el.querySelectorAll('input[type="radio"], input[type="checkbox"], option'),
790                    activeControls = self.activeToggles.concat(self.activeFilters),
791                    isActive       = false,
792                    inputEl        = null,
793                    i              = -1;
794
795                if (controlEls.length) {
796                    self.updateControls(controlEls, true);
797                }
798
799                for (i = 0; inputEl = inputEls[i]; i++) {
800                    isActive = activeControls.indexOf(inputEl.value) > -1;
801
802                    switch (inputEl.tagName.toLowerCase()) {
803                        case 'option':
804                            inputEl.selected = isActive;
805
806                            break;
807                        case 'input':
808                            inputEl.checked = isActive;
809
810                            break;
811                    }
812                }
813            }
814        });
815
816        mixitup.MixerDom.registerAction('afterConstruct', 'multifilter', function() {
817            this.filterGroups = [];
818        });
819
820        /**
821         * The `mixitup.Mixer` class is extended with API methods relating to
822         * the MultiFilter extension.
823         *
824         * For the full list of API methods, please refer to the MixItUp
825         * core documentation.
826         *
827         * @constructor
828         * @namespace
829         * @name        Mixer
830         * @memberof    mixitup
831         * @public
832         * @since       3.0.0
833         */
834
835        mixitup.Mixer.registerAction('afterConstruct', 'multifilter', function() {
836            this.filterGroups                   = [];
837            this.filterGroupsHash               = {};
838            this.multifilterFormEventTracker    = null;
839        });
840
841        mixitup.Mixer.registerAction('afterCacheDom', 'multifilter', function() {
842            var self    = this,
843                parent  = null;
844
845            if (!self.config.multifilter.enable) return;
846
847            switch (self.config.controls.scope) {
848                case 'local':
849                    parent = self.dom.container;
850
851                    break;
852                case 'global':
853                    parent = self.dom.document;
854
855                    break;
856                default:
857                    throw new Error(mixitup.messages.ERROR_CONFIG_INVALID_CONTROLS_SCOPE);
858            }
859
860            self.dom.filterGroups = parent.querySelectorAll('[data-filter-group]');
861        });
862
863        mixitup.Mixer.registerAction('beforeInitControls', 'multifilter', function() {
864            var self = this;
865
866            if (!self.config.multifilter.enable) return;
867
868            self.config.controls.live = true; // force live controls if multifilter is enabled
869        });
870
871        mixitup.Mixer.registerAction('afterSanitizeConfig', 'multifilter', function() {
872            var self = this;
873
874            self.config.multifilter.logicBetweenGroups = self.config.multifilter.logicBetweenGroups.toLowerCase().trim();
875            self.config.multifilter.logicWithinGroup = self.config.multifilter.logicWithinGroup.toLowerCase().trim();
876        });
877
878        mixitup.Mixer.registerAction('afterAttach', 'multifilter', function() {
879            var self = this;
880
881            if (self.dom.filterGroups.length) {
882                self.indexFilterGroups();
883            }
884        });
885
886        mixitup.Mixer.registerAction('afterUpdateControls', 'multifilter', function() {
887            var self    = this,
888                group   = null,
889                i       = -1;
890
891            for (i = 0; group = self.filterGroups[i]; i++) {
892                group.updateControls();
893            }
894        });
895
896        mixitup.Mixer.registerAction('beforeDestroy', 'multifilter', function() {
897            var self    = this,
898                group   = null,
899                i       = -1;
900
901            for (i = 0; group = self.filterGroups[i]; i++) {
902                group.unbindEvents();
903            }
904        });
905
906        mixitup.Mixer.extend(
907        /** @lends mixitup.Mixer */
908        {
909            /**
910             * @private
911             * @return {void}
912             */
913
914            indexFilterGroups: function() {
915                var self                = this,
916                    filterGroup         = null,
917                    el                  = null,
918                    i                   = -1;
919
920                for (i = 0; el = self.dom.filterGroups[i]; i++) {
921                    filterGroup = new mixitup.FilterGroup();
922
923                    filterGroup.init(el, self);
924
925                    self.filterGroups.push(filterGroup);
926
927                    if (filterGroup.name) {
928                        // If present, also index by name
929
930                        if (typeof self.filterGroupsHash[filterGroup.name] !== 'undefined') {
931                            throw new Error('[MixItUp MultiFilter] A filter group with name "' + filterGroup.name + '" already exists');
932                        }
933
934                        self.filterGroupsHash[filterGroup.name] = filterGroup;
935                    }
936                }
937            },
938
939            /**
940             * @private
941             * @instance
942             * @since   2.0.0
943             * @param   {Array<*>}  args
944             * @return  {mixitup.UserInstruction}
945             */
946
947            parseParseFilterGroupsArgs: function(args) {
948                var self        = this,
949                    instruction = new mixitup.UserInstruction(),
950                    arg         = null,
951                    i           = -1;
952
953                instruction.animate = self.config.animation.enable;
954                instruction.command = new mixitup.CommandFilter();
955
956                for (i = 0; i < args.length; i++) {
957                    arg = args[i];
958
959                    if (typeof arg === 'boolean') {
960                        instruction.animate = arg;
961                    } else if (typeof arg === 'function') {
962                        instruction.callback = arg;
963                    }
964                }
965
966                h.freeze(instruction);
967
968                return instruction;
969            },
970
971            /**
972             * Recursively builds up paths between all possible permutations
973             * of filter group nodes according to the defined logic.
974             *
975             * @private
976             * @return {Array.<Array.<string>>}
977             */
978
979            getFilterGroupPaths: function() {
980                var self       = this,
981                    buildPath  = null,
982                    crawl      = null,
983                    nodes      = null,
984                    matrix     = [],
985                    paths      = [],
986                    trackers   = [],
987                    i          = -1;
988
989                for (i = 0; i < self.filterGroups.length; i++) {
990                    // Filter out groups without any active filters
991
992                    if ((nodes = self.filterGroups[i].activeSelectors).length) {
993                        matrix.push(nodes);
994
995                        // Initialise tracker for each group
996
997                        trackers.push(0);
998                    }
999                }
1000
1001                buildPath = function() {
1002                    var node = null,
1003                        path = [],
1004                        i    = -1;
1005
1006                    for (i = 0; i < matrix.length; i++) {
1007                        node = matrix[i][trackers[i]];
1008
1009                        if (Array.isArray(node)) {
1010                            // AND logic within group
1011
1012                            node = node.join('');
1013                        }
1014
1015                        path.push(node);
1016                    }
1017
1018                    path = h.clean(path);
1019
1020                    paths.push(path);
1021                };
1022
1023                crawl = function(index) {
1024                    index = index || 0;
1025
1026                    var nodes = matrix[index];
1027
1028                    while (trackers[index] < nodes.length) {
1029                        if (index < matrix.length - 1) {
1030                            // If not last, recurse
1031
1032                            crawl(index + 1);
1033                        } else {
1034                            // Last, build selector
1035
1036                            buildPath();
1037                        }
1038
1039                        trackers[index]++;
1040                    }
1041
1042                    trackers[index] = 0;
1043                };
1044
1045                if (!matrix.length) return '';
1046
1047                crawl();
1048
1049                return paths;
1050            },
1051
1052            /**
1053             * Builds up a selector string from a provided paths array.
1054             *
1055             * @private
1056             * @param  {Array.<Array.<string>>} paths
1057             * @return {string}
1058             */
1059
1060            buildSelectorFromPaths: function(paths) {
1061                var self           = this,
1062                    path           = null,
1063                    output         = [],
1064                    pathSelector   = '',
1065                    nodeDelineator = '',
1066                    i              = -1;
1067
1068                if (!paths.length) {
1069                    return '';
1070                }
1071
1072                if (self.config.multifilter.logicBetweenGroups === 'or') {
1073                    nodeDelineator = ', ';
1074                }
1075
1076                if (paths.length > 1) {
1077                    for (i = 0; i < paths.length; i++) {
1078                        path = paths[i];
1079
1080                        pathSelector = path.join(nodeDelineator);
1081
1082                        if (output.indexOf(pathSelector) < 0) {
1083                            output.push(pathSelector);
1084                        }
1085                    }
1086
1087                    return output.join(', ');
1088                } else {
1089                    return paths[0].join(nodeDelineator);
1090                }
1091            },
1092
1093            /**
1094             * Traverses the currently active filters in all groups, building up a
1095             * compound selector string as per the defined logic. A filter operation
1096             * is then called on the mixer using the resulting selector.
1097             *
1098             * This method can be used to programmatically trigger the parsing of
1099             * filter groups after manipulations to a group's active selector(s) by
1100             * the `.setFilterGroupSelectors()` API method.
1101             *
1102             * @example
1103             *
1104             * .parseFilterGroups([animate] [, callback])
1105             *
1106             * @example <caption>Example: Triggering parsing after programmatically changing the values of a filter group</caption>
1107             *
1108             * mixer.setFilterGroupSelectors('color', ['.green', '.blue']);
1109             *
1110             * mixer.parseFilterGroups();
1111             *
1112             * @public
1113             * @since 3.0.0
1114             * @param       {boolean}   [animate=true]
1115             *      An optional boolean dictating whether the operation should animate, or occur syncronously with no animation. `true` by default.
1116             * @param       {function}  [callback=null]
1117             *      An optional callback function to be invoked after the operation has completed.
1118             * @return      {Promise.<mixitup.State>}
1119             *      A promise resolving with the current state object.
1120             */
1121
1122            parseFilterGroups: function() {
1123                var self        = this,
1124                    instruction = self.parseFilterArgs(arguments),
1125                    paths       = self.getFilterGroupPaths(),
1126                    selector    = self.buildSelectorFromPaths(paths),
1127                    callback    = null,
1128                    command     = {};
1129
1130                if (selector === '') {
1131                    selector = self.config.controls.toggleDefault;
1132                }
1133
1134                instruction.command.selector = selector;
1135
1136                command.filter = instruction.command;
1137
1138                if (typeof (callback = self.config.callbacks.onParseFilterGroups) === 'function') {
1139                    command = callback(command);
1140                }
1141
1142                return self.multimix(command, instruction.animate, instruction.callback);
1143            },
1144
1145            /**
1146             * Programmatically sets one or more active selectors for a specific filter
1147             * group and updates the group's UI.
1148             *
1149             * Because MixItUp has no way of knowing how to break down a provided
1150             * compound selector into its component groups, we can not use the
1151             * standard `.filter()` or `toggleOn()/toggleOff()` API methods when using
1152             * the MultiFilter extension. Instead, this method allows us to perform
1153             * multi-dimensional filtering via the API by setting the active selectors of
1154             * individual groups and then triggering the `.parseFilterGroups()` method.
1155             *
1156             * If setting multiple active selectors, do not pass a compound selector.
1157             * Instead, pass an array with each item containing a single selector
1158             * string as in example 2.
1159             *
1160             * @example
1161             *
1162             * .setFilterGroupSelectors(groupName, selectors)
1163             *
1164             * @example <caption>Example 1: Setting a single active selector for a "color" group</caption>
1165             *
1166             * mixer.setFilterGroupSelectors('color', '.green');
1167             *
1168             * mixer.parseFilterGroups();
1169             *
1170             * @example <caption>Example 2: Setting multiple active selectors for a "size" group</caption>
1171             *
1172             * mixer.setFilterGroupSelectors('size', ['.small', '.large']);
1173             *
1174             * mixer.parseFilterGroups();
1175             *
1176             * @public
1177             * @since   3.2.0
1178             * @param   {string}                    groupName   The name of the filter group as defined in the markup via the `data-filter-group` attribute.
1179             * @param   {(string|Array.<string>)}   selectors   A single selector string, or multiple selector strings as an array.
1180             * @return  {void}
1181             */
1182
1183            setFilterGroupSelectors: function(groupName, selectors) {
1184                var self            = this,
1185                    filterGroup     = null;
1186
1187                selectors = Array.isArray(selectors) ? selectors : [selectors];
1188
1189                if (typeof (filterGroup = self.filterGroupsHash[groupName]) === 'undefined') {
1190                    throw new Error('[MixItUp MultiFilter] No filter group could be found with the name "' + groupName + '"');
1191                }
1192
1193                filterGroup.activeToggles = selectors.slice();
1194
1195                if (filterGroup.logic === 'and') {
1196                    // Compress into single node
1197
1198                    filterGroup.activeSelectors = [filterGroup.activeToggles];
1199                } else {
1200                    filterGroup.activeSelectors = filterGroup.activeToggles;
1201                }
1202
1203                filterGroup.updateUi(filterGroup.activeToggles);
1204            },
1205
1206            /**
1207             * Returns an array of active selectors for a specific filter group.
1208             *
1209             * @example
1210             *
1211             * .getFilterGroupSelectors(groupName)
1212             *
1213             * @example <caption>Example: Retrieving the active selectors for a "size" group</caption>
1214             *
1215             * mixer.getFilterGroupSelectors('size'); // ['.small', '.large']
1216             *
1217             * @public
1218             * @since   3.2.0
1219             * @param   {string}    groupName   The name of the filter group as defined in the markup via the `data-filter-group` attribute.
1220             * @return  {void}
1221             */
1222
1223            getFilterGroupSelectors: function(groupName) {
1224                var self        = this,
1225                    filterGroup = null;
1226
1227                if (typeof (filterGroup = self.filterGroupsHash[groupName]) === 'undefined') {
1228                    throw new Error('[MixItUp MultiFilter] No filter group could be found with the name "' + groupName + '"');
1229                }
1230
1231                return filterGroup.activeSelectors.slice();
1232            }
1233        });
1234
1235        mixitup.Facade.registerAction('afterConstruct', 'multifilter', function(mixer) {
1236            this.parseFilterGroups       = mixer.parseFilterGroups.bind(mixer);
1237            this.setFilterGroupSelectors = mixer.setFilterGroupSelectors.bind(mixer);
1238            this.getFilterGroupSelectors = mixer.getFilterGroupSelectors.bind(mixer);
1239        });    };
1240
1241    mixitupMultifilter.TYPE                    = 'mixitup-extension';
1242    mixitupMultifilter.NAME                    = 'mixitup-multifilter';
1243    mixitupMultifilter.EXTENSION_VERSION       = '3.3.
12436';
1244    mixitupMultifilter.REQUIRE_CORE_VERSION    = '^3.1.2';
1245
1246    if (typeof exports === 'object' && typeof module === 'object') {
1247        module.exports = mixitupMultifilter;
1248    } else if (typeof define === 'function' && define.amd) {
1249        define(function() {
1250            return mixitupMultifilter;
1251        });
1252    } else if (window.mixitup && typeof window.mixitup === 'function') {
1253        mixitupMultifilter(window.mixitup);
1254    } else {
1255        throw new Error('[MixItUp MultiFilter] MixItUp core not found');
1256    }})(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.