shuffle.js 69 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191
  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  3. typeof define === 'function' && define.amd ? define(factory) :
  4. (global = global || self, global.Shuffle = factory());
  5. }(this, function () {
  6. 'use strict';
  7. function _classCallCheck(instance, Constructor) {
  8. if (!(instance instanceof Constructor)) {
  9. throw new TypeError("Cannot call a class as a function");
  10. }
  11. }
  12. function _defineProperties(target, props) {
  13. for (var i = 0; i < props.length; i++) {
  14. var descriptor = props[i];
  15. descriptor.enumerable = descriptor.enumerable || false;
  16. descriptor.configurable = true;
  17. if ("value" in descriptor) descriptor.writable = true;
  18. Object.defineProperty(target, descriptor.key, descriptor);
  19. }
  20. }
  21. function _createClass(Constructor, protoProps, staticProps) {
  22. if (protoProps) _defineProperties(Constructor.prototype, protoProps);
  23. if (staticProps) _defineProperties(Constructor, staticProps);
  24. return Constructor;
  25. }
  26. function _inherits(subClass, superClass) {
  27. if (typeof superClass !== "function" && superClass !== null) {
  28. throw new TypeError("Super expression must either be null or a function");
  29. }
  30. subClass.prototype = Object.create(superClass && superClass.prototype, {
  31. constructor: {
  32. value: subClass,
  33. writable: true,
  34. configurable: true
  35. }
  36. });
  37. if (superClass) _setPrototypeOf(subClass, superClass);
  38. }
  39. function _getPrototypeOf(o) {
  40. _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
  41. return o.__proto__ || Object.getPrototypeOf(o);
  42. };
  43. return _getPrototypeOf(o);
  44. }
  45. function _setPrototypeOf(o, p) {
  46. _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
  47. o.__proto__ = p;
  48. return o;
  49. };
  50. return _setPrototypeOf(o, p);
  51. }
  52. function _assertThisInitialized(self) {
  53. if (self === void 0) {
  54. throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  55. }
  56. return self;
  57. }
  58. function _possibleConstructorReturn(self, call) {
  59. if (call && (typeof call === "object" || typeof call === "function")) {
  60. return call;
  61. }
  62. return _assertThisInitialized(self);
  63. }
  64. function E() {
  65. // Keep this empty so it's easier to inherit from
  66. // (via https://github.com/lipsmack from https://github.com/scottcorgan/tiny-emitter/issues/3)
  67. }
  68. E.prototype = {
  69. on: function (name, callback, ctx) {
  70. var e = this.e || (this.e = {});
  71. (e[name] || (e[name] = [])).push({
  72. fn: callback,
  73. ctx: ctx
  74. });
  75. return this;
  76. },
  77. once: function (name, callback, ctx) {
  78. var self = this;
  79. function listener() {
  80. self.off(name, listener);
  81. callback.apply(ctx, arguments);
  82. }
  83. listener._ = callback;
  84. return this.on(name, listener, ctx);
  85. },
  86. emit: function (name) {
  87. var data = [].slice.call(arguments, 1);
  88. var evtArr = ((this.e || (this.e = {}))[name] || []).slice();
  89. var i = 0;
  90. var len = evtArr.length;
  91. for (i; i < len; i++) {
  92. evtArr[i].fn.apply(evtArr[i].ctx, data);
  93. }
  94. return this;
  95. },
  96. off: function (name, callback) {
  97. var e = this.e || (this.e = {});
  98. var evts = e[name];
  99. var liveEvents = [];
  100. if (evts && callback) {
  101. for (var i = 0, len = evts.length; i < len; i++) {
  102. if (evts[i].fn !== callback && evts[i].fn._ !== callback)
  103. liveEvents.push(evts[i]);
  104. }
  105. }
  106. // Remove event from queue to prevent memory leak
  107. // Suggested by https://github.com/lazd
  108. // Ref: https://github.com/scottcorgan/tiny-emitter/commit/c6ebfaa9bc973b33d110a84a307742b7cf94c953#commitcomment-5024910
  109. (liveEvents.length)
  110. ? e[name] = liveEvents
  111. : delete e[name];
  112. return this;
  113. }
  114. };
  115. var tinyEmitter = E;
  116. var TinyEmitter = E;
  117. tinyEmitter.TinyEmitter = TinyEmitter;
  118. var proto = typeof Element !== 'undefined' ? Element.prototype : {};
  119. var vendor = proto.matches
  120. || proto.matchesSelector
  121. || proto.webkitMatchesSelector
  122. || proto.mozMatchesSelector
  123. || proto.msMatchesSelector
  124. || proto.oMatchesSelector;
  125. var matchesSelector = match;
  126. /**
  127. * Match `el` to `selector`.
  128. *
  129. * @param {Element} el
  130. * @param {String} selector
  131. * @return {Boolean}
  132. * @api public
  133. */
  134. function match(el, selector) {
  135. if (!el || el.nodeType !== 1) return false;
  136. if (vendor) return vendor.call(el, selector);
  137. var nodes = el.parentNode.querySelectorAll(selector);
  138. for (var i = 0; i < nodes.length; i++) {
  139. if (nodes[i] == el) return true;
  140. }
  141. return false;
  142. }
  143. var throttleit = throttle;
  144. /**
  145. * Returns a new function that, when invoked, invokes `func` at most once per `wait` milliseconds.
  146. *
  147. * @param {Function} func Function to wrap.
  148. * @param {Number} wait Number of milliseconds that must elapse between `func` invocations.
  149. * @return {Function} A new function that wraps the `func` function passed in.
  150. */
  151. function throttle(func, wait) {
  152. var ctx, args, rtn, timeoutID; // caching
  153. var last = 0;
  154. return function throttled() {
  155. ctx = this;
  156. args = arguments;
  157. var delta = new Date() - last;
  158. if (!timeoutID)
  159. if (delta >= wait) call();
  160. else timeoutID = setTimeout(call, wait - delta);
  161. return rtn;
  162. };
  163. function call() {
  164. timeoutID = 0;
  165. last = +new Date();
  166. rtn = func.apply(ctx, args);
  167. ctx = null;
  168. args = null;
  169. }
  170. }
  171. var arrayParallel = function parallel(fns, context, callback) {
  172. if (!callback) {
  173. if (typeof context === 'function') {
  174. callback = context;
  175. context = null;
  176. } else {
  177. callback = noop;
  178. }
  179. }
  180. var pending = fns && fns.length;
  181. if (!pending) return callback(null, []);
  182. var finished = false;
  183. var results = new Array(pending);
  184. fns.forEach(context ? function (fn, i) {
  185. fn.call(context, maybeDone(i));
  186. } : function (fn, i) {
  187. fn(maybeDone(i));
  188. });
  189. function maybeDone(i) {
  190. return function (err, result) {
  191. if (finished) return;
  192. if (err) {
  193. callback(err, results);
  194. finished = true;
  195. return
  196. }
  197. results[i] = result;
  198. if (!--pending) callback(null, results);
  199. }
  200. }
  201. };
  202. function noop() { }
  203. /**
  204. * Always returns a numeric value, given a value. Logic from jQuery's `isNumeric`.
  205. * @param {*} value Possibly numeric value.
  206. * @return {number} `value` or zero if `value` isn't numeric.
  207. */
  208. function getNumber(value) {
  209. return parseFloat(value) || 0;
  210. }
  211. var Point =
  212. /*#__PURE__*/
  213. function () {
  214. /**
  215. * Represents a coordinate pair.
  216. * @param {number} [x=0] X.
  217. * @param {number} [y=0] Y.
  218. */
  219. function Point(x, y) {
  220. _classCallCheck(this, Point);
  221. this.x = getNumber(x);
  222. this.y = getNumber(y);
  223. }
  224. /**
  225. * Whether two points are equal.
  226. * @param {Point} a Point A.
  227. * @param {Point} b Point B.
  228. * @return {boolean}
  229. */
  230. _createClass(Point, null, [{
  231. key: "equals",
  232. value: function equals(a, b) {
  233. return a.x === b.x && a.y === b.y;
  234. }
  235. }]);
  236. return Point;
  237. }();
  238. var Rect =
  239. /*#__PURE__*/
  240. function () {
  241. /**
  242. * Class for representing rectangular regions.
  243. * https://github.com/google/closure-library/blob/master/closure/goog/math/rect.js
  244. * @param {number} x Left.
  245. * @param {number} y Top.
  246. * @param {number} w Width.
  247. * @param {number} h Height.
  248. * @param {number} id Identifier
  249. * @constructor
  250. */
  251. function Rect(x, y, w, h, id) {
  252. _classCallCheck(this, Rect);
  253. this.id = id;
  254. /** @type {number} */
  255. this.left = x;
  256. /** @type {number} */
  257. this.top = y;
  258. /** @type {number} */
  259. this.width = w;
  260. /** @type {number} */
  261. this.height = h;
  262. }
  263. /**
  264. * Returns whether two rectangles intersect.
  265. * @param {Rect} a A Rectangle.
  266. * @param {Rect} b A Rectangle.
  267. * @return {boolean} Whether a and b intersect.
  268. */
  269. _createClass(Rect, null, [{
  270. key: "intersects",
  271. value: function intersects(a, b) {
  272. return a.left < b.left + b.width && b.left < a.left + a.width && a.top < b.top + b.height && b.top < a.top + a.height;
  273. }
  274. }]);
  275. return Rect;
  276. }();
  277. var Classes = {
  278. BASE: 'shuffle',
  279. SHUFFLE_ITEM: 'shuffle-item',
  280. VISIBLE: 'shuffle-item--visible',
  281. HIDDEN: 'shuffle-item--hidden'
  282. };
  283. var id = 0;
  284. var ShuffleItem =
  285. /*#__PURE__*/
  286. function () {
  287. function ShuffleItem(element) {
  288. _classCallCheck(this, ShuffleItem);
  289. id += 1;
  290. this.id = id;
  291. this.element = element;
  292. /**
  293. * Used to separate items for layout and shrink.
  294. */
  295. this.isVisible = true;
  296. /**
  297. * Used to determine if a transition will happen. By the time the _layout
  298. * and _shrink methods get the ShuffleItem instances, the `isVisible` value
  299. * has already been changed by the separation methods, so this property is
  300. * needed to know if the item was visible/hidden before the shrink/layout.
  301. */
  302. this.isHidden = false;
  303. }
  304. _createClass(ShuffleItem, [{
  305. key: "show",
  306. value: function show() {
  307. this.isVisible = true;
  308. this.element.classList.remove(Classes.HIDDEN);
  309. this.element.classList.add(Classes.VISIBLE);
  310. this.element.removeAttribute('aria-hidden');
  311. }
  312. }, {
  313. key: "hide",
  314. value: function hide() {
  315. this.isVisible = false;
  316. this.element.classList.remove(Classes.VISIBLE);
  317. this.element.classList.add(Classes.HIDDEN);
  318. this.element.setAttribute('aria-hidden', true);
  319. }
  320. }, {
  321. key: "init",
  322. value: function init() {
  323. this.addClasses([Classes.SHUFFLE_ITEM, Classes.VISIBLE]);
  324. this.applyCss(ShuffleItem.Css.INITIAL);
  325. this.scale = ShuffleItem.Scale.VISIBLE;
  326. this.point = new Point();
  327. }
  328. }, {
  329. key: "addClasses",
  330. value: function addClasses(classes) {
  331. var _this = this;
  332. classes.forEach(function (className) {
  333. _this.element.classList.add(className);
  334. });
  335. }
  336. }, {
  337. key: "removeClasses",
  338. value: function removeClasses(classes) {
  339. var _this2 = this;
  340. classes.forEach(function (className) {
  341. _this2.element.classList.remove(className);
  342. });
  343. }
  344. }, {
  345. key: "applyCss",
  346. value: function applyCss(obj) {
  347. var _this3 = this;
  348. Object.keys(obj).forEach(function (key) {
  349. _this3.element.style[key] = obj[key];
  350. });
  351. }
  352. }, {
  353. key: "dispose",
  354. value: function dispose() {
  355. this.removeClasses([Classes.HIDDEN, Classes.VISIBLE, Classes.SHUFFLE_ITEM]);
  356. this.element.removeAttribute('style');
  357. this.element = null;
  358. }
  359. }]);
  360. return ShuffleItem;
  361. }();
  362. ShuffleItem.Css = {
  363. INITIAL: {
  364. position: 'absolute',
  365. top: 0,
  366. left: 0,
  367. visibility: 'visible',
  368. willChange: 'transform'
  369. },
  370. VISIBLE: {
  371. before: {
  372. opacity: 1,
  373. visibility: 'visible'
  374. },
  375. after: {
  376. transitionDelay: ''
  377. }
  378. },
  379. HIDDEN: {
  380. before: {
  381. opacity: 0
  382. },
  383. after: {
  384. visibility: 'hidden',
  385. transitionDelay: ''
  386. }
  387. }
  388. };
  389. ShuffleItem.Scale = {
  390. VISIBLE: 1,
  391. HIDDEN: 0.001
  392. };
  393. var value = null;
  394. var testComputedSize = (function () {
  395. if (value !== null) {
  396. return value;
  397. }
  398. var element = document.body || document.documentElement;
  399. var e = document.createElement('div');
  400. e.style.cssText = 'width:10px;padding:2px;box-sizing:border-box;';
  401. element.appendChild(e);
  402. value = window.getComputedStyle(e, null).width === '10px';
  403. element.removeChild(e);
  404. return value;
  405. });
  406. /**
  407. * Retrieve the computed style for an element, parsed as a float.
  408. * @param {Element} element Element to get style for.
  409. * @param {string} style Style property.
  410. * @param {CSSStyleDeclaration} [styles] Optionally include clean styles to
  411. * use instead of asking for them again.
  412. * @return {number} The parsed computed value or zero if that fails because IE
  413. * will return 'auto' when the element doesn't have margins instead of
  414. * the computed style.
  415. */
  416. function getNumberStyle(element, style) {
  417. var styles = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : window.getComputedStyle(element, null);
  418. var value = getNumber(styles[style]); // Support IE<=11 and W3C spec.
  419. if (!testComputedSize() && style === 'width') {
  420. value += getNumber(styles.paddingLeft) + getNumber(styles.paddingRight) + getNumber(styles.borderLeftWidth) + getNumber(styles.borderRightWidth);
  421. } else if (!testComputedSize() && style === 'height') {
  422. value += getNumber(styles.paddingTop) + getNumber(styles.paddingBottom) + getNumber(styles.borderTopWidth) + getNumber(styles.borderBottomWidth);
  423. }
  424. return value;
  425. }
  426. /**
  427. * Fisher-Yates shuffle.
  428. * http://stackoverflow.com/a/962890/373422
  429. * https://bost.ocks.org/mike/shuffle/
  430. * @param {Array} array Array to shuffle.
  431. * @return {Array} Randomly sorted array.
  432. */
  433. function randomize(array) {
  434. var n = array.length;
  435. while (n) {
  436. n -= 1;
  437. var i = Math.floor(Math.random() * (n + 1));
  438. var temp = array[i];
  439. array[i] = array[n];
  440. array[n] = temp;
  441. }
  442. return array;
  443. }
  444. var defaults = {
  445. // Use array.reverse() to reverse the results
  446. reverse: false,
  447. // Sorting function
  448. by: null,
  449. // Custom sort function
  450. compare: null,
  451. // If true, this will skip the sorting and return a randomized order in the array
  452. randomize: false,
  453. // Determines which property of each item in the array is passed to the
  454. // sorting method.
  455. key: 'element'
  456. };
  457. /**
  458. * You can return `undefined` from the `by` function to revert to DOM order.
  459. * @param {Array<T>} arr Array to sort.
  460. * @param {SortOptions} options Sorting options.
  461. * @return {Array<T>}
  462. */
  463. function sorter(arr, options) {
  464. var opts = Object.assign({}, defaults, options);
  465. var original = Array.from(arr);
  466. var revert = false;
  467. if (!arr.length) {
  468. return [];
  469. }
  470. if (opts.randomize) {
  471. return randomize(arr);
  472. } // Sort the elements by the opts.by function.
  473. // If we don't have opts.by, default to DOM order
  474. if (typeof opts.by === 'function') {
  475. arr.sort(function (a, b) {
  476. // Exit early if we already know we want to revert
  477. if (revert) {
  478. return 0;
  479. }
  480. var valA = opts.by(a[opts.key]);
  481. var valB = opts.by(b[opts.key]); // If both values are undefined, use the DOM order
  482. if (valA === undefined && valB === undefined) {
  483. revert = true;
  484. return 0;
  485. }
  486. if (valA < valB || valA === 'sortFirst' || valB === 'sortLast') {
  487. return -1;
  488. }
  489. if (valA > valB || valA === 'sortLast' || valB === 'sortFirst') {
  490. return 1;
  491. }
  492. return 0;
  493. });
  494. } else if (typeof opts.compare === 'function') {
  495. arr.sort(opts.compare);
  496. } // Revert to the original array if necessary
  497. if (revert) {
  498. return original;
  499. }
  500. if (opts.reverse) {
  501. arr.reverse();
  502. }
  503. return arr;
  504. }
  505. var transitions = {};
  506. var eventName = 'transitionend';
  507. var count = 0;
  508. function uniqueId() {
  509. count += 1;
  510. return eventName + count;
  511. }
  512. function cancelTransitionEnd(id) {
  513. if (transitions[id]) {
  514. transitions[id].element.removeEventListener(eventName, transitions[id].listener);
  515. transitions[id] = null;
  516. return true;
  517. }
  518. return false;
  519. }
  520. function onTransitionEnd(element, callback) {
  521. var id = uniqueId();
  522. var listener = function listener(evt) {
  523. if (evt.currentTarget === evt.target) {
  524. cancelTransitionEnd(id);
  525. callback(evt);
  526. }
  527. };
  528. element.addEventListener(eventName, listener);
  529. transitions[id] = {
  530. element: element,
  531. listener: listener
  532. };
  533. return id;
  534. }
  535. function arrayMax(array) {
  536. return Math.max.apply(Math, array); // eslint-disable-line prefer-spread
  537. }
  538. function arrayMin(array) {
  539. return Math.min.apply(Math, array); // eslint-disable-line prefer-spread
  540. }
  541. /**
  542. * Determine the number of columns an items spans.
  543. * @param {number} itemWidth Width of the item.
  544. * @param {number} columnWidth Width of the column (includes gutter).
  545. * @param {number} columns Total number of columns
  546. * @param {number} threshold A buffer value for the size of the column to fit.
  547. * @return {number}
  548. */
  549. function getColumnSpan(itemWidth, columnWidth, columns, threshold) {
  550. var columnSpan = itemWidth / columnWidth; // If the difference between the rounded column span number and the
  551. // calculated column span number is really small, round the number to
  552. // make it fit.
  553. if (Math.abs(Math.round(columnSpan) - columnSpan) < threshold) {
  554. // e.g. columnSpan = 4.0089945390298745
  555. columnSpan = Math.round(columnSpan);
  556. } // Ensure the column span is not more than the amount of columns in the whole layout.
  557. return Math.min(Math.ceil(columnSpan), columns);
  558. }
  559. /**
  560. * Retrieves the column set to use for placement.
  561. * @param {number} columnSpan The number of columns this current item spans.
  562. * @param {number} columns The total columns in the grid.
  563. * @return {Array.<number>} An array of numbers represeting the column set.
  564. */
  565. function getAvailablePositions(positions, columnSpan, columns) {
  566. // The item spans only one column.
  567. if (columnSpan === 1) {
  568. return positions;
  569. } // The item spans more than one column, figure out how many different
  570. // places it could fit horizontally.
  571. // The group count is the number of places within the positions this block
  572. // could fit, ignoring the current positions of items.
  573. // Imagine a 2 column brick as the second item in a 4 column grid with
  574. // 10px height each. Find the places it would fit:
  575. // [20, 10, 10, 0]
  576. // | | |
  577. // * * *
  578. //
  579. // Then take the places which fit and get the bigger of the two:
  580. // max([20, 10]), max([10, 10]), max([10, 0]) = [20, 10, 10]
  581. //
  582. // Next, find the first smallest number (the short column).
  583. // [20, 10, 10]
  584. // |
  585. // *
  586. //
  587. // And that's where it should be placed!
  588. //
  589. // Another example where the second column's item extends past the first:
  590. // [10, 20, 10, 0] => [20, 20, 10] => 10
  591. var available = []; // For how many possible positions for this item there are.
  592. for (var i = 0; i <= columns - columnSpan; i++) {
  593. // Find the bigger value for each place it could fit.
  594. available.push(arrayMax(positions.slice(i, i + columnSpan)));
  595. }
  596. return available;
  597. }
  598. /**
  599. * Find index of short column, the first from the left where this item will go.
  600. *
  601. * @param {Array.<number>} positions The array to search for the smallest number.
  602. * @param {number} buffer Optional buffer which is very useful when the height
  603. * is a percentage of the width.
  604. * @return {number} Index of the short column.
  605. */
  606. function getShortColumn(positions, buffer) {
  607. var minPosition = arrayMin(positions);
  608. for (var i = 0, len = positions.length; i < len; i++) {
  609. if (positions[i] >= minPosition - buffer && positions[i] <= minPosition + buffer) {
  610. return i;
  611. }
  612. }
  613. return 0;
  614. }
  615. /**
  616. * Determine the location of the next item, based on its size.
  617. * @param {Object} itemSize Object with width and height.
  618. * @param {Array.<number>} positions Positions of the other current items.
  619. * @param {number} gridSize The column width or row height.
  620. * @param {number} total The total number of columns or rows.
  621. * @param {number} threshold Buffer value for the column to fit.
  622. * @param {number} buffer Vertical buffer for the height of items.
  623. * @return {Point}
  624. */
  625. function getItemPosition(_ref) {
  626. var itemSize = _ref.itemSize,
  627. positions = _ref.positions,
  628. gridSize = _ref.gridSize,
  629. total = _ref.total,
  630. threshold = _ref.threshold,
  631. buffer = _ref.buffer;
  632. var span = getColumnSpan(itemSize.width, gridSize, total, threshold);
  633. var setY = getAvailablePositions(positions, span, total);
  634. var shortColumnIndex = getShortColumn(setY, buffer); // Position the item
  635. var point = new Point(gridSize * shortColumnIndex, setY[shortColumnIndex]); // Update the columns array with the new values for each column.
  636. // e.g. before the update the columns could be [250, 0, 0, 0] for an item
  637. // which spans 2 columns. After it would be [250, itemHeight, itemHeight, 0].
  638. var setHeight = setY[shortColumnIndex] + itemSize.height;
  639. for (var i = 0; i < span; i++) {
  640. positions[shortColumnIndex + i] = setHeight;
  641. }
  642. return point;
  643. }
  644. /**
  645. * This method attempts to center items. This method could potentially be slow
  646. * with a large number of items because it must place items, then check every
  647. * previous item to ensure there is no overlap.
  648. * @param {Array.<Rect>} itemRects Item data objects.
  649. * @param {number} containerWidth Width of the containing element.
  650. * @return {Array.<Point>}
  651. */
  652. function getCenteredPositions(itemRects, containerWidth) {
  653. var rowMap = {}; // Populate rows by their offset because items could jump between rows like:
  654. // a c
  655. // bbb
  656. itemRects.forEach(function (itemRect) {
  657. if (rowMap[itemRect.top]) {
  658. // Push the point to the last row array.
  659. rowMap[itemRect.top].push(itemRect);
  660. } else {
  661. // Start of a new row.
  662. rowMap[itemRect.top] = [itemRect];
  663. }
  664. }); // For each row, find the end of the last item, then calculate
  665. // the remaining space by dividing it by 2. Then add that
  666. // offset to the x position of each point.
  667. var rects = [];
  668. var rows = [];
  669. var centeredRows = [];
  670. Object.keys(rowMap).forEach(function (key) {
  671. var itemRects = rowMap[key];
  672. rows.push(itemRects);
  673. var lastItem = itemRects[itemRects.length - 1];
  674. var end = lastItem.left + lastItem.width;
  675. var offset = Math.round((containerWidth - end) / 2);
  676. var finalRects = itemRects;
  677. var canMove = false;
  678. if (offset > 0) {
  679. var newRects = [];
  680. canMove = itemRects.every(function (r) {
  681. var newRect = new Rect(r.left + offset, r.top, r.width, r.height, r.id); // Check all current rects to make sure none overlap.
  682. var noOverlap = !rects.some(function (r) {
  683. return Rect.intersects(newRect, r);
  684. });
  685. newRects.push(newRect);
  686. return noOverlap;
  687. }); // If none of the rectangles overlapped, the whole group can be centered.
  688. if (canMove) {
  689. finalRects = newRects;
  690. }
  691. } // If the items are not going to be offset, ensure that the original
  692. // placement for this row will not overlap previous rows (row-spanning
  693. // elements could be in the way).
  694. if (!canMove) {
  695. var intersectingRect;
  696. var hasOverlap = itemRects.some(function (itemRect) {
  697. return rects.some(function (r) {
  698. var intersects = Rect.intersects(itemRect, r);
  699. if (intersects) {
  700. intersectingRect = r;
  701. }
  702. return intersects;
  703. });
  704. }); // If there is any overlap, replace the overlapping row with the original.
  705. if (hasOverlap) {
  706. var rowIndex = centeredRows.findIndex(function (items) {
  707. return items.includes(intersectingRect);
  708. });
  709. centeredRows.splice(rowIndex, 1, rows[rowIndex]);
  710. }
  711. }
  712. rects = rects.concat(finalRects);
  713. centeredRows.push(finalRects);
  714. }); // Reduce array of arrays to a single array of points.
  715. // https://stackoverflow.com/a/10865042/373422
  716. // Then reset sort back to how the items were passed to this method.
  717. // Remove the wrapper object with index, map to a Point.
  718. return [].concat.apply([], centeredRows) // eslint-disable-line prefer-spread
  719. .sort(function (a, b) {
  720. return a.id - b.id;
  721. }).map(function (itemRect) {
  722. return new Point(itemRect.left, itemRect.top);
  723. });
  724. }
  725. /**
  726. * Hyphenates a javascript style string to a css one. For example:
  727. * MozBoxSizing -> -moz-box-sizing.
  728. * @param {string} str The string to hyphenate.
  729. * @return {string} The hyphenated string.
  730. */
  731. function hyphenate(str) {
  732. return str.replace(/([A-Z])/g, function (str, m1) {
  733. return "-".concat(m1.toLowerCase());
  734. });
  735. }
  736. function arrayUnique(x) {
  737. return Array.from(new Set(x));
  738. } // Used for unique instance variables
  739. var id$1 = 0;
  740. var Shuffle =
  741. /*#__PURE__*/
  742. function (_TinyEmitter) {
  743. _inherits(Shuffle, _TinyEmitter);
  744. /**
  745. * Categorize, sort, and filter a responsive grid of items.
  746. *
  747. * @param {Element} element An element which is the parent container for the grid items.
  748. * @param {Object} [options=Shuffle.options] Options object.
  749. * @constructor
  750. */
  751. function Shuffle(element) {
  752. var _this;
  753. var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  754. _classCallCheck(this, Shuffle);
  755. _this = _possibleConstructorReturn(this, _getPrototypeOf(Shuffle).call(this));
  756. _this.options = Object.assign({}, Shuffle.options, options); // Allow misspelling of delimiter since that's how it used to be.
  757. // Remove in v6.
  758. if (_this.options.delimeter) {
  759. _this.options.delimiter = _this.options.delimeter;
  760. }
  761. _this.lastSort = {};
  762. _this.group = Shuffle.ALL_ITEMS;
  763. _this.lastFilter = Shuffle.ALL_ITEMS;
  764. _this.isEnabled = true;
  765. _this.isDestroyed = false;
  766. _this.isInitialized = false;
  767. _this._transitions = [];
  768. _this.isTransitioning = false;
  769. _this._queue = [];
  770. var el = _this._getElementOption(element);
  771. if (!el) {
  772. throw new TypeError('Shuffle needs to be initialized with an element.');
  773. }
  774. _this.element = el;
  775. _this.id = 'shuffle_' + id$1;
  776. id$1 += 1;
  777. _this._init();
  778. _this.isInitialized = true;
  779. return _this;
  780. }
  781. _createClass(Shuffle, [{
  782. key: "_init",
  783. value: function _init() {
  784. this.items = this._getItems();
  785. this.options.sizer = this._getElementOption(this.options.sizer); // Add class and invalidate styles
  786. this.element.classList.add(Shuffle.Classes.BASE); // Set initial css for each item
  787. this._initItems(this.items); // Bind resize events
  788. this._onResize = this._getResizeFunction();
  789. window.addEventListener('resize', this._onResize); // If the page has not already emitted the `load` event, call layout on load.
  790. // This avoids layout issues caused by images and fonts loading after the
  791. // instance has been initialized.
  792. if (document.readyState !== 'complete') {
  793. var layout = this.layout.bind(this);
  794. window.addEventListener('load', function onLoad() {
  795. window.removeEventListener('load', onLoad);
  796. layout();
  797. });
  798. } // Get container css all in one request. Causes reflow
  799. var containerCss = window.getComputedStyle(this.element, null);
  800. var containerWidth = Shuffle.getSize(this.element).width; // Add styles to the container if it doesn't have them.
  801. this._validateStyles(containerCss); // We already got the container's width above, no need to cause another
  802. // reflow getting it again... Calculate the number of columns there will be
  803. this._setColumns(containerWidth); // Kick off!
  804. this.filter(this.options.group, this.options.initialSort); // The shuffle items haven't had transitions set on them yet so the user
  805. // doesn't see the first layout. Set them now that the first layout is done.
  806. // First, however, a synchronous layout must be caused for the previous
  807. // styles to be applied without transitions.
  808. this.element.offsetWidth; // eslint-disable-line no-unused-expressions
  809. this.setItemTransitions(this.items);
  810. this.element.style.transition = "height ".concat(this.options.speed, "ms ").concat(this.options.easing);
  811. }
  812. /**
  813. * Returns a throttled and proxied function for the resize handler.
  814. * @return {function}
  815. * @private
  816. */
  817. }, {
  818. key: "_getResizeFunction",
  819. value: function _getResizeFunction() {
  820. var resizeFunction = this._handleResize.bind(this);
  821. return this.options.throttle ? this.options.throttle(resizeFunction, this.options.throttleTime) : resizeFunction;
  822. }
  823. /**
  824. * Retrieve an element from an option.
  825. * @param {string|jQuery|Element} option The option to check.
  826. * @return {?Element} The plain element or null.
  827. * @private
  828. */
  829. }, {
  830. key: "_getElementOption",
  831. value: function _getElementOption(option) {
  832. // If column width is a string, treat is as a selector and search for the
  833. // sizer element within the outermost container
  834. if (typeof option === 'string') {
  835. return this.element.querySelector(option);
  836. } // Check for an element
  837. if (option && option.nodeType && option.nodeType === 1) {
  838. return option;
  839. } // Check for jQuery object
  840. if (option && option.jquery) {
  841. return option[0];
  842. }
  843. return null;
  844. }
  845. /**
  846. * Ensures the shuffle container has the css styles it needs applied to it.
  847. * @param {Object} styles Key value pairs for position and overflow.
  848. * @private
  849. */
  850. }, {
  851. key: "_validateStyles",
  852. value: function _validateStyles(styles) {
  853. // Position cannot be static.
  854. if (styles.position === 'static') {
  855. this.element.style.position = 'relative';
  856. } // Overflow has to be hidden.
  857. if (styles.overflow !== 'hidden') {
  858. this.element.style.overflow = 'hidden';
  859. }
  860. }
  861. /**
  862. * Filter the elements by a category.
  863. * @param {string|string[]|function(Element):boolean} [category] Category to
  864. * filter by. If it's given, the last category will be used to filter the items.
  865. * @param {Array} [collection] Optionally filter a collection. Defaults to
  866. * all the items.
  867. * @return {{visible: ShuffleItem[], hidden: ShuffleItem[]}}
  868. * @private
  869. */
  870. }, {
  871. key: "_filter",
  872. value: function _filter() {
  873. var category = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.lastFilter;
  874. var collection = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.items;
  875. var set = this._getFilteredSets(category, collection); // Individually add/remove hidden/visible classes
  876. this._toggleFilterClasses(set); // Save the last filter in case elements are appended.
  877. this.lastFilter = category; // This is saved mainly because providing a filter function (like searching)
  878. // will overwrite the `lastFilter` property every time its called.
  879. if (typeof category === 'string') {
  880. this.group = category;
  881. }
  882. return set;
  883. }
  884. /**
  885. * Returns an object containing the visible and hidden elements.
  886. * @param {string|string[]|function(Element):boolean} category Category or function to filter by.
  887. * @param {ShuffleItem[]} items A collection of items to filter.
  888. * @return {{visible: ShuffleItem[], hidden: ShuffleItem[]}}
  889. * @private
  890. */
  891. }, {
  892. key: "_getFilteredSets",
  893. value: function _getFilteredSets(category, items) {
  894. var _this2 = this;
  895. var visible = [];
  896. var hidden = []; // category === 'all', add visible class to everything
  897. if (category === Shuffle.ALL_ITEMS) {
  898. visible = items; // Loop through each item and use provided function to determine
  899. // whether to hide it or not.
  900. } else {
  901. items.forEach(function (item) {
  902. if (_this2._doesPassFilter(category, item.element)) {
  903. visible.push(item);
  904. } else {
  905. hidden.push(item);
  906. }
  907. });
  908. }
  909. return {
  910. visible: visible,
  911. hidden: hidden
  912. };
  913. }
  914. /**
  915. * Test an item to see if it passes a category.
  916. * @param {string|string[]|function():boolean} category Category or function to filter by.
  917. * @param {Element} element An element to test.
  918. * @return {boolean} Whether it passes the category/filter.
  919. * @private
  920. */
  921. }, {
  922. key: "_doesPassFilter",
  923. value: function _doesPassFilter(category, element) {
  924. if (typeof category === 'function') {
  925. return category.call(element, element, this);
  926. } // Check each element's data-groups attribute against the given category.
  927. var attr = element.getAttribute('data-' + Shuffle.FILTER_ATTRIBUTE_KEY);
  928. var keys = this.options.delimiter ? attr.split(this.options.delimiter) : JSON.parse(attr);
  929. function testCategory(category) {
  930. return keys.includes(category);
  931. }
  932. if (Array.isArray(category)) {
  933. if (this.options.filterMode === Shuffle.FilterMode.ANY) {
  934. return category.some(testCategory);
  935. }
  936. return category.every(testCategory);
  937. }
  938. return keys.includes(category);
  939. }
  940. /**
  941. * Toggles the visible and hidden class names.
  942. * @param {{visible, hidden}} Object with visible and hidden arrays.
  943. * @private
  944. */
  945. }, {
  946. key: "_toggleFilterClasses",
  947. value: function _toggleFilterClasses(_ref) {
  948. var visible = _ref.visible,
  949. hidden = _ref.hidden;
  950. visible.forEach(function (item) {
  951. item.show();
  952. });
  953. hidden.forEach(function (item) {
  954. item.hide();
  955. });
  956. }
  957. /**
  958. * Set the initial css for each item
  959. * @param {ShuffleItem[]} items Set to initialize.
  960. * @private
  961. */
  962. }, {
  963. key: "_initItems",
  964. value: function _initItems(items) {
  965. items.forEach(function (item) {
  966. item.init();
  967. });
  968. }
  969. /**
  970. * Remove element reference and styles.
  971. * @param {ShuffleItem[]} items Set to dispose.
  972. * @private
  973. */
  974. }, {
  975. key: "_disposeItems",
  976. value: function _disposeItems(items) {
  977. items.forEach(function (item) {
  978. item.dispose();
  979. });
  980. }
  981. /**
  982. * Updates the visible item count.
  983. * @private
  984. */
  985. }, {
  986. key: "_updateItemCount",
  987. value: function _updateItemCount() {
  988. this.visibleItems = this._getFilteredItems().length;
  989. }
  990. /**
  991. * Sets css transform transition on a group of elements. This is not executed
  992. * at the same time as `item.init` so that transitions don't occur upon
  993. * initialization of a new Shuffle instance.
  994. * @param {ShuffleItem[]} items Shuffle items to set transitions on.
  995. * @protected
  996. */
  997. }, {
  998. key: "setItemTransitions",
  999. value: function setItemTransitions(items) {
  1000. var _this$options = this.options,
  1001. speed = _this$options.speed,
  1002. easing = _this$options.easing;
  1003. var positionProps = this.options.useTransforms ? ['transform'] : ['top', 'left']; // Allow users to transtion other properties if they exist in the `before`
  1004. // css mapping of the shuffle item.
  1005. var cssProps = Object.keys(ShuffleItem.Css.HIDDEN.before).map(function (k) {
  1006. return hyphenate(k);
  1007. });
  1008. var properties = positionProps.concat(cssProps).join();
  1009. items.forEach(function (item) {
  1010. item.element.style.transitionDuration = speed + 'ms';
  1011. item.element.style.transitionTimingFunction = easing;
  1012. item.element.style.transitionProperty = properties;
  1013. });
  1014. }
  1015. }, {
  1016. key: "_getItems",
  1017. value: function _getItems() {
  1018. var _this3 = this;
  1019. return Array.from(this.element.children).filter(function (el) {
  1020. return matchesSelector(el, _this3.options.itemSelector);
  1021. }).map(function (el) {
  1022. return new ShuffleItem(el);
  1023. });
  1024. }
  1025. /**
  1026. * Combine the current items array with a new one and sort it by DOM order.
  1027. * @param {ShuffleItem[]} items Items to track.
  1028. * @return {ShuffleItem[]}
  1029. */
  1030. }, {
  1031. key: "_mergeNewItems",
  1032. value: function _mergeNewItems(items) {
  1033. var children = Array.from(this.element.children);
  1034. return sorter(this.items.concat(items), {
  1035. by: function by(element) {
  1036. return children.indexOf(element);
  1037. }
  1038. });
  1039. }
  1040. }, {
  1041. key: "_getFilteredItems",
  1042. value: function _getFilteredItems() {
  1043. return this.items.filter(function (item) {
  1044. return item.isVisible;
  1045. });
  1046. }
  1047. }, {
  1048. key: "_getConcealedItems",
  1049. value: function _getConcealedItems() {
  1050. return this.items.filter(function (item) {
  1051. return !item.isVisible;
  1052. });
  1053. }
  1054. /**
  1055. * Returns the column size, based on column width and sizer options.
  1056. * @param {number} containerWidth Size of the parent container.
  1057. * @param {number} gutterSize Size of the gutters.
  1058. * @return {number}
  1059. * @private
  1060. */
  1061. }, {
  1062. key: "_getColumnSize",
  1063. value: function _getColumnSize(containerWidth, gutterSize) {
  1064. var size; // If the columnWidth property is a function, then the grid is fluid
  1065. if (typeof this.options.columnWidth === 'function') {
  1066. size = this.options.columnWidth(containerWidth); // columnWidth option isn't a function, are they using a sizing element?
  1067. } else if (this.options.sizer) {
  1068. size = Shuffle.getSize(this.options.sizer).width; // if not, how about the explicitly set option?
  1069. } else if (this.options.columnWidth) {
  1070. size = this.options.columnWidth; // or use the size of the first item
  1071. } else if (this.items.length > 0) {
  1072. size = Shuffle.getSize(this.items[0].element, true).width; // if there's no items, use size of container
  1073. } else {
  1074. size = containerWidth;
  1075. } // Don't let them set a column width of zero.
  1076. if (size === 0) {
  1077. size = containerWidth;
  1078. }
  1079. return size + gutterSize;
  1080. }
  1081. /**
  1082. * Returns the gutter size, based on gutter width and sizer options.
  1083. * @param {number} containerWidth Size of the parent container.
  1084. * @return {number}
  1085. * @private
  1086. */
  1087. }, {
  1088. key: "_getGutterSize",
  1089. value: function _getGutterSize(containerWidth) {
  1090. var size;
  1091. if (typeof this.options.gutterWidth === 'function') {
  1092. size = this.options.gutterWidth(containerWidth);
  1093. } else if (this.options.sizer) {
  1094. size = getNumberStyle(this.options.sizer, 'marginLeft');
  1095. } else {
  1096. size = this.options.gutterWidth;
  1097. }
  1098. return size;
  1099. }
  1100. /**
  1101. * Calculate the number of columns to be used. Gets css if using sizer element.
  1102. * @param {number} [containerWidth] Optionally specify a container width if
  1103. * it's already available.
  1104. */
  1105. }, {
  1106. key: "_setColumns",
  1107. value: function _setColumns() {
  1108. var containerWidth = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Shuffle.getSize(this.element).width;
  1109. var gutter = this._getGutterSize(containerWidth);
  1110. var columnWidth = this._getColumnSize(containerWidth, gutter);
  1111. var calculatedColumns = (containerWidth + gutter) / columnWidth; // Widths given from getStyles are not precise enough...
  1112. if (Math.abs(Math.round(calculatedColumns) - calculatedColumns) < this.options.columnThreshold) {
  1113. // e.g. calculatedColumns = 11.998876
  1114. calculatedColumns = Math.round(calculatedColumns);
  1115. }
  1116. this.cols = Math.max(Math.floor(calculatedColumns || 0), 1);
  1117. this.containerWidth = containerWidth;
  1118. this.colWidth = columnWidth;
  1119. }
  1120. /**
  1121. * Adjust the height of the grid
  1122. */
  1123. }, {
  1124. key: "_setContainerSize",
  1125. value: function _setContainerSize() {
  1126. this.element.style.height = this._getContainerSize() + 'px';
  1127. }
  1128. /**
  1129. * Based on the column heights, it returns the biggest one.
  1130. * @return {number}
  1131. * @private
  1132. */
  1133. }, {
  1134. key: "_getContainerSize",
  1135. value: function _getContainerSize() {
  1136. return arrayMax(this.positions);
  1137. }
  1138. /**
  1139. * Get the clamped stagger amount.
  1140. * @param {number} index Index of the item to be staggered.
  1141. * @return {number}
  1142. */
  1143. }, {
  1144. key: "_getStaggerAmount",
  1145. value: function _getStaggerAmount(index) {
  1146. return Math.min(index * this.options.staggerAmount, this.options.staggerAmountMax);
  1147. }
  1148. /**
  1149. * Emit an event from this instance.
  1150. * @param {string} name Event name.
  1151. * @param {Object} [data={}] Optional object data.
  1152. */
  1153. }, {
  1154. key: "_dispatch",
  1155. value: function _dispatch(name) {
  1156. var data = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  1157. if (this.isDestroyed) {
  1158. return;
  1159. }
  1160. data.shuffle = this;
  1161. this.emit(name, data);
  1162. }
  1163. /**
  1164. * Zeros out the y columns array, which is used to determine item placement.
  1165. * @private
  1166. */
  1167. }, {
  1168. key: "_resetCols",
  1169. value: function _resetCols() {
  1170. var i = this.cols;
  1171. this.positions = [];
  1172. while (i) {
  1173. i -= 1;
  1174. this.positions.push(0);
  1175. }
  1176. }
  1177. /**
  1178. * Loops through each item that should be shown and calculates the x, y position.
  1179. * @param {ShuffleItem[]} items Array of items that will be shown/layed
  1180. * out in order in their array.
  1181. */
  1182. }, {
  1183. key: "_layout",
  1184. value: function _layout(items) {
  1185. var _this4 = this;
  1186. var itemPositions = this._getNextPositions(items);
  1187. var count = 0;
  1188. items.forEach(function (item, i) {
  1189. function callback() {
  1190. item.applyCss(ShuffleItem.Css.VISIBLE.after);
  1191. } // If the item will not change its position, do not add it to the render
  1192. // queue. Transitions don't fire when setting a property to the same value.
  1193. if (Point.equals(item.point, itemPositions[i]) && !item.isHidden) {
  1194. item.applyCss(ShuffleItem.Css.VISIBLE.before);
  1195. callback();
  1196. return;
  1197. }
  1198. item.point = itemPositions[i];
  1199. item.scale = ShuffleItem.Scale.VISIBLE;
  1200. item.isHidden = false; // Clone the object so that the `before` object isn't modified when the
  1201. // transition delay is added.
  1202. var styles = _this4.getStylesForTransition(item, ShuffleItem.Css.VISIBLE.before);
  1203. styles.transitionDelay = _this4._getStaggerAmount(count) + 'ms';
  1204. _this4._queue.push({
  1205. item: item,
  1206. styles: styles,
  1207. callback: callback
  1208. });
  1209. count += 1;
  1210. });
  1211. }
  1212. /**
  1213. * Return an array of Point instances representing the future positions of
  1214. * each item.
  1215. * @param {ShuffleItem[]} items Array of sorted shuffle items.
  1216. * @return {Point[]}
  1217. * @private
  1218. */
  1219. }, {
  1220. key: "_getNextPositions",
  1221. value: function _getNextPositions(items) {
  1222. var _this5 = this;
  1223. // If position data is going to be changed, add the item's size to the
  1224. // transformer to allow for calculations.
  1225. if (this.options.isCentered) {
  1226. var itemsData = items.map(function (item, i) {
  1227. var itemSize = Shuffle.getSize(item.element, true);
  1228. var point = _this5._getItemPosition(itemSize);
  1229. return new Rect(point.x, point.y, itemSize.width, itemSize.height, i);
  1230. });
  1231. return this.getTransformedPositions(itemsData, this.containerWidth);
  1232. } // If no transforms are going to happen, simply return an array of the
  1233. // future points of each item.
  1234. return items.map(function (item) {
  1235. return _this5._getItemPosition(Shuffle.getSize(item.element, true));
  1236. });
  1237. }
  1238. /**
  1239. * Determine the location of the next item, based on its size.
  1240. * @param {{width: number, height: number}} itemSize Object with width and height.
  1241. * @return {Point}
  1242. * @private
  1243. */
  1244. }, {
  1245. key: "_getItemPosition",
  1246. value: function _getItemPosition(itemSize) {
  1247. return getItemPosition({
  1248. itemSize: itemSize,
  1249. positions: this.positions,
  1250. gridSize: this.colWidth,
  1251. total: this.cols,
  1252. threshold: this.options.columnThreshold,
  1253. buffer: this.options.buffer
  1254. });
  1255. }
  1256. /**
  1257. * Mutate positions before they're applied.
  1258. * @param {Rect[]} itemRects Item data objects.
  1259. * @param {number} containerWidth Width of the containing element.
  1260. * @return {Point[]}
  1261. * @protected
  1262. */
  1263. }, {
  1264. key: "getTransformedPositions",
  1265. value: function getTransformedPositions(itemRects, containerWidth) {
  1266. return getCenteredPositions(itemRects, containerWidth);
  1267. }
  1268. /**
  1269. * Hides the elements that don't match our filter.
  1270. * @param {ShuffleItem[]} collection Collection to shrink.
  1271. * @private
  1272. */
  1273. }, {
  1274. key: "_shrink",
  1275. value: function _shrink() {
  1276. var _this6 = this;
  1277. var collection = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this._getConcealedItems();
  1278. var count = 0;
  1279. collection.forEach(function (item) {
  1280. function callback() {
  1281. item.applyCss(ShuffleItem.Css.HIDDEN.after);
  1282. } // Continuing would add a transitionend event listener to the element, but
  1283. // that listener would not execute because the transform and opacity would
  1284. // stay the same.
  1285. // The callback is executed here because it is not guaranteed to be called
  1286. // after the transitionend event because the transitionend could be
  1287. // canceled if another animation starts.
  1288. if (item.isHidden) {
  1289. item.applyCss(ShuffleItem.Css.HIDDEN.before);
  1290. callback();
  1291. return;
  1292. }
  1293. item.scale = ShuffleItem.Scale.HIDDEN;
  1294. item.isHidden = true;
  1295. var styles = _this6.getStylesForTransition(item, ShuffleItem.Css.HIDDEN.before);
  1296. styles.transitionDelay = _this6._getStaggerAmount(count) + 'ms';
  1297. _this6._queue.push({
  1298. item: item,
  1299. styles: styles,
  1300. callback: callback
  1301. });
  1302. count += 1;
  1303. });
  1304. }
  1305. /**
  1306. * Resize handler.
  1307. * @private
  1308. */
  1309. }, {
  1310. key: "_handleResize",
  1311. value: function _handleResize() {
  1312. // If shuffle is disabled, destroyed, don't do anything
  1313. if (!this.isEnabled || this.isDestroyed) {
  1314. return;
  1315. }
  1316. this.update();
  1317. }
  1318. /**
  1319. * Returns styles which will be applied to the an item for a transition.
  1320. * @param {ShuffleItem} item Item to get styles for. Should have updated
  1321. * scale and point properties.
  1322. * @param {Object} styleObject Extra styles that will be used in the transition.
  1323. * @return {!Object} Transforms for transitions, left/top for animate.
  1324. * @protected
  1325. */
  1326. }, {
  1327. key: "getStylesForTransition",
  1328. value: function getStylesForTransition(item, styleObject) {
  1329. // Clone the object to avoid mutating the original.
  1330. var styles = Object.assign({}, styleObject);
  1331. if (this.options.useTransforms) {
  1332. var x = this.options.roundTransforms ? Math.round(item.point.x) : item.point.x;
  1333. var y = this.options.roundTransforms ? Math.round(item.point.y) : item.point.y;
  1334. styles.transform = "translate(".concat(x, "px, ").concat(y, "px) scale(").concat(item.scale, ")");
  1335. } else {
  1336. styles.left = item.point.x + 'px';
  1337. styles.top = item.point.y + 'px';
  1338. }
  1339. return styles;
  1340. }
  1341. /**
  1342. * Listen for the transition end on an element and execute the itemCallback
  1343. * when it finishes.
  1344. * @param {Element} element Element to listen on.
  1345. * @param {function} itemCallback Callback for the item.
  1346. * @param {function} done Callback to notify `parallel` that this one is done.
  1347. */
  1348. }, {
  1349. key: "_whenTransitionDone",
  1350. value: function _whenTransitionDone(element, itemCallback, done) {
  1351. var id = onTransitionEnd(element, function (evt) {
  1352. itemCallback();
  1353. done(null, evt);
  1354. });
  1355. this._transitions.push(id);
  1356. }
  1357. /**
  1358. * Return a function which will set CSS styles and call the `done` function
  1359. * when (if) the transition finishes.
  1360. * @param {Object} opts Transition object.
  1361. * @return {function} A function to be called with a `done` function.
  1362. */
  1363. }, {
  1364. key: "_getTransitionFunction",
  1365. value: function _getTransitionFunction(opts) {
  1366. var _this7 = this;
  1367. return function (done) {
  1368. opts.item.applyCss(opts.styles);
  1369. _this7._whenTransitionDone(opts.item.element, opts.callback, done);
  1370. };
  1371. }
  1372. /**
  1373. * Execute the styles gathered in the style queue. This applies styles to elements,
  1374. * triggering transitions.
  1375. * @private
  1376. */
  1377. }, {
  1378. key: "_processQueue",
  1379. value: function _processQueue() {
  1380. if (this.isTransitioning) {
  1381. this._cancelMovement();
  1382. }
  1383. var hasSpeed = this.options.speed > 0;
  1384. var hasQueue = this._queue.length > 0;
  1385. if (hasQueue && hasSpeed && this.isInitialized) {
  1386. this._startTransitions(this._queue);
  1387. } else if (hasQueue) {
  1388. this._styleImmediately(this._queue);
  1389. this._dispatch(Shuffle.EventType.LAYOUT); // A call to layout happened, but none of the newly visible items will
  1390. // change position or the transition duration is zero, which will not trigger
  1391. // the transitionend event.
  1392. } else {
  1393. this._dispatch(Shuffle.EventType.LAYOUT);
  1394. } // Remove everything in the style queue
  1395. this._queue.length = 0;
  1396. }
  1397. /**
  1398. * Wait for each transition to finish, the emit the layout event.
  1399. * @param {Object[]} transitions Array of transition objects.
  1400. */
  1401. }, {
  1402. key: "_startTransitions",
  1403. value: function _startTransitions(transitions) {
  1404. var _this8 = this;
  1405. // Set flag that shuffle is currently in motion.
  1406. this.isTransitioning = true; // Create an array of functions to be called.
  1407. var callbacks = transitions.map(function (obj) {
  1408. return _this8._getTransitionFunction(obj);
  1409. });
  1410. arrayParallel(callbacks, this._movementFinished.bind(this));
  1411. }
  1412. }, {
  1413. key: "_cancelMovement",
  1414. value: function _cancelMovement() {
  1415. // Remove the transition end event for each listener.
  1416. this._transitions.forEach(cancelTransitionEnd); // Reset the array.
  1417. this._transitions.length = 0; // Show it's no longer active.
  1418. this.isTransitioning = false;
  1419. }
  1420. /**
  1421. * Apply styles without a transition.
  1422. * @param {Object[]} objects Array of transition objects.
  1423. * @private
  1424. */
  1425. }, {
  1426. key: "_styleImmediately",
  1427. value: function _styleImmediately(objects) {
  1428. if (objects.length) {
  1429. var elements = objects.map(function (obj) {
  1430. return obj.item.element;
  1431. });
  1432. Shuffle._skipTransitions(elements, function () {
  1433. objects.forEach(function (obj) {
  1434. obj.item.applyCss(obj.styles);
  1435. obj.callback();
  1436. });
  1437. });
  1438. }
  1439. }
  1440. }, {
  1441. key: "_movementFinished",
  1442. value: function _movementFinished() {
  1443. this._transitions.length = 0;
  1444. this.isTransitioning = false;
  1445. this._dispatch(Shuffle.EventType.LAYOUT);
  1446. }
  1447. /**
  1448. * The magic. This is what makes the plugin 'shuffle'
  1449. * @param {string|string[]|function(Element):boolean} [category] Category to filter by.
  1450. * Can be a function, string, or array of strings.
  1451. * @param {SortOptions} [sortOptions] A sort object which can sort the visible set
  1452. */
  1453. }, {
  1454. key: "filter",
  1455. value: function filter(category, sortOptions) {
  1456. if (!this.isEnabled) {
  1457. return;
  1458. }
  1459. if (!category || category && category.length === 0) {
  1460. category = Shuffle.ALL_ITEMS; // eslint-disable-line no-param-reassign
  1461. }
  1462. this._filter(category); // Shrink each hidden item
  1463. this._shrink(); // How many visible elements?
  1464. this._updateItemCount(); // Update transforms on visible elements so they will animate to their new positions.
  1465. this.sort(sortOptions);
  1466. }
  1467. /**
  1468. * Gets the visible elements, sorts them, and passes them to layout.
  1469. * @param {SortOptions} [sortOptions] The options object to pass to `sorter`.
  1470. */
  1471. }, {
  1472. key: "sort",
  1473. value: function sort() {
  1474. var sortOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.lastSort;
  1475. if (!this.isEnabled) {
  1476. return;
  1477. }
  1478. this._resetCols();
  1479. var items = sorter(this._getFilteredItems(), sortOptions);
  1480. this._layout(items); // `_layout` always happens after `_shrink`, so it's safe to process the style
  1481. // queue here with styles from the shrink method.
  1482. this._processQueue(); // Adjust the height of the container.
  1483. this._setContainerSize();
  1484. this.lastSort = sortOptions;
  1485. }
  1486. /**
  1487. * Reposition everything.
  1488. * @param {boolean} [isOnlyLayout=false] If true, column and gutter widths won't be recalculated.
  1489. */
  1490. }, {
  1491. key: "update",
  1492. value: function update() {
  1493. var isOnlyLayout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
  1494. if (this.isEnabled) {
  1495. if (!isOnlyLayout) {
  1496. // Get updated colCount
  1497. this._setColumns();
  1498. } // Layout items
  1499. this.sort();
  1500. }
  1501. }
  1502. /**
  1503. * Use this instead of `update()` if you don't need the columns and gutters updated
  1504. * Maybe an image inside `shuffle` loaded (and now has a height), which means calculations
  1505. * could be off.
  1506. */
  1507. }, {
  1508. key: "layout",
  1509. value: function layout() {
  1510. this.update(true);
  1511. }
  1512. /**
  1513. * New items have been appended to shuffle. Mix them in with the current
  1514. * filter or sort status.
  1515. * @param {Element[]} newItems Collection of new items.
  1516. */
  1517. }, {
  1518. key: "add",
  1519. value: function add(newItems) {
  1520. var _this9 = this;
  1521. var items = arrayUnique(newItems).map(function (el) {
  1522. return new ShuffleItem(el);
  1523. }); // Add classes and set initial positions.
  1524. this._initItems(items); // Determine which items will go with the current filter.
  1525. this._resetCols();
  1526. var allItems = this._mergeNewItems(items);
  1527. var sortedItems = sorter(allItems, this.lastSort);
  1528. var allSortedItemsSet = this._filter(this.lastFilter, sortedItems);
  1529. var isNewItem = function isNewItem(item) {
  1530. return items.includes(item);
  1531. };
  1532. var applyHiddenState = function applyHiddenState(item) {
  1533. item.scale = ShuffleItem.Scale.HIDDEN;
  1534. item.isHidden = true;
  1535. item.applyCss(ShuffleItem.Css.HIDDEN.before);
  1536. item.applyCss(ShuffleItem.Css.HIDDEN.after);
  1537. }; // Layout all items again so that new items get positions.
  1538. // Synchonously apply positions.
  1539. var itemPositions = this._getNextPositions(allSortedItemsSet.visible);
  1540. allSortedItemsSet.visible.forEach(function (item, i) {
  1541. if (isNewItem(item)) {
  1542. item.point = itemPositions[i];
  1543. applyHiddenState(item);
  1544. item.applyCss(_this9.getStylesForTransition(item, {}));
  1545. }
  1546. });
  1547. allSortedItemsSet.hidden.forEach(function (item) {
  1548. if (isNewItem(item)) {
  1549. applyHiddenState(item);
  1550. }
  1551. }); // Cause layout so that the styles above are applied.
  1552. this.element.offsetWidth; // eslint-disable-line no-unused-expressions
  1553. // Add transition to each item.
  1554. this.setItemTransitions(items); // Update the list of items.
  1555. this.items = this._mergeNewItems(items); // Update layout/visibility of new and old items.
  1556. this.filter(this.lastFilter);
  1557. }
  1558. /**
  1559. * Disables shuffle from updating dimensions and layout on resize
  1560. */
  1561. }, {
  1562. key: "disable",
  1563. value: function disable() {
  1564. this.isEnabled = false;
  1565. }
  1566. /**
  1567. * Enables shuffle again
  1568. * @param {boolean} [isUpdateLayout=true] if undefined, shuffle will update columns and gutters
  1569. */
  1570. }, {
  1571. key: "enable",
  1572. value: function enable() {
  1573. var isUpdateLayout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
  1574. this.isEnabled = true;
  1575. if (isUpdateLayout) {
  1576. this.update();
  1577. }
  1578. }
  1579. /**
  1580. * Remove 1 or more shuffle items.
  1581. * @param {Element[]} elements An array containing one or more
  1582. * elements in shuffle
  1583. * @return {Shuffle} The shuffle instance.
  1584. */
  1585. }, {
  1586. key: "remove",
  1587. value: function remove(elements) {
  1588. var _this10 = this;
  1589. if (!elements.length) {
  1590. return;
  1591. }
  1592. var collection = arrayUnique(elements);
  1593. var oldItems = collection.map(function (element) {
  1594. return _this10.getItemByElement(element);
  1595. }).filter(function (item) {
  1596. return !!item;
  1597. });
  1598. var handleLayout = function handleLayout() {
  1599. _this10._disposeItems(oldItems); // Remove the collection in the callback
  1600. collection.forEach(function (element) {
  1601. element.parentNode.removeChild(element);
  1602. });
  1603. _this10._dispatch(Shuffle.EventType.REMOVED, {
  1604. collection: collection
  1605. });
  1606. }; // Hide collection first.
  1607. this._toggleFilterClasses({
  1608. visible: [],
  1609. hidden: oldItems
  1610. });
  1611. this._shrink(oldItems);
  1612. this.sort(); // Update the list of items here because `remove` could be called again
  1613. // with an item that is in the process of being removed.
  1614. this.items = this.items.filter(function (item) {
  1615. return !oldItems.includes(item);
  1616. });
  1617. this._updateItemCount();
  1618. this.once(Shuffle.EventType.LAYOUT, handleLayout);
  1619. }
  1620. /**
  1621. * Retrieve a shuffle item by its element.
  1622. * @param {Element} element Element to look for.
  1623. * @return {?ShuffleItem} A shuffle item or undefined if it's not found.
  1624. */
  1625. }, {
  1626. key: "getItemByElement",
  1627. value: function getItemByElement(element) {
  1628. return this.items.find(function (item) {
  1629. return item.element === element;
  1630. });
  1631. }
  1632. /**
  1633. * Dump the elements currently stored and reinitialize all child elements which
  1634. * match the `itemSelector`.
  1635. */
  1636. }, {
  1637. key: "resetItems",
  1638. value: function resetItems() {
  1639. var _this11 = this;
  1640. // Remove refs to current items.
  1641. this._disposeItems(this.items);
  1642. this.isInitialized = false; // Find new items in the DOM.
  1643. this.items = this._getItems(); // Set initial styles on the new items.
  1644. this._initItems(this.items);
  1645. this.once(Shuffle.EventType.LAYOUT, function () {
  1646. // Add transition to each item.
  1647. _this11.setItemTransitions(_this11.items);
  1648. _this11.isInitialized = true;
  1649. }); // Lay out all items.
  1650. this.filter(this.lastFilter);
  1651. }
  1652. /**
  1653. * Destroys shuffle, removes events, styles, and classes
  1654. */
  1655. }, {
  1656. key: "destroy",
  1657. value: function destroy() {
  1658. this._cancelMovement();
  1659. window.removeEventListener('resize', this._onResize); // Reset container styles
  1660. this.element.classList.remove('shuffle');
  1661. this.element.removeAttribute('style'); // Reset individual item styles
  1662. this._disposeItems(this.items);
  1663. this.items.length = 0;
  1664. this._transitions.length = 0; // Null DOM references
  1665. this.options.sizer = null;
  1666. this.element = null; // Set a flag so if a debounced resize has been triggered,
  1667. // it can first check if it is actually isDestroyed and not doing anything
  1668. this.isDestroyed = true;
  1669. this.isEnabled = false;
  1670. }
  1671. /**
  1672. * Returns the outer width of an element, optionally including its margins.
  1673. *
  1674. * There are a few different methods for getting the width of an element, none of
  1675. * which work perfectly for all Shuffle's use cases.
  1676. *
  1677. * 1. getBoundingClientRect() `left` and `right` properties.
  1678. * - Accounts for transform scaled elements, making it useless for Shuffle
  1679. * elements which have shrunk.
  1680. * 2. The `offsetWidth` property.
  1681. * - This value stays the same regardless of the elements transform property,
  1682. * however, it does not return subpixel values.
  1683. * 3. getComputedStyle()
  1684. * - This works great Chrome, Firefox, Safari, but IE<=11 does not include
  1685. * padding and border when box-sizing: border-box is set, requiring a feature
  1686. * test and extra work to add the padding back for IE and other browsers which
  1687. * follow the W3C spec here.
  1688. *
  1689. * @param {Element} element The element.
  1690. * @param {boolean} [includeMargins=false] Whether to include margins.
  1691. * @return {{width: number, height: number}} The width and height.
  1692. */
  1693. }], [{
  1694. key: "getSize",
  1695. value: function getSize(element) {
  1696. var includeMargins = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
  1697. // Store the styles so that they can be used by others without asking for it again.
  1698. var styles = window.getComputedStyle(element, null);
  1699. var width = getNumberStyle(element, 'width', styles);
  1700. var height = getNumberStyle(element, 'height', styles);
  1701. if (includeMargins) {
  1702. var marginLeft = getNumberStyle(element, 'marginLeft', styles);
  1703. var marginRight = getNumberStyle(element, 'marginRight', styles);
  1704. var marginTop = getNumberStyle(element, 'marginTop', styles);
  1705. var marginBottom = getNumberStyle(element, 'marginBottom', styles);
  1706. width += marginLeft + marginRight;
  1707. height += marginTop + marginBottom;
  1708. }
  1709. return {
  1710. width: width,
  1711. height: height
  1712. };
  1713. }
  1714. /**
  1715. * Change a property or execute a function which will not have a transition
  1716. * @param {Element[]} elements DOM elements that won't be transitioned.
  1717. * @param {function} callback A function which will be called while transition
  1718. * is set to 0ms.
  1719. * @private
  1720. */
  1721. }, {
  1722. key: "_skipTransitions",
  1723. value: function _skipTransitions(elements, callback) {
  1724. var zero = '0ms'; // Save current duration and delay.
  1725. var data = elements.map(function (element) {
  1726. var style = element.style;
  1727. var duration = style.transitionDuration;
  1728. var delay = style.transitionDelay; // Set the duration to zero so it happens immediately
  1729. style.transitionDuration = zero;
  1730. style.transitionDelay = zero;
  1731. return {
  1732. duration: duration,
  1733. delay: delay
  1734. };
  1735. });
  1736. callback(); // Cause forced synchronous layout.
  1737. elements[0].offsetWidth; // eslint-disable-line no-unused-expressions
  1738. // Put the duration back
  1739. elements.forEach(function (element, i) {
  1740. element.style.transitionDuration = data[i].duration;
  1741. element.style.transitionDelay = data[i].delay;
  1742. });
  1743. }
  1744. }]);
  1745. return Shuffle;
  1746. }(tinyEmitter);
  1747. Shuffle.ShuffleItem = ShuffleItem;
  1748. Shuffle.ALL_ITEMS = 'all';
  1749. Shuffle.FILTER_ATTRIBUTE_KEY = 'groups';
  1750. /** @enum {string} */
  1751. Shuffle.EventType = {
  1752. LAYOUT: 'shuffle:layout',
  1753. REMOVED: 'shuffle:removed'
  1754. };
  1755. /** @enum {string} */
  1756. Shuffle.Classes = Classes;
  1757. /** @enum {string} */
  1758. Shuffle.FilterMode = {
  1759. ANY: 'any',
  1760. ALL: 'all'
  1761. }; // Overrideable options
  1762. Shuffle.options = {
  1763. // Initial filter group.
  1764. group: Shuffle.ALL_ITEMS,
  1765. // Transition/animation speed (milliseconds).
  1766. speed: 250,
  1767. // CSS easing function to use.
  1768. easing: 'cubic-bezier(0.4, 0.0, 0.2, 1)',
  1769. // e.g. '.picture-item'.
  1770. itemSelector: '*',
  1771. // Element or selector string. Use an element to determine the size of columns
  1772. // and gutters.
  1773. sizer: null,
  1774. // A static number or function that tells the plugin how wide the gutters
  1775. // between columns are (in pixels).
  1776. gutterWidth: 0,
  1777. // A static number or function that returns a number which tells the plugin
  1778. // how wide the columns are (in pixels).
  1779. columnWidth: 0,
  1780. // If your group is not json, and is comma delimeted, you could set delimiter
  1781. // to ','.
  1782. delimiter: null,
  1783. // Useful for percentage based heights when they might not always be exactly
  1784. // the same (in pixels).
  1785. buffer: 0,
  1786. // Reading the width of elements isn't precise enough and can cause columns to
  1787. // jump between values.
  1788. columnThreshold: 0.01,
  1789. // Shuffle can be isInitialized with a sort object. It is the same object
  1790. // given to the sort method.
  1791. initialSort: null,
  1792. // By default, shuffle will throttle resize events. This can be changed or
  1793. // removed.
  1794. throttle: throttleit,
  1795. // How often shuffle can be called on resize (in milliseconds).
  1796. throttleTime: 300,
  1797. // Transition delay offset for each item in milliseconds.
  1798. staggerAmount: 15,
  1799. // Maximum stagger delay in milliseconds.
  1800. staggerAmountMax: 150,
  1801. // Whether to use transforms or absolute positioning.
  1802. useTransforms: true,
  1803. // Affects using an array with filter. e.g. `filter(['one', 'two'])`. With "any",
  1804. // the element passes the test if any of its groups are in the array. With "all",
  1805. // the element only passes if all groups are in the array.
  1806. filterMode: Shuffle.FilterMode.ANY,
  1807. // Attempt to center grid items in each row.
  1808. isCentered: false,
  1809. // Whether to round pixel values used in translate(x, y). This usually avoids
  1810. // blurriness.
  1811. roundTransforms: true
  1812. };
  1813. Shuffle.Point = Point;
  1814. Shuffle.Rect = Rect; // Expose for testing. Hack at your own risk.
  1815. Shuffle.__sorter = sorter;
  1816. Shuffle.__getColumnSpan = getColumnSpan;
  1817. Shuffle.__getAvailablePositions = getAvailablePositions;
  1818. Shuffle.__getShortColumn = getShortColumn;
  1819. Shuffle.__getCenteredPositions = getCenteredPositions;
  1820. return Shuffle;
  1821. }));
  1822. //# sourceMappingURL=shuffle.js.map