├── .eslintrc.js ├── .github └── FUNDING.yml ├── .gitignore ├── .prettierrc.json ├── .travis.yml ├── AUTHORS.txt ├── CONTRIBUTING.md ├── LICENSE.md ├── README.md ├── dist ├── muuri.js ├── muuri.min.js └── muuri.module.js ├── docs ├── .vitepress │ ├── config.js │ └── theme │ │ ├── custom.css │ │ └── index.js ├── examples.md ├── getting-started.md ├── grid-constructor.md ├── grid-events.md ├── grid-methods.md ├── grid-options.md ├── index.md ├── item-methods.md └── public │ └── muuri-icon.svg ├── gulpfile.js ├── karma.conf.js ├── karma.defaults.js ├── package-lock.json ├── package.json ├── rollup.banner.js ├── rollup.config.js ├── src ├── Animator │ └── Animator.js ├── AutoScroller │ ├── AutoScroller.js │ ├── LICENSE.md │ ├── Pool.js │ ├── ScrollAction.js │ ├── ScrollRequest.js │ ├── constants.js │ └── utils.js ├── Dragger │ ├── Dragger.js │ ├── EdgeHack.js │ └── LICENSE.md ├── Emitter │ ├── Emitter.js │ └── LICENSE.md ├── Grid │ └── Grid.js ├── Item │ ├── Item.js │ ├── ItemDrag.js │ ├── ItemDragPlaceholder.js │ ├── ItemDragRelease.js │ ├── ItemLayout.js │ ├── ItemMigrate.js │ └── ItemVisibility.js ├── Packer │ ├── LICENSE.md │ ├── Packer.js │ └── PackerProcessor.js ├── Ticker │ ├── LICENSE.md │ └── Ticker.js ├── constants.js ├── index.d.ts ├── index.js ├── ticker.js └── utils │ ├── addClass.js │ ├── arrayInsert.js │ ├── arrayMove.js │ ├── arraySwap.js │ ├── createUid.js │ ├── debounce.js │ ├── elementMatches.js │ ├── getContainingBlock.js │ ├── getCurrentStyles.js │ ├── getIntersectionArea.js │ ├── getIntersectionScore.js │ ├── getOffsetDiff.js │ ├── getPrefixedPropName.js │ ├── getScrollableAncestors.js │ ├── getStyle.js │ ├── getStyleAsFloat.js │ ├── getStyleName.js │ ├── getTranslate.js │ ├── getTranslateString.js │ ├── getUnprefixedPropName.js │ ├── hasPassiveEvents.js │ ├── isFunction.js │ ├── isNative.js │ ├── isNodeList.js │ ├── isOverlapping.js │ ├── isPlainObject.js │ ├── isScrollable.js │ ├── isTransformed.js │ ├── noop.js │ ├── normalizeArrayIndex.js │ ├── raf.js │ ├── removeClass.js │ ├── setStyles.js │ ├── toArray.js │ ├── transformProp.js │ └── transformStyle.js └── tests ├── grid-constructor ├── container.js └── instance.js ├── grid-events ├── add.js ├── beforeReceive.js ├── beforeSend.js ├── destroy.js ├── dragEnd.js ├── dragInit.js ├── dragMove.js ├── dragReleaseEnd.js ├── dragReleaseStart.js ├── dragScroll.js ├── dragStart.js ├── draggerEvent.js ├── filter.js ├── hideEnd.js ├── hideStart.js ├── layoutAbort.js ├── layoutEnd.js ├── layoutStart.js ├── move.js ├── receive.js ├── remove.js ├── send.js ├── showEnd.js ├── showStart.js ├── sort.js └── synchronize.js ├── grid-methods ├── add.js ├── destroy.js ├── filter.js ├── getElement.js ├── getItems.js ├── hide.js ├── layout.js ├── move.js ├── off.js ├── on.js ├── refreshItems.js ├── refreshSortData.js ├── remove.js ├── send.js ├── show.js ├── sort.js └── synchronize.js ├── grid-options ├── containerClass.js ├── dragAutoScroll.js ├── dragAxis.js ├── dragContainer.js ├── dragEnabled.js ├── dragPlaceholder.js ├── dragSort.js ├── dragSortPredicate.js ├── dragStartPredicate.js ├── hideDuration.js ├── itemClass.js ├── itemDraggingClass.js ├── itemHiddenClass.js ├── itemPositioningClass.js ├── itemReleasingClass.js ├── itemVisibleClass.js ├── items.js ├── layout.js ├── showDuration.js └── visibleStyles-hiddenStyles.js ├── index.js ├── item-methods ├── getElement.js ├── getGrid.js ├── getHeight.js ├── getMargin.js ├── getPosition.js ├── getWidth.js ├── isActive.js ├── isDestroyed.js ├── isDragging.js ├── isHiding.js ├── isPositioning.js ├── isReleasing.js ├── isShowing.js └── isVisible.js └── utils.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parserOptions: { 3 | ecmaVersion: 7, 4 | sourceType: 'module' 5 | }, 6 | rules: { 7 | // Possible errors 8 | 'no-duplicate-case': 'error', 9 | 'no-ex-assign': 'error', 10 | 'no-empty': ['error', { allowEmptyCatch: true }], 11 | 'no-extra-semi': 'error', 12 | 'no-func-assign': 'error', 13 | 'no-invalid-regexp': 'error', 14 | 'no-irregular-whitespace': 'error', 15 | 'no-obj-calls': 'error', 16 | 'no-sparse-arrays': 'error', 17 | 'no-unexpected-multiline': 'error', 18 | 'no-unreachable': 'error', 19 | 'no-unsafe-finally': 'error', 20 | 'use-isnan': 'error', 21 | 'valid-typeof': 'error', 22 | // Best practices 23 | 'array-callback-return': 'error', 24 | eqeqeq: ['error', 'always'], 25 | 'no-caller': 'error', 26 | 'no-case-declarations': 'error', 27 | 'no-empty-pattern': 'error', 28 | 'no-eq-null': 'error', 29 | 'no-eval': 'error', 30 | 'no-extend-native': 'error', 31 | 'no-extra-bind': 'error', 32 | 'no-extra-label': 'error', 33 | 'no-fallthrough': 'error', 34 | 'no-floating-decimal': 'error', 35 | 'no-global-assign': 'error', 36 | 'no-implicit-globals': 'error', 37 | 'no-implied-eval': 'error', 38 | 'no-iterator': 'error', 39 | 'no-lone-blocks': 'error', 40 | 'no-multi-spaces': 'error', 41 | 'no-multi-str': 'error', 42 | 'no-octal-escape': 'error', 43 | 'no-octal': 'error', 44 | 'no-proto': 'error', 45 | 'no-redeclare': 'error', 46 | 'no-return-await': 'error', 47 | 'no-script-url': 'error', 48 | 'no-self-assign': 'error', 49 | 'no-self-compare': 'error', 50 | 'no-sequences': 'error', 51 | 'no-throw-literal': 'error', 52 | 'no-unmodified-loop-condition': 'error', 53 | 'no-unused-expressions': ['error', { allowShortCircuit: true, allowTernary: true }], 54 | 'no-unused-labels': 'error', 55 | 'no-useless-call': 'error', 56 | 'no-useless-concat': 'error', 57 | 'no-useless-escape': 'error', 58 | 'no-useless-return': 'error', 59 | 'no-with': 'error', 60 | radix: ['error', 'as-needed'], 61 | 'no-delete-var': 'error', 62 | 'no-label-var': 'error', 63 | 'no-undef-init': 'error', 64 | 'no-unused-vars': 'error' 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [niklasramo] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | website 3 | /coverage 4 | /website 5 | *.log 6 | *.env 7 | *.DS_Store 8 | docs/.vitepress/dist 9 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '12' 4 | env: 5 | email: false 6 | global: 7 | - secure: UcHda6DVxvFcQPscXNG/6jd0pwVJoik0wx7AldoJSr1v5hZGXhdVuPUYpLVK53uhifl6DNxsovzCJF41zAg7QhJSqNaQlDXhoLghAC4iL/6VsVJBu4Jx2KZKrsPeMVJxqA6Q5FLQzF91jpkgV+pWf2g2RnXkDnm81EWIVQDhg83bs4HhrRTzdsND2nkl8iSucO9Nm3tDjC8rNaa9jagGyF3ZVTO+B/L/E0opNxPzZFdxL6UrQYjrplHjF4HEoNyBBLWf4p2Tco49xDFBNoTs8B+pBmI1i6WufuWiBhhMTwCYaKwQ0HGjKldC/v3kgYF3k+pnRQtQZdjlFrP48JI+BLn2lg0KrgU37xyDRatpTpOVD+sZ5rJlVIBYfTxgMBfdr4vQxXdXfQDwydCPl13b76XavJxEiivXvPP12IR1WD7Lpa6Smd8jroROCexwQiE8HKLaXF9uuLikgzqq2rbUNzKb3xxhMoRNNs194V44ez6oq5a2S5i9JLVmYyoGJP/hjCM7lM82mmkGIcbeFIg+V7By7VEBNLBZmWABYpSOdFv3aLPd/ckHrwhkS36wkttCiSyYUIKAKqeZwciCJqM7ibj/ZlHyUHRnXUbwLgJ+YwehJzFxotuF/9bf/7EUhHQYWVo+3+SMhg46B2nmFZW6ANWzXq06aiIoioEqd0RqiXY= 8 | - secure: UIZsGf28/ZLxTsxrn+AVOOtrc1tBQoIP4Ym0LiaPbL40QZFdmd8EDGpCPKJGVGY673xZ1FD8AnzrpufOUhqzlVDKlH3+CE6fhqBU9ckTnvngCvx3Tr5RkwMMBPEsdugAZA/BF1KpkRboiPXvrp+3z5Mo4CliBQofh/EYZUCkIziGXUrCiJrkO30IojVonLsq9mhiL5Vd6fHeYfQczqxdbW0Xaq9nm68J6yWyva5wlLsnhiDRZPmnb3CWZUkKIDm2YM9AyZROvHW1qfNL70XyAVNLaJ6sRCT5+0A6s0OkIYbfSDB/hhlaFyw7y7sR2lBCG6RpbIaYOKVR673exonKyamuoP5XhqAZ0avXimB6Es0aGBLdXcQusJzw+x3oji7c4r+987nb0vyzuLx9KpaaeHk8PgcNeenEe//1YtRcffp2LvyCaKAflVQurVrDOTfaSmPakvFm1fBH4U4aEqjQA1lk39yqRu+7+9BsWh5yGdIAlluTI1mU9M1TTr2K4Fj3+yaqlvc2pSjrKXBuGZwcF+3kRxUzu8qiPUmEJmjrv2FBdOObLexTba1ScpW83rwqFunkyEzlUGhNvpDq6jWZ3FfTbOqSWGAS6PLYXXbA0s4j3qXgQZrBKzRi2Swf69gqy4BkQ1jspSLi8pP0p6TEodEm5BVzd+A/CsMMUzxDcwY= 9 | -------------------------------------------------------------------------------- /AUTHORS.txt: -------------------------------------------------------------------------------- 1 | Authors ordered by first contribution. 2 | 3 | Niklas Rämö 4 | Indigane 5 | Jason Holland 6 | Aslak Hellesøy 7 | Makhambet Adiyetov 8 | Alexis Rouillard 9 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Muuri 2 | 3 | Thanks for the interest in contributing to Muuri! Here you will find some instructions on how to create an issue or a pull request. 4 | 5 | ## Creating an issue 6 | 7 | ### Questions 8 | 9 | First of all you should check out the existing [questions](https://github.com/haltu/muuri/issues?q=label%3Aquestion%20) and see if your question has been asked/answered already. If not, you can [create a new issue](https://github.com/haltu/muuri/issues/new) and explain the problem you're facing. 10 | 11 | ### Improvements 12 | 13 | Improvement ideas are always welcome! Please check first the existing [ideas](https://github.com/haltu/muuri/issues?utf8=%E2%9C%93&q=label%3Aidea), [features](https://github.com/haltu/muuri/issues?q=label%3Afeature) and [enhancements](https://github.com/haltu/muuri/issues?q=label%3Aenhancement) so that you won't be creating a duplicate issue. 14 | 15 | ### Bugs 16 | 17 | Please [create an issue](https://github.com/haltu/muuri/issues/new) and explain the bug in detail. If possible create a [reduced test case](https://css-tricks.com/reduced-test-cases/) and share a link to it. You can, for example, fork [this CodePen example](https://codepen.io/niklasramo/pen/jyJLGM) and modify it to demonstrate the bug. 18 | 19 | ## Creating a pull request 20 | 21 | 1. **Discuss first.** 22 | * The first step should always be [creating a new issue](https://github.com/haltu/muuri/issues/new) and discussing your pull request suggestion with the authors and the community. 23 | * After you get green light it's time to get coding. 24 | 2. **Fork the repo and create a new branch for your pull request.** 25 | * [Fork Muuri](https://github.com/haltu/muuri#fork-destination-box). 26 | * Create a new branch for your pull request from the master branch. The name of the pull request branch should start with the id of the issue you opened for the pull request, e.g. `#123-fix-something`. 27 | 3. **Setup the development environment.** 28 | * Run `npm install` in the repository's directory. 29 | * You can now run the following commands: 30 | * `npm run build` 31 | * Builds `dist/muuri.js` and `dist/muuri.min.js` from `src` directory. 32 | * `npm run lint` 33 | * Lints all files in `src` directory with ESLint. 34 | * `npm run format` 35 | * Formats all files in `src` directory with Prettier. 36 | * `npm run test` 37 | * Runs unit tests for `dist/muuri.js` and `dist/muuri.min.js` files in Sauce Labs. 38 | * To make this work you need to create an `.env` file the project root, which should contain `SAUCE_USERNAME` and `SAUCE_ACCESS_KEY` variables. 39 | * Launches chrome, firefox and safari by default. 40 | * You can provide arguments to launch specific browsers: `npm run test --chrome --firefox --safari --edge`. 41 | 4. **Do the updates in `src` folder.** 42 | * Now is the time to make the actual updates to Muuri. 43 | * Remember scope. Don't refactor things that are not related to the pull request. 44 | * After you're done update unit tests and docs (`README.md`) if necessary. 45 | * Also, if this is your first pull request to Muuri remember to add yourself to the `AUTHORS.txt` file, e.g. `John Doe `. 46 | 5. **Format, build and test changes.** 47 | * Run `npm run format`, `npm run build` and finally `npm run test`. 48 | 6. **Create the pull request.** 49 | * Do your best to explain what the pull request fixes. 50 | * Mention which issue(s) will be closed by the pull request, e.g. `Closes #123`. 51 | * Request a review from [@niklasramo](https://github.com/niklasramo) 52 | * After your pull request is accepted it will be merged to the [dev branch](https://github.com/haltu/muuri/tree/dev) and released with the next release. If you did only some minor change in the documentation it may be merged directly to the master branch. 53 | 7. **You made it! Thank you so much for contributing to Muuri!** 54 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | 2 | Copyright (c) 2015, Haltu Oy 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /docs/.vitepress/config.js: -------------------------------------------------------------------------------- 1 | import { version } from '../../package.json'; 2 | 3 | export default { 4 | title: 'Muuri Docs', 5 | description: 'Documentation for Muuri JavaScript library.', 6 | 7 | head: [['link', { rel: 'icon', type: 'image/svg+xml', href: '/muuri-icon.svg' }]], 8 | 9 | lastUpdated: true, 10 | 11 | // Experimental Feature - it is giving 404 when reloading the page in the docs 12 | // cleanUrls: 'without-subfolders', 13 | 14 | themeConfig: { 15 | logo: '/muuri-icon.svg', 16 | 17 | nav: nav(), 18 | 19 | sidebar: { 20 | '/': sidebarGuide(), 21 | }, 22 | 23 | editLink: { 24 | pattern: 'https://github.com/haltu/muuri/edit/master/docs/:path', 25 | text: 'Edit this page on GitHub', 26 | }, 27 | 28 | socialLinks: [{ icon: 'github', link: 'https://github.com/haltu/muuri' }], 29 | 30 | algolia: { 31 | appId: 'xxxxx', 32 | apiKey: 'xxxxx', 33 | indexName: 'muuri', 34 | }, 35 | 36 | // carbonAds: { 37 | // code: "xxxxx", 38 | // placement: "xxxxx", 39 | // }, 40 | }, 41 | }; 42 | 43 | function nav() { 44 | return [ 45 | { 46 | text: version, 47 | items: [ 48 | { 49 | text: 'Changelog', 50 | link: 'https://github.com/haltu/muuri/releases', 51 | }, 52 | { 53 | text: 'Contributing', 54 | link: 'https://github.com/haltu/muuri/blob/master/CONTRIBUTING.md', 55 | }, 56 | ], 57 | }, 58 | ]; 59 | } 60 | 61 | function sidebarGuide() { 62 | return [ 63 | { 64 | collapsible: false, 65 | items: [ 66 | { text: 'Introduction', link: '/' }, 67 | { text: 'Getting Started', link: '/getting-started' }, 68 | { text: 'Examples', link: '/examples' }, 69 | ], 70 | }, 71 | { 72 | text: 'API', 73 | collapsible: false, 74 | items: [ 75 | { text: 'Grid Constructor', link: '/grid-constructor' }, 76 | { text: 'Grid Options', link: '/grid-options' }, 77 | { text: 'Grid Methods', link: '/grid-methods' }, 78 | { text: 'Grid Events', link: '/grid-events' }, 79 | { text: 'Item Methods', link: '/item-methods' }, 80 | ], 81 | }, 82 | ]; 83 | } 84 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/custom.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --vp-c-brand: #ED6EE3; 3 | --vp-c-brand-light: #ED6EE3; 4 | --vp-c-brand-lighter:#FA73EF; 5 | --vp-c-green-dark:#B958D6; 6 | --vp-button-brand-bg: #ED6EE3; 7 | } -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.js: -------------------------------------------------------------------------------- 1 | import DefaultTheme from 'vitepress/theme'; 2 | import './custom.css'; 3 | 4 | export default DefaultTheme; 5 | -------------------------------------------------------------------------------- /docs/getting-started.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ## 1. Get Muuri 4 | 5 | Install via [npm](https://www.npmjs.com/package/muuri): 6 | 7 | ```bash 8 | npm install muuri 9 | ``` 10 | 11 | Or download: 12 | 13 | - [muuri.js](https://cdn.jsdelivr.net/npm/muuri@0.9.5/dist/muuri.js) - for development (not minified, with comments). 14 | - [muuri.min.js](https://cdn.jsdelivr.net/npm/muuri@0.9.5/dist/muuri.min.js) - for production (minified, no comments). 15 | 16 | Or link directly: 17 | 18 | ```html 19 | 20 | ``` 21 | 22 | ## 2. Get Web Animations Polyfill (if needed) 23 | 24 | Muuri uses [Web Animations](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API) to handle all the animations by default. If you need to use Muuri on a browser that does not support Web Animations you need to use a [polyfill](https://github.com/web-animations/web-animations-js). 25 | 26 | Install via [npm](https://www.npmjs.com/package/web-animations-js): 27 | 28 | ```bash 29 | npm install web-animations-js 30 | ``` 31 | 32 | Or download: 33 | 34 | - [web-animations.min.js](https://cdn.jsdelivr.net/npm/web-animations-js@2.3.2/web-animations.min.js) 35 | 36 | Or link directly: 37 | 38 | ```html 39 | 40 | ``` 41 | 42 | ## 3. Add the markup 43 | 44 | - Every grid must have a container element (referred as the _grid element_ from now on). 45 | - Grid items must always consist of at least two elements. The outer element is used for positioning the item and the inner element (first direct child) is used for animating the item's visibility (show/hide methods). You can insert any markup you wish inside the inner item element. 46 | - Note that the class names in the below example are not required by Muuri at all, they're just there for example's sake. 47 | 48 | ```html 49 |
50 |
51 |
52 | 53 | This can be anything. 54 | 55 |
56 |
57 | 58 |
59 |
60 | 61 |
Yippee!
62 | 63 |
64 |
65 |
66 | ``` 67 | 68 | ## 4. Add the styles 69 | 70 | - The grid element must be "positioned" meaning that it's CSS position property must be set to _relative_, _absolute_ or _fixed_. Also note that Muuri automatically resizes the grid element's width/height depending on the area the items cover and the layout algorithm configuration. 71 | - The item elements must have their CSS position set to _absolute_. 72 | - The item elements must not have any CSS transitions or animations applied to them, because they might conflict with Muuri's internal animation engine. However, the grid element can have transitions applied to it if you want it to animate when it's size changes after the layout operation. 73 | - You can control the gaps between the items by giving some margin to the item elements. 74 | - One last thing. Never ever set `overflow: auto;` or `overflow: scroll;` to the grid element. Muuri's calculation logic does not account for that and you _will_ see some item jumps when dragging starts. Always use a wrapper element for the grid element where you set the `auto`/`scroll` overflow values. 75 | 76 | ```css 77 | .grid { 78 | position: relative; 79 | } 80 | .item { 81 | display: block; 82 | position: absolute; 83 | width: 100px; 84 | height: 100px; 85 | margin: 5px; 86 | z-index: 1; 87 | background: #000; 88 | color: #fff; 89 | } 90 | .item.muuri-item-dragging { 91 | z-index: 3; 92 | } 93 | .item.muuri-item-releasing { 94 | z-index: 2; 95 | } 96 | .item.muuri-item-hidden { 97 | z-index: 0; 98 | } 99 | .item-content { 100 | position: relative; 101 | width: 100%; 102 | height: 100%; 103 | } 104 | ``` 105 | 106 | ## 5. Fire it up 107 | 108 | The bare minimum configuration is demonstrated below. You must always provide the grid element (or a selector so Muuri can fetch the element for you), everything else is optional. 109 | 110 | ```javascript 111 | var grid = new Muuri('.grid'); 112 | ``` 113 | 114 | You can view this little tutorial demo [here](https://codepen.io/niklasramo/pen/wpwNjK). After that you might want to check some [examples](/examples.html) as well. 115 | -------------------------------------------------------------------------------- /docs/grid-constructor.md: -------------------------------------------------------------------------------- 1 | # Grid Constructor 2 | 3 | `Muuri` is a constructor function and should be always instantiated with the `new` keyword. For the sake of clarity, we refer to a Muuri instance as _grid_ throughout the documentation. 4 | 5 | **Syntax** 6 | 7 | `Muuri( element, [options] )` 8 | 9 | **Parameters** 10 | 11 | - **element**  —  _element_ / _string_ 12 | - Default value: `null`. 13 | - You can provide the element directly or use a selector (string) which uses [querySelector()](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector) internally. 14 | - **options**  —  _object_ 15 | - Optional. Check out the [detailed options reference](#grid-options). 16 | 17 | **Default options** 18 | 19 | The default options are stored in `Muuri.defaultOptions` object, which in it's default state contains the following configuration: 20 | 21 | ```javascript 22 | { 23 | // Initial item elements 24 | items: '*', 25 | // Default show animation 26 | showDuration: 300, 27 | showEasing: 'ease', 28 | // Default hide animation 29 | hideDuration: 300, 30 | hideEasing: 'ease', 31 | // Item's visible/hidden state styles 32 | visibleStyles: { 33 | opacity: '1', 34 | transform: 'scale(1)' 35 | }, 36 | hiddenStyles: { 37 | opacity: '0', 38 | transform: 'scale(0.5)' 39 | }, 40 | // Layout 41 | layout: { 42 | fillGaps: false, 43 | horizontal: false, 44 | alignRight: false, 45 | alignBottom: false, 46 | rounding: false 47 | }, 48 | layoutOnResize: 150, 49 | layoutOnInit: true, 50 | layoutDuration: 300, 51 | layoutEasing: 'ease', 52 | // Sorting 53 | sortData: null, 54 | // Drag & Drop 55 | dragEnabled: false, 56 | dragContainer: null, 57 | dragHandle: null, 58 | dragStartPredicate: { 59 | distance: 0, 60 | delay: 0 61 | }, 62 | dragAxis: 'xy', 63 | dragSort: true, 64 | dragSortHeuristics: { 65 | sortInterval: 100, 66 | minDragDistance: 10, 67 | minBounceBackAngle: 1 68 | }, 69 | dragSortPredicate: { 70 | threshold: 50, 71 | action: 'move', 72 | migrateAction: 'move' 73 | }, 74 | dragRelease: { 75 | duration: 300, 76 | easing: 'ease', 77 | useDragContainer: true 78 | }, 79 | dragCssProps: { 80 | touchAction: 'none', 81 | userSelect: 'none', 82 | userDrag: 'none', 83 | tapHighlightColor: 'rgba(0, 0, 0, 0)', 84 | touchCallout: 'none', 85 | contentZooming: 'none' 86 | }, 87 | dragPlaceholder: { 88 | enabled: false, 89 | createElement: null, 90 | onCreate: null, 91 | onRemove: null 92 | }, 93 | dragAutoScroll: { 94 | targets: [], 95 | handle: null, 96 | threshold: 50, 97 | safeZone: 0.2, 98 | speed: Muuri.AutoScroller.smoothSpeed(1000, 2000, 2500), 99 | sortDuringScroll: true, 100 | smoothStop: false, 101 | onStart: null, 102 | onStop: null 103 | }, 104 | // Classnames 105 | containerClass: 'muuri', 106 | itemClass: 'muuri-item', 107 | itemVisibleClass: 'muuri-item-shown', 108 | itemHiddenClass: 'muuri-item-hidden', 109 | itemPositioningClass: 'muuri-item-positioning', 110 | itemDraggingClass: 'muuri-item-dragging', 111 | itemReleasingClass: 'muuri-item-releasing', 112 | itemPlaceholderClass: 'muuri-item-placeholder' 113 | } 114 | ``` 115 | 116 | You can modify the default options easily: 117 | 118 | ```javascript 119 | Muuri.defaultOptions.showDuration = 400; 120 | Muuri.defaultOptions.dragSortPredicate.action = 'swap'; 121 | ``` 122 | 123 | This is how you would use the options: 124 | 125 | ```javascript 126 | // Minimum configuration. 127 | var gridA = new Muuri('.grid-a'); 128 | // Providing some options. 129 | var gridB = new Muuri('.grid-b', { 130 | items: '.item', 131 | }); 132 | ``` 133 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Muuri Docs 2 | 3 | You've stumbled on the official documentation site of [Muuri](https://muuri.dev), a JavaScript layout engine. Here you will find detailed API documentation, usage guides and some nifty examples. 4 | 5 | Hopefully you'll find what you're looking for, but if not, please help us improve these docs. You can either [create an issue](https://github.com/haltu/muuri/issues/new) or submit a pull request on Muuri's repo. 6 | -------------------------------------------------------------------------------- /docs/item-methods.md: -------------------------------------------------------------------------------- 1 | # Item Methods 2 | 3 | ## getGrid 4 | 5 | `item.getGrid()` 6 | 7 | Get the grid instance the item belongs to. 8 | 9 | **Returns**  —  _Muuri_ 10 | 11 | **Examples** 12 | 13 | ```javascript 14 | const grid = item.getGrid(); 15 | ``` 16 | 17 | ## getElement 18 | 19 | `item.getElement()` 20 | 21 | Get the item element. 22 | 23 | **Returns**  —  _element_ 24 | 25 | **Examples** 26 | 27 | ```javascript 28 | const elem = item.getElement(); 29 | ``` 30 | 31 | ## getWidth 32 | 33 | `item.getWidth()` 34 | 35 | Get item element's cached width (in pixels). The returned value includes the element's paddings and borders. 36 | 37 | **Returns**  —  _number_ 38 | 39 | **Examples** 40 | 41 | ```javascript 42 | const width = item.getWidth(); 43 | ``` 44 | 45 | ## getHeight 46 | 47 | `item.getWidth()` 48 | 49 | Get item element's cached height (in pixels). The returned value includes the element's paddings and borders. 50 | 51 | **Returns**  —  _number_ 52 | 53 | **Examples** 54 | 55 | ```javascript 56 | const height = item.getHeight(); 57 | ``` 58 | 59 | ## getMargin 60 | 61 | `item.getMargin()` 62 | 63 | Get item element's cached margins (in pixels). 64 | 65 | **Returns**  —  _object_ 66 | 67 | - **obj.left**  —  _number_ 68 | - **obj.right**  —  _number_ 69 | - **obj.top**  —  _number_ 70 | - **obj.bottom**  —  _number_ 71 | 72 | **Examples** 73 | 74 | ```javascript 75 | const margin = item.getMargin(); 76 | ``` 77 | 78 | ## getPosition 79 | 80 | `item.getPosition()` 81 | 82 | Get item element's cached position (in pixels, relative to the grid element). 83 | 84 | **Returns**  —  _object_ 85 | 86 | - **obj.left**  —  _number_ 87 | - **obj.top**  —  _number_ 88 | 89 | **Examples** 90 | 91 | ```javascript 92 | const position = item.getPosition(); 93 | ``` 94 | 95 | ## isActive 96 | 97 | `item.isActive()` 98 | 99 | Check if the item is currently _active_. Only active items are considered to be part of the layout. 100 | 101 | **Returns**  —  _boolean_ 102 | 103 | **Examples** 104 | 105 | ```javascript 106 | const isActive = item.isActive(); 107 | ``` 108 | 109 | ## isVisible 110 | 111 | `item.isVisible()` 112 | 113 | Check if the item is currently _visible_. 114 | 115 | **Returns**  —  _boolean_ 116 | 117 | **Examples** 118 | 119 | ```javascript 120 | const isVisible = item.isVisible(); 121 | ``` 122 | 123 | ## isShowing 124 | 125 | `item.isShowing()` 126 | 127 | Check if the item is currently animating to visible. 128 | 129 | **Returns**  —  _boolean_ 130 | 131 | **Examples** 132 | 133 | ```javascript 134 | const isShowing = item.isShowing(); 135 | ``` 136 | 137 | ## isHiding 138 | 139 | `item.isHiding()` 140 | 141 | Check if the item is currently animating to hidden. 142 | 143 | **Returns**  —  _boolean_ 144 | 145 | **Examples** 146 | 147 | ```javascript 148 | const isHiding = item.isHiding(); 149 | ``` 150 | 151 | ## isPositioning 152 | 153 | `item.isPositioning()` 154 | 155 | Check if the item is currently being positioned. 156 | 157 | **Returns**  —  _boolean_ 158 | 159 | **Examples** 160 | 161 | ```javascript 162 | const isPositioning = item.isPositioning(); 163 | ``` 164 | 165 | ## isDragging 166 | 167 | `item.isDragging()` 168 | 169 | Check if the item is currently being dragged. 170 | 171 | **Returns**  —  _boolean_ 172 | 173 | **Examples** 174 | 175 | ```javascript 176 | const isDragging = item.isDragging(); 177 | ``` 178 | 179 | ## isReleasing 180 | 181 | `item.isReleasing()` 182 | 183 | Check if the item is currently being released. 184 | 185 | **Returns**  —  _boolean_ 186 | 187 | **Examples** 188 | 189 | ```javascript 190 | const isReleasing = item.isReleasing(); 191 | ``` 192 | 193 | ## isDestroyed 194 | 195 | `item.isDestroyed()` 196 | 197 | Check if the item is destroyed. 198 | 199 | **Returns**  —  _boolean_ 200 | 201 | **Examples** 202 | 203 | ```javascript 204 | const isDestroyed = item.isDestroyed(); 205 | ``` 206 | -------------------------------------------------------------------------------- /docs/public/muuri-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const gulp = require('gulp'); 3 | const eslint = require('gulp-eslint'); 4 | const karma = require('karma'); 5 | const size = require('gulp-size'); 6 | const rimraf = require('rimraf'); 7 | const dotenv = require('dotenv'); 8 | const exec = require('child_process').exec; 9 | 10 | const pkg = require('./package.json'); 11 | const karmaDefaults = require('./karma.defaults.js'); 12 | 13 | if (fs.existsSync('./.env')) dotenv.config(); 14 | 15 | gulp.task('lint', () => { 16 | return gulp 17 | .src('./src/**/*.js') 18 | .pipe(eslint()) 19 | .pipe(eslint.format()) 20 | .pipe(eslint.failAfterError()); 21 | }); 22 | 23 | gulp.task('size', () => { 24 | const mainPath = './' + pkg.main; 25 | const minifiedPath = mainPath.replace('.js', '.min.js'); 26 | 27 | return gulp 28 | .src([mainPath, minifiedPath]) 29 | .pipe( 30 | size({ 31 | showFiles: true, 32 | showTotal: false, 33 | }) 34 | ) 35 | .pipe( 36 | size({ 37 | showFiles: true, 38 | showTotal: false, 39 | title: 'gzipped', 40 | gzip: true, 41 | }) 42 | ); 43 | }); 44 | 45 | gulp.task('clean', (cb) => { 46 | rimraf('./*.log', cb); 47 | }); 48 | 49 | gulp.task('test-local', (done) => { 50 | // Setup browsers. 51 | const browsers = ['Chrome']; 52 | 53 | new karma.Server( 54 | { 55 | configFile: __dirname + '/karma.conf.js', 56 | action: 'run', 57 | browsers, 58 | }, 59 | (exitCode) => { 60 | done(exitCode); 61 | } 62 | ).start(); 63 | }); 64 | 65 | gulp.task('test-local-min', (done) => { 66 | // Setup browsers. 67 | const browsers = ['Chrome']; 68 | 69 | // Replace main file with minified version. 70 | const mainPath = './' + pkg.main; 71 | const minifiedPath = mainPath.replace('.js', '.min.js'); 72 | const files = karmaDefaults.files.map((path) => { 73 | if (path === mainPath) return minifiedPath; 74 | return path; 75 | }); 76 | 77 | new karma.Server( 78 | { 79 | configFile: __dirname + '/karma.conf.js', 80 | action: 'run', 81 | browsers, 82 | files, 83 | }, 84 | (exitCode) => { 85 | done(exitCode); 86 | } 87 | ).start(); 88 | }); 89 | 90 | gulp.task('test-sauce', (done) => { 91 | // Setup browsers. 92 | const browsers = ['slChrome', 'slFirefox', 'slSafari']; 93 | 94 | new karma.Server( 95 | { 96 | configFile: __dirname + '/karma.conf.js', 97 | action: 'run', 98 | browsers, 99 | }, 100 | (exitCode) => { 101 | done(exitCode); 102 | } 103 | ).start(); 104 | }); 105 | 106 | gulp.task('test-sauce-min', (done) => { 107 | // Setup browsers. 108 | const browsers = ['slChrome', 'slFirefox', 'slSafari']; 109 | 110 | // Replace main file with minified version. 111 | const mainPath = './' + pkg.main; 112 | const minifiedPath = mainPath.replace('.js', '.min.js'); 113 | const files = karmaDefaults.files.map((path) => { 114 | if (path === mainPath) return minifiedPath; 115 | return path; 116 | }); 117 | 118 | new karma.Server( 119 | { 120 | configFile: __dirname + '/karma.conf.js', 121 | action: 'run', 122 | browsers, 123 | files, 124 | }, 125 | (exitCode) => { 126 | done(exitCode); 127 | } 128 | ).start(); 129 | }); 130 | 131 | gulp.task('validate-formatting', (cb) => { 132 | exec('npm run validate-formatting', (err, stdout, stderr) => { 133 | console.log(stdout); 134 | console.log(stderr); 135 | cb(err); 136 | }); 137 | }); 138 | 139 | gulp.task('bundle', (cb) => { 140 | exec('npm run bundle', (err, stdout, stderr) => { 141 | console.log(stdout); 142 | console.log(stderr); 143 | cb(err); 144 | }); 145 | }); 146 | 147 | gulp.task('minify', (cb) => { 148 | exec('npm run minify', (err, stdout, stderr) => { 149 | console.log(stdout); 150 | console.log(stderr); 151 | cb(err); 152 | }); 153 | }); 154 | 155 | gulp.task( 156 | 'build', 157 | gulp.series('bundle', 'minify', (done) => { 158 | done(); 159 | }) 160 | ); 161 | 162 | gulp.task( 163 | 'pre-commit', 164 | gulp.series('lint', 'validate-formatting', (done) => { 165 | done(); 166 | }) 167 | ); 168 | 169 | gulp.task( 170 | 'test', 171 | gulp.series('lint', 'validate-formatting', 'test-sauce', 'test-sauce-min', 'clean', (done) => { 172 | done(); 173 | }) 174 | ); 175 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | const defaultSettings = require('./karma.defaults.js'); 2 | 3 | module.exports = function (config) { 4 | config.set(defaultSettings); 5 | }; -------------------------------------------------------------------------------- /karma.defaults.js: -------------------------------------------------------------------------------- 1 | const pkg = require('./package.json'); 2 | 3 | module.exports = { 4 | basePath: '', 5 | frameworks: ['qunit'], 6 | plugins: ['karma-qunit', 'karma-chrome-launcher', 'karma-sauce-launcher', 'karma-story-reporter'], 7 | files: [ 8 | './node_modules/web-animations-js/web-animations.min.js', 9 | './node_modules/prosthetic-hand/dist/prosthetic-hand.js', 10 | './node_modules/mezr/mezr.js', 11 | './' + pkg.main, 12 | './tests/index.js', 13 | './tests/utils.js', 14 | './tests/grid-constructor/*.js', 15 | './tests/grid-options/*.js', 16 | './tests/grid-methods/*.js', 17 | './tests/grid-events/*.js', 18 | './tests/item-methods/*.js', 19 | ], 20 | reporters: ['story', 'saucelabs'], 21 | colors: true, 22 | autoWatch: false, 23 | browserDisconnectTolerance: 2, 24 | singleRun: true, 25 | hostname: '127.0.0.1', 26 | sauceLabs: { 27 | testName: pkg.name + ' - ' + pkg.version + ' - unit tests', 28 | }, 29 | customLaunchers: { 30 | slChrome: { 31 | base: 'SauceLabs', 32 | browserName: 'chrome', 33 | platform: 'Windows 10', 34 | version: 'latest', 35 | }, 36 | slFirefox: { 37 | base: 'SauceLabs', 38 | browserName: 'firefox', 39 | platform: 'Windows 10', 40 | version: 'latest', 41 | }, 42 | slSafari: { 43 | base: 'SauceLabs', 44 | browserName: 'safari', 45 | platform: 'macOS 10.12', 46 | version: 'latest', 47 | }, 48 | slEdge: { 49 | base: 'SauceLabs', 50 | browserName: 'MicrosoftEdge', 51 | platform: 'Windows 10', 52 | version: 'latest', 53 | }, 54 | }, 55 | }; 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "muuri", 3 | "version": "0.9.5", 4 | "description": "Responsive, sortable, filterable and draggable layouts", 5 | "keywords": [ 6 | "grid", 7 | "layout", 8 | "bin-packing", 9 | "filter", 10 | "sort", 11 | "drag" 12 | ], 13 | "homepage": "https://muuri.dev/", 14 | "license": "MIT", 15 | "author": { 16 | "name": "Niklas Rämö", 17 | "email": "inramo@gmail.com", 18 | "url": "https://github.com/niklasramo" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git@github.com:haltu/muuri.git" 23 | }, 24 | "main": "dist/muuri.js", 25 | "module": "dist/muuri.module.js", 26 | "types": "src/index.d.ts", 27 | "files": [ 28 | "package.json", 29 | "src", 30 | "dist", 31 | "README.md", 32 | "LICENSE.md", 33 | "AUTHORS.txt", 34 | "CONTRIBUTING.md" 35 | ], 36 | "scripts": { 37 | "test": "./node_modules/.bin/gulp test", 38 | "test-sauce": "./node_modules/.bin/gulp test-sauce", 39 | "test-sauce-min": "./node_modules/.bin/gulp test-sauce-min", 40 | "test-local": "./node_modules/.bin/gulp test-local", 41 | "test-local-min": "./node_modules/.bin/gulp test-local-min", 42 | "bundle": "./node_modules/.bin/rollup -c", 43 | "minify": "./node_modules/.bin/terser ./dist/muuri.js -o ./dist/muuri.min.js -c -m --comments", 44 | "build": "./node_modules/.bin/gulp build", 45 | "lint": "./node_modules/.bin/gulp lint", 46 | "format": "./node_modules/.bin/prettier --write ./src/**/*.js", 47 | "format-tests": "./node_modules/.bin/prettier --write ./tests/**/*.js", 48 | "validate-formatting": "./node_modules/.bin/prettier --list-different ./src/**/*.js", 49 | "size": "./node_modules/.bin/gulp size", 50 | "pre-commit-hook": "./node_modules/.bin/gulp pre-commit", 51 | "docs-dev": "vitepress dev docs", 52 | "docs-build": "vitepress build docs", 53 | "docs-serve": "vitepress serve docs" 54 | }, 55 | "husky": { 56 | "hooks": { 57 | "pre-commit": "npm run pre-commit-hook" 58 | } 59 | }, 60 | "devDependencies": { 61 | "dotenv": "10.0.0", 62 | "gulp": "4.0.2", 63 | "gulp-eslint": "6.0.0", 64 | "gulp-size": "4.0.1", 65 | "husky": "4.3.8", 66 | "karma": "6.3.4", 67 | "karma-chrome-launcher": "3.1.0", 68 | "karma-qunit": "4.1.2", 69 | "karma-sauce-launcher": "4.3.6", 70 | "karma-story-reporter": "0.3.1", 71 | "mezr": "0.6.2", 72 | "prettier": "2.3.2", 73 | "prosthetic-hand": "1.3.1", 74 | "qunit": "2.16.0", 75 | "rimraf": "3.0.2", 76 | "rollup": "2.52.8", 77 | "terser": "5.7.1", 78 | "web-animations-js": "2.3.2", 79 | "vitepress": "1.0.0-alpha.14", 80 | "vue": "3.2.39" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /rollup.banner.js: -------------------------------------------------------------------------------- 1 | const pkg = require('./package.json'); 2 | 3 | module.exports = `/** 4 | * Muuri v${pkg.version} 5 | * ${pkg.homepage} 6 | * Copyright (c) 2015-present, Haltu Oy 7 | * Released under the MIT license 8 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 9 | * @license MIT 10 | * 11 | * Muuri Packer 12 | * Copyright (c) 2016-present, Niklas Rämö 13 | * @license MIT 14 | * 15 | * Muuri Ticker / Muuri Emitter / Muuri Dragger 16 | * Copyright (c) 2018-present, Niklas Rämö 17 | * @license MIT 18 | * 19 | * Muuri AutoScroller 20 | * Copyright (c) 2019-present, Niklas Rämö 21 | * @license MIT 22 | */ 23 | `; 24 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | const pkg = require('./package.json'); 2 | const banner = require('./rollup.banner.js'); 3 | 4 | const stripBanner = { 5 | transform(code) { 6 | return { 7 | code: code.replace(/\/\*\*([\s\S]*?)\*\//, ''), 8 | map: { mappings: '' } 9 | }; 10 | } 11 | }; 12 | 13 | module.exports = { 14 | input: 'src/index.js', 15 | output: [ 16 | { 17 | name: 'Muuri', 18 | file: pkg.main, 19 | format: 'umd', 20 | banner: banner 21 | }, 22 | { 23 | name: 'Muuri', 24 | file: pkg.module, 25 | format: 'es', 26 | banner: banner 27 | } 28 | ], 29 | plugins: [stripBanner] 30 | }; 31 | -------------------------------------------------------------------------------- /src/AutoScroller/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019, Niklas Rämö 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/AutoScroller/Pool.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Muuri AutoScroller 3 | * Copyright (c) 2019-present, Niklas Rämö 4 | * Released under the MIT license 5 | * https://github.com/haltu/muuri/blob/master/src/AutoScroller/LICENSE.md 6 | */ 7 | 8 | export default function Pool(createItem, releaseItem) { 9 | this.pool = []; 10 | this.createItem = createItem; 11 | this.releaseItem = releaseItem; 12 | } 13 | 14 | Pool.prototype.pick = function () { 15 | return this.pool.pop() || this.createItem(); 16 | }; 17 | 18 | Pool.prototype.release = function (item) { 19 | this.releaseItem(item); 20 | if (this.pool.indexOf(item) !== -1) return; 21 | this.pool.push(item); 22 | }; 23 | 24 | Pool.prototype.reset = function () { 25 | this.pool.length = 0; 26 | }; 27 | -------------------------------------------------------------------------------- /src/AutoScroller/ScrollAction.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Muuri AutoScroller 3 | * Copyright (c) 2019-present, Niklas Rämö 4 | * Released under the MIT license 5 | * https://github.com/haltu/muuri/blob/master/src/AutoScroller/LICENSE.md 6 | */ 7 | 8 | import { getScrollLeft, getScrollTop } from './utils'; 9 | import { AXIS_X } from './constants'; 10 | 11 | export default function ScrollAction() { 12 | this.element = null; 13 | this.requestX = null; 14 | this.requestY = null; 15 | this.scrollLeft = 0; 16 | this.scrollTop = 0; 17 | } 18 | 19 | ScrollAction.prototype.reset = function () { 20 | if (this.requestX) this.requestX.action = null; 21 | if (this.requestY) this.requestY.action = null; 22 | this.element = null; 23 | this.requestX = null; 24 | this.requestY = null; 25 | this.scrollLeft = 0; 26 | this.scrollTop = 0; 27 | }; 28 | 29 | ScrollAction.prototype.addRequest = function (request) { 30 | if (AXIS_X & request.direction) { 31 | this.removeRequest(this.requestX); 32 | this.requestX = request; 33 | } else { 34 | this.removeRequest(this.requestY); 35 | this.requestY = request; 36 | } 37 | request.action = this; 38 | }; 39 | 40 | ScrollAction.prototype.removeRequest = function (request) { 41 | if (!request) return; 42 | if (this.requestX === request) { 43 | this.requestX = null; 44 | request.action = null; 45 | } else if (this.requestY === request) { 46 | this.requestY = null; 47 | request.action = null; 48 | } 49 | }; 50 | 51 | ScrollAction.prototype.computeScrollValues = function () { 52 | this.scrollLeft = this.requestX ? this.requestX.value : getScrollLeft(this.element); 53 | this.scrollTop = this.requestY ? this.requestY.value : getScrollTop(this.element); 54 | }; 55 | 56 | ScrollAction.prototype.scroll = function () { 57 | var element = this.element; 58 | if (!element) return; 59 | 60 | if (element.scrollTo) { 61 | element.scrollTo(this.scrollLeft, this.scrollTop); 62 | } else { 63 | element.scrollLeft = this.scrollLeft; 64 | element.scrollTop = this.scrollTop; 65 | } 66 | }; 67 | -------------------------------------------------------------------------------- /src/AutoScroller/ScrollRequest.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Muuri AutoScroller 3 | * Copyright (c) 2019-present, Niklas Rämö 4 | * Released under the MIT license 5 | * https://github.com/haltu/muuri/blob/master/src/AutoScroller/LICENSE.md 6 | */ 7 | 8 | import isFunction from '../utils/isFunction'; 9 | import { AXIS_X, FORWARD } from './constants'; 10 | import { getScrollLeft, getScrollTop, getItemAutoScrollSettings } from './utils'; 11 | 12 | export default function ScrollRequest() { 13 | this.reset(); 14 | } 15 | 16 | ScrollRequest.prototype.reset = function () { 17 | if (this.isActive) this.onStop(); 18 | this.item = null; 19 | this.element = null; 20 | this.isActive = false; 21 | this.isEnding = false; 22 | this.direction = null; 23 | this.value = null; 24 | this.maxValue = 0; 25 | this.threshold = 0; 26 | this.distance = 0; 27 | this.speed = 0; 28 | this.duration = 0; 29 | this.action = null; 30 | }; 31 | 32 | ScrollRequest.prototype.hasReachedEnd = function () { 33 | return FORWARD & this.direction ? this.value >= this.maxValue : this.value <= 0; 34 | }; 35 | 36 | ScrollRequest.prototype.computeCurrentScrollValue = function () { 37 | if (this.value === null) { 38 | return AXIS_X & this.direction ? getScrollLeft(this.element) : getScrollTop(this.element); 39 | } 40 | return Math.max(0, Math.min(this.value, this.maxValue)); 41 | }; 42 | 43 | ScrollRequest.prototype.computeNextScrollValue = function (deltaTime) { 44 | var delta = this.speed * (deltaTime / 1000); 45 | var nextValue = FORWARD & this.direction ? this.value + delta : this.value - delta; 46 | return Math.max(0, Math.min(nextValue, this.maxValue)); 47 | }; 48 | 49 | ScrollRequest.prototype.computeSpeed = (function () { 50 | var data = { 51 | direction: null, 52 | threshold: 0, 53 | distance: 0, 54 | value: 0, 55 | maxValue: 0, 56 | deltaTime: 0, 57 | duration: 0, 58 | isEnding: false, 59 | }; 60 | 61 | return function (deltaTime) { 62 | var item = this.item; 63 | var speed = getItemAutoScrollSettings(item).speed; 64 | 65 | if (isFunction(speed)) { 66 | data.direction = this.direction; 67 | data.threshold = this.threshold; 68 | data.distance = this.distance; 69 | data.value = this.value; 70 | data.maxValue = this.maxValue; 71 | data.duration = this.duration; 72 | data.speed = this.speed; 73 | data.deltaTime = deltaTime; 74 | data.isEnding = this.isEnding; 75 | return speed(item, this.element, data); 76 | } else { 77 | return speed; 78 | } 79 | }; 80 | })(); 81 | 82 | ScrollRequest.prototype.tick = function (deltaTime) { 83 | if (!this.isActive) { 84 | this.isActive = true; 85 | this.onStart(); 86 | } 87 | this.value = this.computeCurrentScrollValue(); 88 | this.speed = this.computeSpeed(deltaTime); 89 | this.value = this.computeNextScrollValue(deltaTime); 90 | this.duration += deltaTime; 91 | return this.value; 92 | }; 93 | 94 | ScrollRequest.prototype.onStart = function () { 95 | var item = this.item; 96 | var onStart = getItemAutoScrollSettings(item).onStart; 97 | if (isFunction(onStart)) onStart(item, this.element, this.direction); 98 | }; 99 | 100 | ScrollRequest.prototype.onStop = function () { 101 | var item = this.item; 102 | var onStop = getItemAutoScrollSettings(item).onStop; 103 | if (isFunction(onStop)) onStop(item, this.element, this.direction); 104 | // Manually nudge sort to happen. There's a good chance that the item is still 105 | // after the scroll stops which means that the next sort will be triggered 106 | // only after the item is moved or it's parent scrolled. 107 | if (item._drag) item._drag.sort(); 108 | }; 109 | -------------------------------------------------------------------------------- /src/AutoScroller/constants.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Muuri AutoScroller 3 | * Copyright (c) 2019-present, Niklas Rämö 4 | * Released under the MIT license 5 | * https://github.com/haltu/muuri/blob/master/src/AutoScroller/LICENSE.md 6 | */ 7 | 8 | export var AXIS_X = 1; 9 | export var AXIS_Y = 2; 10 | export var FORWARD = 4; 11 | export var BACKWARD = 8; 12 | export var LEFT = AXIS_X | BACKWARD; 13 | export var RIGHT = AXIS_X | FORWARD; 14 | export var UP = AXIS_Y | BACKWARD; 15 | export var DOWN = AXIS_Y | FORWARD; 16 | -------------------------------------------------------------------------------- /src/AutoScroller/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Muuri AutoScroller 3 | * Copyright (c) 2019-present, Niklas Rämö 4 | * Released under the MIT license 5 | * https://github.com/haltu/muuri/blob/master/src/AutoScroller/LICENSE.md 6 | */ 7 | 8 | import getStyleAsFloat from '../utils/getStyleAsFloat'; 9 | 10 | var DOC_ELEM = document.documentElement; 11 | var BODY = document.body; 12 | var THRESHOLD_DATA = { value: 0, offset: 0 }; 13 | 14 | /** 15 | * @param {HTMLElement|Window} element 16 | * @returns {HTMLElement|Window} 17 | */ 18 | export function getScrollElement(element) { 19 | if (element === window || element === DOC_ELEM || element === BODY) { 20 | return window; 21 | } else { 22 | return element; 23 | } 24 | } 25 | 26 | /** 27 | * @param {HTMLElement|Window} element 28 | * @returns {Number} 29 | */ 30 | export function getScrollLeft(element) { 31 | return element === window ? element.pageXOffset : element.scrollLeft; 32 | } 33 | 34 | /** 35 | * @param {HTMLElement|Window} element 36 | * @returns {Number} 37 | */ 38 | export function getScrollTop(element) { 39 | return element === window ? element.pageYOffset : element.scrollTop; 40 | } 41 | 42 | /** 43 | * @param {HTMLElement|Window} element 44 | * @returns {Number} 45 | */ 46 | export function getScrollLeftMax(element) { 47 | if (element === window) { 48 | return DOC_ELEM.scrollWidth - DOC_ELEM.clientWidth; 49 | } else { 50 | return element.scrollWidth - element.clientWidth; 51 | } 52 | } 53 | 54 | /** 55 | * @param {HTMLElement|Window} element 56 | * @returns {Number} 57 | */ 58 | export function getScrollTopMax(element) { 59 | if (element === window) { 60 | return DOC_ELEM.scrollHeight - DOC_ELEM.clientHeight; 61 | } else { 62 | return element.scrollHeight - element.clientHeight; 63 | } 64 | } 65 | 66 | /** 67 | * Get window's or element's client rectangle data relative to the element's 68 | * content dimensions (includes inner size + padding, excludes scrollbars, 69 | * borders and margins). 70 | * 71 | * @param {HTMLElement|Window} element 72 | * @returns {Rectangle} 73 | */ 74 | export function getContentRect(element, result) { 75 | result = result || {}; 76 | 77 | if (element === window) { 78 | result.width = DOC_ELEM.clientWidth; 79 | result.height = DOC_ELEM.clientHeight; 80 | result.left = 0; 81 | result.right = result.width; 82 | result.top = 0; 83 | result.bottom = result.height; 84 | } else { 85 | var bcr = element.getBoundingClientRect(); 86 | var borderLeft = element.clientLeft || getStyleAsFloat(element, 'border-left-width'); 87 | var borderTop = element.clientTop || getStyleAsFloat(element, 'border-top-width'); 88 | result.width = element.clientWidth; 89 | result.height = element.clientHeight; 90 | result.left = bcr.left + borderLeft; 91 | result.right = result.left + result.width; 92 | result.top = bcr.top + borderTop; 93 | result.bottom = result.top + result.height; 94 | } 95 | 96 | return result; 97 | } 98 | 99 | /** 100 | * @param {Item} item 101 | * @returns {Object} 102 | */ 103 | export function getItemAutoScrollSettings(item) { 104 | return item._drag._getGrid()._settings.dragAutoScroll; 105 | } 106 | 107 | /** 108 | * @param {Item} item 109 | */ 110 | export function prepareItemScrollSync(item) { 111 | if (!item._drag) return; 112 | item._drag._prepareScroll(); 113 | } 114 | 115 | /** 116 | * @param {Item} item 117 | */ 118 | export function applyItemScrollSync(item) { 119 | if (!item._drag || !item._isActive) return; 120 | var drag = item._drag; 121 | drag._scrollDiffX = drag._scrollDiffY = 0; 122 | item._setTranslate(drag._left, drag._top); 123 | } 124 | 125 | /** 126 | * Compute threshold value and edge offset. 127 | * 128 | * @param {Number} threshold 129 | * @param {Number} safeZone 130 | * @param {Number} itemSize 131 | * @param {Number} targetSize 132 | * @returns {Object} 133 | */ 134 | export function computeThreshold(threshold, safeZone, itemSize, targetSize) { 135 | THRESHOLD_DATA.value = Math.min(targetSize / 2, threshold); 136 | THRESHOLD_DATA.offset = 137 | Math.max(0, itemSize + THRESHOLD_DATA.value * 2 + targetSize * safeZone - targetSize) / 2; 138 | return THRESHOLD_DATA; 139 | } 140 | -------------------------------------------------------------------------------- /src/Dragger/EdgeHack.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Muuri Dragger 3 | * Copyright (c) 2018-present, Niklas Rämö 4 | * Released under the MIT license 5 | * https://github.com/haltu/muuri/blob/master/src/Dragger/LICENSE.md 6 | */ 7 | 8 | import { HAS_POINTER_EVENTS, HAS_MS_POINTER_EVENTS } from '../constants'; 9 | 10 | var pointerout = HAS_POINTER_EVENTS ? 'pointerout' : HAS_MS_POINTER_EVENTS ? 'MSPointerOut' : ''; 11 | var waitDuration = 100; 12 | 13 | /** 14 | * If you happen to use Edge or IE on a touch capable device there is a 15 | * a specific case where pointercancel and pointerend events are never emitted, 16 | * even though one them should always be emitted when you release your finger 17 | * from the screen. The bug appears specifically when Muuri shifts the dragged 18 | * element's position in the DOM after pointerdown event, IE and Edge don't like 19 | * that behaviour and quite often forget to emit the pointerend/pointercancel 20 | * event. But, they do emit pointerout event so we utilize that here. 21 | * Specifically, if there has been no pointermove event within 100 milliseconds 22 | * since the last pointerout event we force cancel the drag operation. This hack 23 | * works surprisingly well 99% of the time. There is that 1% chance there still 24 | * that dragged items get stuck but it is what it is. 25 | * 26 | * @class 27 | * @param {Dragger} dragger 28 | */ 29 | function EdgeHack(dragger) { 30 | if (!pointerout) return; 31 | 32 | this._dragger = dragger; 33 | this._timeout = null; 34 | this._outEvent = null; 35 | this._isActive = false; 36 | 37 | this._addBehaviour = this._addBehaviour.bind(this); 38 | this._removeBehaviour = this._removeBehaviour.bind(this); 39 | this._onTimeout = this._onTimeout.bind(this); 40 | this._resetData = this._resetData.bind(this); 41 | this._onStart = this._onStart.bind(this); 42 | this._onOut = this._onOut.bind(this); 43 | 44 | this._dragger.on('start', this._onStart); 45 | } 46 | 47 | /** 48 | * @private 49 | */ 50 | EdgeHack.prototype._addBehaviour = function () { 51 | if (this._isActive) return; 52 | this._isActive = true; 53 | this._dragger.on('move', this._resetData); 54 | this._dragger.on('cancel', this._removeBehaviour); 55 | this._dragger.on('end', this._removeBehaviour); 56 | window.addEventListener(pointerout, this._onOut); 57 | }; 58 | 59 | /** 60 | * @private 61 | */ 62 | EdgeHack.prototype._removeBehaviour = function () { 63 | if (!this._isActive) return; 64 | this._dragger.off('move', this._resetData); 65 | this._dragger.off('cancel', this._removeBehaviour); 66 | this._dragger.off('end', this._removeBehaviour); 67 | window.removeEventListener(pointerout, this._onOut); 68 | this._resetData(); 69 | this._isActive = false; 70 | }; 71 | 72 | /** 73 | * @private 74 | */ 75 | EdgeHack.prototype._resetData = function () { 76 | window.clearTimeout(this._timeout); 77 | this._timeout = null; 78 | this._outEvent = null; 79 | }; 80 | 81 | /** 82 | * @private 83 | * @param {(PointerEvent|TouchEvent|MouseEvent)} e 84 | */ 85 | EdgeHack.prototype._onStart = function (e) { 86 | if (e.pointerType === 'mouse') return; 87 | this._addBehaviour(); 88 | }; 89 | 90 | /** 91 | * @private 92 | * @param {(PointerEvent|TouchEvent|MouseEvent)} e 93 | */ 94 | EdgeHack.prototype._onOut = function (e) { 95 | if (!this._dragger._getTrackedTouch(e)) return; 96 | this._resetData(); 97 | this._outEvent = e; 98 | this._timeout = window.setTimeout(this._onTimeout, waitDuration); 99 | }; 100 | 101 | /** 102 | * @private 103 | */ 104 | EdgeHack.prototype._onTimeout = function () { 105 | var e = this._outEvent; 106 | this._resetData(); 107 | if (this._dragger.isActive()) this._dragger._onCancel(e); 108 | }; 109 | 110 | /** 111 | * @public 112 | */ 113 | EdgeHack.prototype.destroy = function () { 114 | if (!pointerout) return; 115 | this._dragger.off('start', this._onStart); 116 | this._removeBehaviour(); 117 | }; 118 | 119 | export default EdgeHack; 120 | -------------------------------------------------------------------------------- /src/Dragger/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, Niklas Rämö 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/Emitter/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, Niklas Rämö 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/Packer/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016, Niklas Rämö 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/Ticker/LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018, Niklas Rämö 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/Ticker/Ticker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Muuri Ticker 3 | * Copyright (c) 2018-present, Niklas Rämö 4 | * Released under the MIT license 5 | * https://github.com/haltu/muuri/blob/master/src/Ticker/LICENSE.md 6 | */ 7 | 8 | import raf from '../utils/raf'; 9 | 10 | /** 11 | * A ticker system for handling DOM reads and writes in an efficient way. 12 | * 13 | * @class 14 | */ 15 | function Ticker(numLanes) { 16 | this._nextStep = null; 17 | this._lanes = []; 18 | this._stepQueue = []; 19 | this._stepCallbacks = {}; 20 | this._step = this._step.bind(this); 21 | for (var i = 0; i < numLanes; i++) { 22 | this._lanes.push(new TickerLane()); 23 | } 24 | } 25 | 26 | Ticker.prototype._step = function (time) { 27 | var lanes = this._lanes; 28 | var stepQueue = this._stepQueue; 29 | var stepCallbacks = this._stepCallbacks; 30 | var i, j, id, laneQueue, laneCallbacks, laneIndices; 31 | 32 | this._nextStep = null; 33 | 34 | for (i = 0; i < lanes.length; i++) { 35 | laneQueue = lanes[i].queue; 36 | laneCallbacks = lanes[i].callbacks; 37 | laneIndices = lanes[i].indices; 38 | for (j = 0; j < laneQueue.length; j++) { 39 | id = laneQueue[j]; 40 | if (!id) continue; 41 | stepQueue.push(id); 42 | stepCallbacks[id] = laneCallbacks[id]; 43 | delete laneCallbacks[id]; 44 | delete laneIndices[id]; 45 | } 46 | laneQueue.length = 0; 47 | } 48 | 49 | for (i = 0; i < stepQueue.length; i++) { 50 | id = stepQueue[i]; 51 | if (stepCallbacks[id]) stepCallbacks[id](time); 52 | delete stepCallbacks[id]; 53 | } 54 | 55 | stepQueue.length = 0; 56 | }; 57 | 58 | Ticker.prototype.add = function (laneIndex, id, callback) { 59 | this._lanes[laneIndex].add(id, callback); 60 | if (!this._nextStep) this._nextStep = raf(this._step); 61 | }; 62 | 63 | Ticker.prototype.remove = function (laneIndex, id) { 64 | this._lanes[laneIndex].remove(id); 65 | }; 66 | 67 | /** 68 | * A lane for ticker. 69 | * 70 | * @class 71 | */ 72 | function TickerLane() { 73 | this.queue = []; 74 | this.indices = {}; 75 | this.callbacks = {}; 76 | } 77 | 78 | TickerLane.prototype.add = function (id, callback) { 79 | var index = this.indices[id]; 80 | if (index !== undefined) this.queue[index] = undefined; 81 | this.queue.push(id); 82 | this.callbacks[id] = callback; 83 | this.indices[id] = this.queue.length - 1; 84 | }; 85 | 86 | TickerLane.prototype.remove = function (id) { 87 | var index = this.indices[id]; 88 | if (index === undefined) return; 89 | this.queue[index] = undefined; 90 | delete this.callbacks[id]; 91 | delete this.indices[id]; 92 | }; 93 | 94 | export default Ticker; 95 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | export var GRID_INSTANCES = {}; 8 | export var ITEM_ELEMENT_MAP = typeof Map === 'function' ? new Map() : null; 9 | 10 | export var ACTION_SWAP = 'swap'; 11 | export var ACTION_MOVE = 'move'; 12 | 13 | export var EVENT_SYNCHRONIZE = 'synchronize'; 14 | export var EVENT_LAYOUT_START = 'layoutStart'; 15 | export var EVENT_LAYOUT_END = 'layoutEnd'; 16 | export var EVENT_LAYOUT_ABORT = 'layoutAbort'; 17 | export var EVENT_ADD = 'add'; 18 | export var EVENT_REMOVE = 'remove'; 19 | export var EVENT_SHOW_START = 'showStart'; 20 | export var EVENT_SHOW_END = 'showEnd'; 21 | export var EVENT_HIDE_START = 'hideStart'; 22 | export var EVENT_HIDE_END = 'hideEnd'; 23 | export var EVENT_FILTER = 'filter'; 24 | export var EVENT_SORT = 'sort'; 25 | export var EVENT_MOVE = 'move'; 26 | export var EVENT_SEND = 'send'; 27 | export var EVENT_BEFORE_SEND = 'beforeSend'; 28 | export var EVENT_RECEIVE = 'receive'; 29 | export var EVENT_BEFORE_RECEIVE = 'beforeReceive'; 30 | export var EVENT_DRAG_INIT = 'dragInit'; 31 | export var EVENT_DRAG_START = 'dragStart'; 32 | export var EVENT_DRAG_MOVE = 'dragMove'; 33 | export var EVENT_DRAG_SCROLL = 'dragScroll'; 34 | export var EVENT_DRAG_END = 'dragEnd'; 35 | export var EVENT_DRAG_RELEASE_START = 'dragReleaseStart'; 36 | export var EVENT_DRAG_RELEASE_END = 'dragReleaseEnd'; 37 | export var EVENT_DESTROY = 'destroy'; 38 | 39 | export var HAS_TOUCH_EVENTS = 'ontouchstart' in window; 40 | export var HAS_POINTER_EVENTS = !!window.PointerEvent; 41 | export var HAS_MS_POINTER_EVENTS = !!window.navigator.msPointerEnabled; 42 | 43 | export var MAX_SAFE_FLOAT32_INTEGER = 16777216; 44 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | export { default } from './Grid/Grid'; 8 | -------------------------------------------------------------------------------- /src/ticker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | import Ticker from './Ticker/Ticker'; 8 | 9 | var LAYOUT_READ = 'layoutRead'; 10 | var LAYOUT_WRITE = 'layoutWrite'; 11 | var VISIBILITY_READ = 'visibilityRead'; 12 | var VISIBILITY_WRITE = 'visibilityWrite'; 13 | var DRAG_START_READ = 'dragStartRead'; 14 | var DRAG_START_WRITE = 'dragStartWrite'; 15 | var DRAG_MOVE_READ = 'dragMoveRead'; 16 | var DRAG_MOVE_WRITE = 'dragMoveWrite'; 17 | var DRAG_SCROLL_READ = 'dragScrollRead'; 18 | var DRAG_SCROLL_WRITE = 'dragScrollWrite'; 19 | var DRAG_SORT_READ = 'dragSortRead'; 20 | var PLACEHOLDER_LAYOUT_READ = 'placeholderLayoutRead'; 21 | var PLACEHOLDER_LAYOUT_WRITE = 'placeholderLayoutWrite'; 22 | var PLACEHOLDER_RESIZE_WRITE = 'placeholderResizeWrite'; 23 | var AUTO_SCROLL_READ = 'autoScrollRead'; 24 | var AUTO_SCROLL_WRITE = 'autoScrollWrite'; 25 | var DEBOUNCE_READ = 'debounceRead'; 26 | 27 | var LANE_READ = 0; 28 | var LANE_READ_TAIL = 1; 29 | var LANE_WRITE = 2; 30 | 31 | var ticker = new Ticker(3); 32 | export default ticker; 33 | 34 | export function addLayoutTick(itemId, read, write) { 35 | ticker.add(LANE_READ, LAYOUT_READ + itemId, read); 36 | ticker.add(LANE_WRITE, LAYOUT_WRITE + itemId, write); 37 | } 38 | 39 | export function cancelLayoutTick(itemId) { 40 | ticker.remove(LANE_READ, LAYOUT_READ + itemId); 41 | ticker.remove(LANE_WRITE, LAYOUT_WRITE + itemId); 42 | } 43 | 44 | export function addVisibilityTick(itemId, read, write) { 45 | ticker.add(LANE_READ, VISIBILITY_READ + itemId, read); 46 | ticker.add(LANE_WRITE, VISIBILITY_WRITE + itemId, write); 47 | } 48 | 49 | export function cancelVisibilityTick(itemId) { 50 | ticker.remove(LANE_READ, VISIBILITY_READ + itemId); 51 | ticker.remove(LANE_WRITE, VISIBILITY_WRITE + itemId); 52 | } 53 | 54 | export function addDragStartTick(itemId, read, write) { 55 | ticker.add(LANE_READ, DRAG_START_READ + itemId, read); 56 | ticker.add(LANE_WRITE, DRAG_START_WRITE + itemId, write); 57 | } 58 | 59 | export function cancelDragStartTick(itemId) { 60 | ticker.remove(LANE_READ, DRAG_START_READ + itemId); 61 | ticker.remove(LANE_WRITE, DRAG_START_WRITE + itemId); 62 | } 63 | 64 | export function addDragMoveTick(itemId, read, write) { 65 | ticker.add(LANE_READ, DRAG_MOVE_READ + itemId, read); 66 | ticker.add(LANE_WRITE, DRAG_MOVE_WRITE + itemId, write); 67 | } 68 | 69 | export function cancelDragMoveTick(itemId) { 70 | ticker.remove(LANE_READ, DRAG_MOVE_READ + itemId); 71 | ticker.remove(LANE_WRITE, DRAG_MOVE_WRITE + itemId); 72 | } 73 | 74 | export function addDragScrollTick(itemId, read, write) { 75 | ticker.add(LANE_READ, DRAG_SCROLL_READ + itemId, read); 76 | ticker.add(LANE_WRITE, DRAG_SCROLL_WRITE + itemId, write); 77 | } 78 | 79 | export function cancelDragScrollTick(itemId) { 80 | ticker.remove(LANE_READ, DRAG_SCROLL_READ + itemId); 81 | ticker.remove(LANE_WRITE, DRAG_SCROLL_WRITE + itemId); 82 | } 83 | 84 | export function addDragSortTick(itemId, read) { 85 | ticker.add(LANE_READ_TAIL, DRAG_SORT_READ + itemId, read); 86 | } 87 | 88 | export function cancelDragSortTick(itemId) { 89 | ticker.remove(LANE_READ_TAIL, DRAG_SORT_READ + itemId); 90 | } 91 | 92 | export function addPlaceholderLayoutTick(itemId, read, write) { 93 | ticker.add(LANE_READ, PLACEHOLDER_LAYOUT_READ + itemId, read); 94 | ticker.add(LANE_WRITE, PLACEHOLDER_LAYOUT_WRITE + itemId, write); 95 | } 96 | 97 | export function cancelPlaceholderLayoutTick(itemId) { 98 | ticker.remove(LANE_READ, PLACEHOLDER_LAYOUT_READ + itemId); 99 | ticker.remove(LANE_WRITE, PLACEHOLDER_LAYOUT_WRITE + itemId); 100 | } 101 | 102 | export function addPlaceholderResizeTick(itemId, write) { 103 | ticker.add(LANE_WRITE, PLACEHOLDER_RESIZE_WRITE + itemId, write); 104 | } 105 | 106 | export function cancelPlaceholderResizeTick(itemId) { 107 | ticker.remove(LANE_WRITE, PLACEHOLDER_RESIZE_WRITE + itemId); 108 | } 109 | 110 | export function addAutoScrollTick(read, write) { 111 | ticker.add(LANE_READ, AUTO_SCROLL_READ, read); 112 | ticker.add(LANE_WRITE, AUTO_SCROLL_WRITE, write); 113 | } 114 | 115 | export function cancelAutoScrollTick() { 116 | ticker.remove(LANE_READ, AUTO_SCROLL_READ); 117 | ticker.remove(LANE_WRITE, AUTO_SCROLL_WRITE); 118 | } 119 | 120 | export function addDebounceTick(debounceId, read) { 121 | ticker.add(LANE_READ, DEBOUNCE_READ + debounceId, read); 122 | } 123 | 124 | export function cancelDebounceTick(debounceId) { 125 | ticker.remove(LANE_READ, DEBOUNCE_READ + debounceId); 126 | } 127 | -------------------------------------------------------------------------------- /src/utils/addClass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | import elementMatches from './elementMatches'; 8 | 9 | /** 10 | * Add class to an element. 11 | * 12 | * @param {HTMLElement} element 13 | * @param {String} className 14 | */ 15 | export default function addClass(element, className) { 16 | if (!className) return; 17 | 18 | if (element.classList) { 19 | element.classList.add(className); 20 | } else { 21 | if (!elementMatches(element, '.' + className)) { 22 | element.className += ' ' + className; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/arrayInsert.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | var tempArray = []; 8 | var numberType = 'number'; 9 | 10 | /** 11 | * Insert an item or an array of items to array to a specified index. Mutates 12 | * the array. The index can be negative in which case the items will be added 13 | * to the end of the array. 14 | * 15 | * @param {Array} array 16 | * @param {*} items 17 | * @param {Number} [index=-1] 18 | */ 19 | export default function arrayInsert(array, items, index) { 20 | var startIndex = typeof index === numberType ? index : -1; 21 | if (startIndex < 0) startIndex = array.length - startIndex + 1; 22 | 23 | array.splice.apply(array, tempArray.concat(startIndex, 0, items)); 24 | tempArray.length = 0; 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/arrayMove.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | import normalizeArrayIndex from './normalizeArrayIndex'; 8 | 9 | /** 10 | * Move array item to another index. 11 | * 12 | * @param {Array} array 13 | * @param {Number} fromIndex 14 | * - Index (positive or negative) of the item that will be moved. 15 | * @param {Number} toIndex 16 | * - Index (positive or negative) where the item should be moved to. 17 | */ 18 | export default function arrayMove(array, fromIndex, toIndex) { 19 | // Make sure the array has two or more items. 20 | if (array.length < 2) return; 21 | 22 | // Normalize the indices. 23 | var from = normalizeArrayIndex(array, fromIndex); 24 | var to = normalizeArrayIndex(array, toIndex); 25 | 26 | // Add target item to the new position. 27 | if (from !== to) { 28 | array.splice(to, 0, array.splice(from, 1)[0]); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/arraySwap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | import normalizeArrayIndex from './normalizeArrayIndex'; 8 | 9 | /** 10 | * Swap array items. 11 | * 12 | * @param {Array} array 13 | * @param {Number} index 14 | * - Index (positive or negative) of the item that will be swapped. 15 | * @param {Number} withIndex 16 | * - Index (positive or negative) of the other item that will be swapped. 17 | */ 18 | export default function arraySwap(array, index, withIndex) { 19 | // Make sure the array has two or more items. 20 | if (array.length < 2) return; 21 | 22 | // Normalize the indices. 23 | var indexA = normalizeArrayIndex(array, index); 24 | var indexB = normalizeArrayIndex(array, withIndex); 25 | var temp; 26 | 27 | // Swap the items. 28 | if (indexA !== indexB) { 29 | temp = array[indexA]; 30 | array[indexA] = array[indexB]; 31 | array[indexB] = temp; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/utils/createUid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | var id = 0; 8 | 9 | /** 10 | * Returns a unique numeric id (increments a base value on every call). 11 | * @returns {Number} 12 | */ 13 | export default function createUid() { 14 | return ++id; 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/debounce.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | import { addDebounceTick, cancelDebounceTick } from '../ticker'; 8 | 9 | var debounceId = 0; 10 | 11 | /** 12 | * Returns a function, that, as long as it continues to be invoked, will not 13 | * be triggered. The function will be called after it stops being called for 14 | * N milliseconds. The returned function accepts one argument which, when 15 | * being `true`, cancels the debounce function immediately. When the debounce 16 | * function is canceled it cannot be invoked again. 17 | * 18 | * @param {Function} fn 19 | * @param {Number} durationMs 20 | * @returns {Function} 21 | */ 22 | export default function debounce(fn, durationMs) { 23 | var id = ++debounceId; 24 | var timer = 0; 25 | var lastTime = 0; 26 | var isCanceled = false; 27 | var tick = function (time) { 28 | if (isCanceled) return; 29 | 30 | if (lastTime) timer -= time - lastTime; 31 | lastTime = time; 32 | 33 | if (timer > 0) { 34 | addDebounceTick(id, tick); 35 | } else { 36 | timer = lastTime = 0; 37 | fn(); 38 | } 39 | }; 40 | 41 | return function (cancel) { 42 | if (isCanceled) return; 43 | 44 | if (durationMs <= 0) { 45 | if (cancel !== true) fn(); 46 | return; 47 | } 48 | 49 | if (cancel === true) { 50 | isCanceled = true; 51 | timer = lastTime = 0; 52 | tick = undefined; 53 | cancelDebounceTick(id); 54 | return; 55 | } 56 | 57 | if (timer <= 0) { 58 | timer = durationMs; 59 | tick(0); 60 | } else { 61 | timer = durationMs; 62 | } 63 | }; 64 | } 65 | -------------------------------------------------------------------------------- /src/utils/elementMatches.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | var ElProto = window.Element.prototype; 8 | var matchesFn = 9 | ElProto.matches || 10 | ElProto.matchesSelector || 11 | ElProto.webkitMatchesSelector || 12 | ElProto.mozMatchesSelector || 13 | ElProto.msMatchesSelector || 14 | ElProto.oMatchesSelector || 15 | function () { 16 | return false; 17 | }; 18 | 19 | /** 20 | * Check if element matches a CSS selector. 21 | * 22 | * @param {Element} el 23 | * @param {String} selector 24 | * @returns {Boolean} 25 | */ 26 | export default function elementMatches(el, selector) { 27 | return matchesFn.call(el, selector); 28 | } 29 | -------------------------------------------------------------------------------- /src/utils/getContainingBlock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | import getStyle from './getStyle'; 8 | import isTransformed from './isTransformed'; 9 | 10 | /** 11 | * Returns an absolute positioned element's containing block, which is 12 | * considered to be the closest ancestor element that the target element's 13 | * positioning is relative to. Disclaimer: this only works as intended for 14 | * absolute positioned elements. 15 | * 16 | * @param {HTMLElement} element 17 | * @returns {(Document|Element)} 18 | */ 19 | export default function getContainingBlock(element) { 20 | // As long as the containing block is an element, static and not 21 | // transformed, try to get the element's parent element and fallback to 22 | // document. https://github.com/niklasramo/mezr/blob/0.6.1/mezr.js#L339 23 | var doc = document; 24 | var res = element || doc; 25 | while (res && res !== doc && getStyle(res, 'position') === 'static' && !isTransformed(res)) { 26 | res = res.parentElement || doc; 27 | } 28 | return res; 29 | } 30 | -------------------------------------------------------------------------------- /src/utils/getCurrentStyles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | import getStyle from './getStyle'; 8 | import getStyleName from './getStyleName'; 9 | 10 | /** 11 | * Get current values of the provided styles definition object or array. 12 | * 13 | * @param {HTMLElement} element 14 | * @param {(Object|Array} styles 15 | * @return {Object} 16 | */ 17 | export default function getCurrentStyles(element, styles) { 18 | var result = {}; 19 | var prop, i; 20 | 21 | if (Array.isArray(styles)) { 22 | for (i = 0; i < styles.length; i++) { 23 | prop = styles[i]; 24 | result[prop] = getStyle(element, getStyleName(prop)); 25 | } 26 | } else { 27 | for (prop in styles) { 28 | result[prop] = getStyle(element, getStyleName(prop)); 29 | } 30 | } 31 | 32 | return result; 33 | } 34 | -------------------------------------------------------------------------------- /src/utils/getIntersectionArea.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | import isOverlapping from './isOverlapping'; 8 | 9 | /** 10 | * Calculate intersection area between two rectangle. 11 | * 12 | * @param {Object} a 13 | * @param {Object} b 14 | * @returns {Number} 15 | */ 16 | export default function getIntersectionArea(a, b) { 17 | if (!isOverlapping(a, b)) return 0; 18 | var width = Math.min(a.left + a.width, b.left + b.width) - Math.max(a.left, b.left); 19 | var height = Math.min(a.top + a.height, b.top + b.height) - Math.max(a.top, b.top); 20 | return width * height; 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/getIntersectionScore.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | import getIntersectionArea from './getIntersectionArea'; 8 | 9 | /** 10 | * Calculate how many percent the intersection area of two rectangles is from 11 | * the maximum potential intersection area between the rectangles. 12 | * 13 | * @param {Object} a 14 | * @param {Object} b 15 | * @returns {Number} 16 | */ 17 | export default function getIntersectionScore(a, b) { 18 | var area = getIntersectionArea(a, b); 19 | if (!area) return 0; 20 | var maxArea = Math.min(a.width, b.width) * Math.min(a.height, b.height); 21 | return (area / maxArea) * 100; 22 | } 23 | -------------------------------------------------------------------------------- /src/utils/getOffsetDiff.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | import getContainingBlock from './getContainingBlock'; 8 | import getStyleAsFloat from './getStyleAsFloat'; 9 | 10 | var offsetA = {}; 11 | var offsetB = {}; 12 | var offsetDiff = {}; 13 | 14 | /** 15 | * Returns the element's document offset, which in practice means the vertical 16 | * and horizontal distance between the element's northwest corner and the 17 | * document's northwest corner. Note that this function always returns the same 18 | * object so be sure to read the data from it instead using it as a reference. 19 | * 20 | * @param {(Document|Element|Window)} element 21 | * @param {Object} [offsetData] 22 | * - Optional data object where the offset data will be inserted to. If not 23 | * provided a new object will be created for the return data. 24 | * @returns {Object} 25 | */ 26 | function getOffset(element, offsetData) { 27 | var offset = offsetData || {}; 28 | var rect; 29 | 30 | // Set up return data. 31 | offset.left = 0; 32 | offset.top = 0; 33 | 34 | // Document's offsets are always 0. 35 | if (element === document) return offset; 36 | 37 | // Add viewport scroll left/top to the respective offsets. 38 | offset.left = window.pageXOffset || 0; 39 | offset.top = window.pageYOffset || 0; 40 | 41 | // Window's offsets are the viewport scroll left/top values. 42 | if (element.self === window.self) return offset; 43 | 44 | // Add element's client rects to the offsets. 45 | rect = element.getBoundingClientRect(); 46 | offset.left += rect.left; 47 | offset.top += rect.top; 48 | 49 | // Exclude element's borders from the offset. 50 | offset.left += getStyleAsFloat(element, 'border-left-width'); 51 | offset.top += getStyleAsFloat(element, 'border-top-width'); 52 | 53 | return offset; 54 | } 55 | 56 | /** 57 | * Calculate the offset difference two elements. 58 | * 59 | * @param {HTMLElement} elemA 60 | * @param {HTMLElement} elemB 61 | * @param {Boolean} [compareContainingBlocks=false] 62 | * - When this is set to true the containing blocks of the provided elements 63 | * will be used for calculating the difference. Otherwise the provided 64 | * elements will be compared directly. 65 | * @returns {Object} 66 | */ 67 | export default function getOffsetDiff(elemA, elemB, compareContainingBlocks) { 68 | offsetDiff.left = 0; 69 | offsetDiff.top = 0; 70 | 71 | // If elements are same let's return early. 72 | if (elemA === elemB) return offsetDiff; 73 | 74 | // Compare containing blocks if necessary. 75 | if (compareContainingBlocks) { 76 | elemA = getContainingBlock(elemA); 77 | elemB = getContainingBlock(elemB); 78 | 79 | // If containing blocks are identical, let's return early. 80 | if (elemA === elemB) return offsetDiff; 81 | } 82 | 83 | // Finally, let's calculate the offset diff. 84 | getOffset(elemA, offsetA); 85 | getOffset(elemB, offsetB); 86 | offsetDiff.left = offsetB.left - offsetA.left; 87 | offsetDiff.top = offsetB.top - offsetA.top; 88 | 89 | return offsetDiff; 90 | } 91 | -------------------------------------------------------------------------------- /src/utils/getPrefixedPropName.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Forked from hammer.js: 3 | * https://github.com/hammerjs/hammer.js/blob/563b5b1e4bfbb5796798dd286cd57b7c56f1eb9e/src/utils/prefixed.js 4 | */ 5 | 6 | // Playing it safe here, test all potential prefixes capitalized and lowercase. 7 | var vendorPrefixes = ['', 'webkit', 'moz', 'ms', 'o', 'Webkit', 'Moz', 'MS', 'O']; 8 | var cache = {}; 9 | 10 | /** 11 | * Get prefixed CSS property name when given a non-prefixed CSS property name. 12 | * Returns null if the property is not supported at all. 13 | * 14 | * @param {CSSStyleDeclaration} style 15 | * @param {String} prop 16 | * @returns {String} 17 | */ 18 | export default function getPrefixedPropName(style, prop) { 19 | var prefixedProp = cache[prop] || ''; 20 | if (prefixedProp) return prefixedProp; 21 | 22 | var camelProp = prop[0].toUpperCase() + prop.slice(1); 23 | var i = 0; 24 | while (i < vendorPrefixes.length) { 25 | prefixedProp = vendorPrefixes[i] ? vendorPrefixes[i] + camelProp : prop; 26 | if (prefixedProp in style) { 27 | cache[prop] = prefixedProp; 28 | return prefixedProp; 29 | } 30 | ++i; 31 | } 32 | 33 | return ''; 34 | } 35 | -------------------------------------------------------------------------------- /src/utils/getScrollableAncestors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | import isScrollable from './isScrollable'; 8 | 9 | /** 10 | * Collect element's ancestors that are potentially scrollable elements. The 11 | * provided element is also also included in the check, meaning that if it is 12 | * scrollable it is added to the result array. 13 | * 14 | * @param {HTMLElement} element 15 | * @param {Array} [result] 16 | * @returns {Array} 17 | */ 18 | export default function getScrollableAncestors(element, result) { 19 | result = result || []; 20 | 21 | // Find scroll parents. 22 | while (element && element !== document) { 23 | // If element is inside ShadowDOM let's get it's host node from the real 24 | // DOM and continue looping. 25 | if (element.getRootNode && element instanceof DocumentFragment) { 26 | element = element.getRootNode().host; 27 | continue; 28 | } 29 | 30 | // If element is scrollable let's add it to the scrollable list. 31 | if (isScrollable(element)) { 32 | result.push(element); 33 | } 34 | 35 | element = element.parentNode; 36 | } 37 | 38 | // Always add window to the results. 39 | result.push(window); 40 | 41 | return result; 42 | } 43 | -------------------------------------------------------------------------------- /src/utils/getStyle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | var cache = typeof WeakMap === 'function' ? new WeakMap() : null; 8 | 9 | /** 10 | * Returns the computed value of an element's style property as a string. 11 | * 12 | * @param {HTMLElement} element 13 | * @param {String} style 14 | * @returns {String} 15 | */ 16 | export default function getStyle(element, style) { 17 | var styles = cache && cache.get(element); 18 | 19 | if (!styles) { 20 | styles = window.getComputedStyle(element, null); 21 | if (cache) cache.set(element, styles); 22 | } 23 | 24 | return styles.getPropertyValue(style); 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/getStyleAsFloat.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | import getStyle from './getStyle'; 8 | 9 | /** 10 | * Returns the computed value of an element's style property transformed into 11 | * a float value. 12 | * 13 | * @param {HTMLElement} el 14 | * @param {String} style 15 | * @returns {Number} 16 | */ 17 | export default function getStyleAsFloat(el, style) { 18 | return parseFloat(getStyle(el, style)) || 0; 19 | } 20 | -------------------------------------------------------------------------------- /src/utils/getStyleName.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | var styleNameRegEx = /([A-Z])/g; 8 | var prefixRegex = /^(webkit-|moz-|ms-|o-)/; 9 | var msPrefixRegex = /^(-m-s-)/; 10 | 11 | /** 12 | * Transforms a camel case style property to kebab case style property. Handles 13 | * vendor prefixed properties elegantly as well, e.g. "WebkitTransform" and 14 | * "webkitTransform" are both transformed into "-webkit-transform". 15 | * 16 | * @param {String} property 17 | * @returns {String} 18 | */ 19 | export default function getStyleName(property) { 20 | // Initial slicing, turns "fooBarProp" into "foo-bar-prop". 21 | var styleName = property.replace(styleNameRegEx, '-$1').toLowerCase(); 22 | 23 | // Handle properties that start with "webkit", "moz", "ms" or "o" prefix (we 24 | // need to add an extra '-' to the beginnig). 25 | styleName = styleName.replace(prefixRegex, '-$1'); 26 | 27 | // Handle properties that start with "MS" prefix (we need to transform the 28 | // "-m-s-" into "-ms-"). 29 | styleName = styleName.replace(msPrefixRegex, '-ms-'); 30 | 31 | return styleName; 32 | } 33 | -------------------------------------------------------------------------------- /src/utils/getTranslate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | import getStyle from './getStyle'; 8 | import transformStyle from './transformStyle'; 9 | 10 | var translateValue = {}; 11 | var transformNone = 'none'; 12 | var rxMat3d = /^matrix3d/; 13 | var rxMatTx = /([^,]*,){4}/; 14 | var rxMat3dTx = /([^,]*,){12}/; 15 | var rxNextItem = /[^,]*,/; 16 | 17 | /** 18 | * Returns the element's computed translateX and translateY values as a floats. 19 | * The returned object is always the same object and updated every time this 20 | * function is called. 21 | * 22 | * @param {HTMLElement} element 23 | * @returns {Object} 24 | */ 25 | export default function getTranslate(element) { 26 | translateValue.x = 0; 27 | translateValue.y = 0; 28 | 29 | var transform = getStyle(element, transformStyle); 30 | if (!transform || transform === transformNone) { 31 | return translateValue; 32 | } 33 | 34 | // Transform style can be in either matrix3d(...) or matrix(...). 35 | var isMat3d = rxMat3d.test(transform); 36 | var tX = transform.replace(isMat3d ? rxMat3dTx : rxMatTx, ''); 37 | var tY = tX.replace(rxNextItem, ''); 38 | 39 | translateValue.x = parseFloat(tX) || 0; 40 | translateValue.y = parseFloat(tY) || 0; 41 | 42 | return translateValue; 43 | } 44 | -------------------------------------------------------------------------------- /src/utils/getTranslateString.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | /** 8 | * Transform translateX and translateY value into CSS transform style 9 | * property's value. 10 | * 11 | * @param {Number} x 12 | * @param {Number} y 13 | * @returns {String} 14 | */ 15 | export default function getTranslateString(x, y) { 16 | return 'translateX(' + x + 'px) translateY(' + y + 'px)'; 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/getUnprefixedPropName.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | var unprefixRegEx = /^(webkit|moz|ms|o|Webkit|Moz|MS|O)(?=[A-Z])/; 8 | var cache = {}; 9 | 10 | /** 11 | * Remove any potential vendor prefixes from a property name. 12 | * 13 | * @param {String} prop 14 | * @returns {String} 15 | */ 16 | export default function getUnprefixedPropName(prop) { 17 | var result = cache[prop]; 18 | if (result) return result; 19 | 20 | result = prop.replace(unprefixRegEx, ''); 21 | 22 | if (result !== prop) { 23 | result = result[0].toLowerCase() + result.slice(1); 24 | } 25 | 26 | cache[prop] = result; 27 | 28 | return result; 29 | } 30 | -------------------------------------------------------------------------------- /src/utils/hasPassiveEvents.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | /** 8 | * Check if passive events are supported. 9 | * https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md#feature-detection 10 | * 11 | * @returns {Boolean} 12 | */ 13 | export default function hasPassiveEvents() { 14 | var isPassiveEventsSupported = false; 15 | 16 | try { 17 | var passiveOpts = Object.defineProperty({}, 'passive', { 18 | get: function () { 19 | isPassiveEventsSupported = true; 20 | }, 21 | }); 22 | window.addEventListener('testPassive', null, passiveOpts); 23 | window.removeEventListener('testPassive', null, passiveOpts); 24 | } catch (e) {} 25 | 26 | return isPassiveEventsSupported; 27 | } 28 | -------------------------------------------------------------------------------- /src/utils/isFunction.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | var functionType = 'function'; 8 | 9 | /** 10 | * Check if a value is a function. 11 | * 12 | * @param {*} val 13 | * @returns {Boolean} 14 | */ 15 | export default function isFunction(val) { 16 | return typeof val === functionType; 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/isNative.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | import isFunction from './isFunction'; 8 | 9 | var nativeCode = '[native code]'; 10 | 11 | /** 12 | * Check if a value (e.g. a method or constructor) is native code. Good for 13 | * detecting when a polyfill is used and when not. 14 | * 15 | * @param {*} feat 16 | * @returns {Boolean} 17 | */ 18 | export default function isNative(feat) { 19 | var S = window.Symbol; 20 | return !!( 21 | feat && 22 | isFunction(S) && 23 | isFunction(S.toString) && 24 | S(feat).toString().indexOf(nativeCode) > -1 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/utils/isNodeList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | var htmlCollectionType = '[object HTMLCollection]'; 8 | var nodeListType = '[object NodeList]'; 9 | 10 | /** 11 | * Check if a value is a node list or a html collection. 12 | * 13 | * @param {*} val 14 | * @returns {Boolean} 15 | */ 16 | export default function isNodeList(val) { 17 | var type = Object.prototype.toString.call(val); 18 | return type === htmlCollectionType || type === nodeListType; 19 | } 20 | -------------------------------------------------------------------------------- /src/utils/isOverlapping.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | /** 8 | * Check if two rectangles are overlapping. 9 | * 10 | * @param {Object} a 11 | * @param {Object} b 12 | * @returns {Number} 13 | */ 14 | export default function isOverlapping(a, b) { 15 | return !( 16 | a.left + a.width <= b.left || 17 | b.left + b.width <= a.left || 18 | a.top + a.height <= b.top || 19 | b.top + b.height <= a.top 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/isPlainObject.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | var objectType = 'object'; 8 | var objectToStringType = '[object Object]'; 9 | var toString = Object.prototype.toString; 10 | 11 | /** 12 | * Check if a value is a plain object. 13 | * 14 | * @param {*} val 15 | * @returns {Boolean} 16 | */ 17 | export default function isPlainObject(val) { 18 | return typeof val === objectType && toString.call(val) === objectToStringType; 19 | } 20 | -------------------------------------------------------------------------------- /src/utils/isScrollable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | import getStyle from './getStyle'; 8 | 9 | /** 10 | * Check if overflow style value is scrollable. 11 | * 12 | * @param {String} value 13 | * @returns {Boolean} 14 | */ 15 | function isScrollableOverflow(value) { 16 | return value === 'auto' || value === 'scroll' || value === 'overlay'; 17 | } 18 | 19 | /** 20 | * Check if an element is scrollable. 21 | * 22 | * @param {HTMLElement} element 23 | * @returns {Boolean} 24 | */ 25 | export default function isScrollable(element) { 26 | return ( 27 | isScrollableOverflow(getStyle(element, 'overflow')) || 28 | isScrollableOverflow(getStyle(element, 'overflow-x')) || 29 | isScrollableOverflow(getStyle(element, 'overflow-y')) 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/utils/isTransformed.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | import getStyle from './getStyle'; 8 | import transformStyle from './transformStyle'; 9 | 10 | var transformNone = 'none'; 11 | var displayInline = 'inline'; 12 | var displayNone = 'none'; 13 | var displayStyle = 'display'; 14 | 15 | /** 16 | * Returns true if element is transformed, false if not. In practice the 17 | * element's display value must be anything else than "none" or "inline" as 18 | * well as have a valid transform value applied in order to be counted as a 19 | * transformed element. 20 | * 21 | * Borrowed from Mezr (v0.6.1): 22 | * https://github.com/niklasramo/mezr/blob/0.6.1/mezr.js#L661 23 | * 24 | * @param {HTMLElement} element 25 | * @returns {Boolean} 26 | */ 27 | export default function isTransformed(element) { 28 | var transform = getStyle(element, transformStyle); 29 | if (!transform || transform === transformNone) return false; 30 | 31 | var display = getStyle(element, displayStyle); 32 | if (display === displayInline || display === displayNone) return false; 33 | 34 | return true; 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/noop.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | export default function noop() {} 8 | -------------------------------------------------------------------------------- /src/utils/normalizeArrayIndex.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | /** 8 | * Normalize array index. Basically this function makes sure that the provided 9 | * array index is within the bounds of the provided array and also transforms 10 | * negative index to the matching positive index. The third (optional) argument 11 | * allows you to define offset for array's length in case you are adding items 12 | * to the array or removing items from the array. 13 | * 14 | * @param {Array} array 15 | * @param {Number} index 16 | * @param {Number} [sizeOffset] 17 | */ 18 | export default function normalizeArrayIndex(array, index, sizeOffset) { 19 | var maxIndex = Math.max(0, array.length - 1 + (sizeOffset || 0)); 20 | return index > maxIndex ? maxIndex : index < 0 ? Math.max(maxIndex + index + 1, 0) : index; 21 | } 22 | -------------------------------------------------------------------------------- /src/utils/raf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | var dt = 1000 / 60; 8 | 9 | var raf = ( 10 | window.requestAnimationFrame || 11 | window.webkitRequestAnimationFrame || 12 | window.mozRequestAnimationFrame || 13 | window.msRequestAnimationFrame || 14 | function (callback) { 15 | return this.setTimeout(function () { 16 | callback(Date.now()); 17 | }, dt); 18 | } 19 | ).bind(window); 20 | 21 | /** 22 | * @param {Function} callback 23 | * @returns {Number} 24 | */ 25 | export default raf; 26 | -------------------------------------------------------------------------------- /src/utils/removeClass.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | import elementMatches from './elementMatches'; 8 | 9 | /** 10 | * Remove class from an element. 11 | * 12 | * @param {HTMLElement} element 13 | * @param {String} className 14 | */ 15 | export default function removeClass(element, className) { 16 | if (!className) return; 17 | 18 | if (element.classList) { 19 | element.classList.remove(className); 20 | } else { 21 | if (elementMatches(element, '.' + className)) { 22 | element.className = (' ' + element.className + ' ') 23 | .replace(' ' + className + ' ', ' ') 24 | .trim(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/utils/setStyles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | /** 8 | * Set inline styles to an element. 9 | * 10 | * @param {HTMLElement} element 11 | * @param {Object} styles 12 | */ 13 | export default function setStyles(element, styles) { 14 | for (var prop in styles) { 15 | element.style[prop] = styles[prop]; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/toArray.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | import isNodeList from './isNodeList'; 8 | 9 | /** 10 | * Converts a value to an array or clones an array. 11 | * 12 | * @param {*} val 13 | * @returns {Array} 14 | */ 15 | export default function toArray(val) { 16 | return isNodeList(val) ? Array.prototype.slice.call(val) : Array.prototype.concat(val); 17 | } 18 | -------------------------------------------------------------------------------- /src/utils/transformProp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | import getPrefixedPropName from './getPrefixedPropName'; 8 | 9 | var transformProp = getPrefixedPropName(document.documentElement.style, 'transform') || 'transform'; 10 | 11 | export default transformProp; 12 | -------------------------------------------------------------------------------- /src/utils/transformStyle.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Haltu Oy 3 | * Released under the MIT license 4 | * https://github.com/haltu/muuri/blob/master/LICENSE.md 5 | */ 6 | 7 | import transformProp from './transformProp'; 8 | import getStyleName from './getStyleName'; 9 | 10 | var transformStyle = getStyleName(transformProp); 11 | 12 | export default transformStyle; 13 | -------------------------------------------------------------------------------- /tests/grid-constructor/container.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid instance'); 5 | 6 | QUnit.test('Muuri constructor should not accept an invalid container element', function (assert) { 7 | assert.expect(5); 8 | 9 | assert.throws(function () { 10 | new Muuri(); 11 | }, 'Should throw an error when no arguments are provided'); 12 | 13 | assert.throws(function () { 14 | new Muuri(document); 15 | }, 'Should throw an error when document is set as container'); 16 | 17 | assert.throws(function () { 18 | new Muuri(document.documentElement); 19 | }, 'Should throw an error when documentElement is set as container'); 20 | 21 | assert.throws(function () { 22 | new Muuri(document.createElement('div')); 23 | }, 'Should throw an error when an element which is not in the DOM is set as the container'); 24 | 25 | assert.throws(function () { 26 | new Muuri('.does-not-exist'); 27 | }, 'Should throw an error when a valid element matching selector query string is not found'); 28 | }); 29 | 30 | QUnit.test('Muuri constructor should accept document body as container', function (assert) { 31 | var muuri = new Muuri(document.body, { items: [] }); 32 | assert.strictEqual( 33 | muuri instanceof Muuri, 34 | true, 35 | 'Should initiate succesfully when body element is set as the container' 36 | ); 37 | muuri.destroy(); 38 | }); 39 | 40 | QUnit.test( 41 | 'Muuri constructor should accept any descendant of document body as container', 42 | function (assert) { 43 | var element = document.createElement('div'); 44 | document.body.appendChild(element); 45 | 46 | var childElement = document.createElement('div'); 47 | element.appendChild(childElement); 48 | 49 | var muuri = new Muuri(childElement); 50 | assert.strictEqual( 51 | muuri instanceof Muuri, 52 | true, 53 | 'Should initiate succesfully when an element which is not a direct child but a descendant of document body is set as the container' 54 | ); 55 | muuri.destroy(); 56 | 57 | element.parentNode.removeChild(element); 58 | } 59 | ); 60 | 61 | QUnit.test( 62 | 'Muuri constructor should accept a selector string as container and query it', 63 | function (assert) { 64 | var element = document.createElement('div'); 65 | document.body.appendChild(element); 66 | 67 | var childElement = document.createElement('div'); 68 | childElement.classList.add('muuri-grid'); 69 | element.appendChild(childElement); 70 | 71 | var muuri = new Muuri('.muuri-grid'); 72 | assert.strictEqual( 73 | muuri instanceof Muuri, 74 | true, 75 | 'Should initiate succesfully when a selector string is passed as the container parameter' 76 | ); 77 | muuri.destroy(); 78 | 79 | element.parentNode.removeChild(element); 80 | } 81 | ); 82 | })(this); 83 | -------------------------------------------------------------------------------- /tests/grid-constructor/instance.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid instance'); 5 | 6 | QUnit.test('Muuri should be a global function', function (assert) { 7 | assert.expect(1); 8 | assert.strictEqual(typeof Muuri, 'function'); 9 | }); 10 | })(this); 11 | -------------------------------------------------------------------------------- /tests/grid-events/add.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid events'); 5 | 6 | QUnit.test('add: should be triggered after grid.add()', function (assert) { 7 | assert.expect(2); 8 | 9 | var container = utils.createGridElements(); 10 | var grid = new Muuri(container); 11 | var newElems = [ 12 | document.createElement('div').appendChild(document.createElement('div')).parentNode, 13 | document.createElement('div').appendChild(document.createElement('div')).parentNode, 14 | ]; 15 | var teardown = function () { 16 | grid.destroy(); 17 | container.parentNode.removeChild(container); 18 | }; 19 | 20 | grid.on('add', function (items) { 21 | assert.strictEqual(arguments.length, 1, 'callback: should have one argument'); 22 | assert.deepEqual( 23 | utils.sortedIdList(items), 24 | utils.sortedIdList(grid.getItems(newElems)), 25 | 'callback: first argument should be an array of the added items' 26 | ); 27 | }); 28 | grid.add(newElems); 29 | teardown(); 30 | }); 31 | })(this); 32 | -------------------------------------------------------------------------------- /tests/grid-events/destroy.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid events'); 5 | 6 | QUnit.test('destroy: should be triggered after grid.destroy()', function (assert) { 7 | assert.expect(2); 8 | 9 | var container = utils.createGridElements(); 10 | var grid = new Muuri(container); 11 | var calls = 0; 12 | var teardown = function () { 13 | grid.destroy(); 14 | container.parentNode.removeChild(container); 15 | }; 16 | 17 | grid.on('destroy', function () { 18 | assert.strictEqual(arguments.length, 0, 'callback: should have no arguments'); 19 | ++calls; 20 | }); 21 | grid.destroy().destroy().destroy(); 22 | assert.strictEqual( 23 | calls, 24 | 1, 25 | 'should be called only once no matter how many times grid.destroy() is called' 26 | ); 27 | teardown(); 28 | }); 29 | })(this); 30 | -------------------------------------------------------------------------------- /tests/grid-events/dragEnd.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid events'); 5 | 6 | QUnit.test('dragEnd: should be triggered when item is dragged', function (assert) { 7 | assert.expect(9); 8 | 9 | var done = assert.async(); 10 | var container = utils.createGridElements(); 11 | var grid = new Muuri(container, { dragEnabled: true }); 12 | var item = grid.getItems()[0]; 13 | var calls = 0; 14 | var isStartCalled = false; 15 | var isMoveCalled = false; 16 | var teardown = function () { 17 | grid.destroy(); 18 | container.parentNode.removeChild(container); 19 | done(); 20 | }; 21 | 22 | grid.on('dragStart', function () { 23 | isStartCalled = true; 24 | }); 25 | 26 | grid.on('dragMove', function () { 27 | isMoveCalled = true; 28 | }); 29 | 30 | grid.on('dragEnd', function (draggedItem, ev) { 31 | assert.strictEqual(arguments.length, 2, 'callback: should receive two arguments'); 32 | assert.strictEqual(draggedItem, item, 'callback: first argument should be the dragged item'); 33 | assert.strictEqual(isStartCalled, true, 'callback: should be called after dragStart'); 34 | assert.strictEqual(isMoveCalled, true, 'callback: should be called after dragMove'); 35 | 36 | assert.strictEqual( 37 | utils.isDraggerEvent(ev), 38 | true, 39 | 'callback: second argument should be a Dragger event object' 40 | ); 41 | assert.strictEqual(ev.isFirst, false, 'event.isFirst should be false'); 42 | assert.strictEqual(ev.isFinal, true, 'event.isFinal should be true'); 43 | assert.strictEqual(ev.type, 'end', 'event.type should be "end"'); 44 | 45 | ++calls; 46 | }); 47 | 48 | grid.on('dragReleaseEnd', function () { 49 | assert.strictEqual(calls, 1, 'should be called only once during drag process'); 50 | teardown(); 51 | }); 52 | 53 | utils.dragElement({ 54 | element: item.getElement(), 55 | x: 100, 56 | y: 100, 57 | }); 58 | }); 59 | })(this); 60 | -------------------------------------------------------------------------------- /tests/grid-events/dragInit.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid events'); 5 | 6 | QUnit.test( 7 | 'dragInit: should be triggered when dragging starts (in the beginning of the drag start process)', 8 | function (assert) { 9 | assert.expect(9); 10 | 11 | var done = assert.async(); 12 | var container = utils.createGridElements(); 13 | var grid = new Muuri(container, { 14 | dragEnabled: true, 15 | dragContainer: document.body, 16 | }); 17 | var item = grid.getItems()[0]; 18 | var calls = 0; 19 | var teardown = function () { 20 | grid.destroy(); 21 | container.parentNode.removeChild(container); 22 | done(); 23 | }; 24 | 25 | grid.on('dragInit', function (draggedItem, ev) { 26 | assert.strictEqual(arguments.length, 2, 'callback: should have receive two arguments'); 27 | assert.strictEqual( 28 | draggedItem, 29 | item, 30 | 'callback: first argument should be the dragged item' 31 | ); 32 | assert.strictEqual( 33 | utils.matches(draggedItem.getElement(), '.muuri-item-dragging'), 34 | false, 35 | 'should be called before dragging classname is set' 36 | ); 37 | assert.strictEqual( 38 | draggedItem.getElement().parentNode, 39 | container, 40 | 'should be called before dragged element is appended to it`s drag container' 41 | ); 42 | 43 | assert.strictEqual( 44 | utils.isDraggerEvent(ev), 45 | true, 46 | 'callback: second argument should be a Dragger event object' 47 | ); 48 | assert.strictEqual(ev.isFirst, true, 'event.isFirst should be true'); 49 | assert.strictEqual(ev.isFinal, false, 'event.isFinal should be false'); 50 | assert.strictEqual(ev.type, 'start', 'event.type should be "start"'); 51 | 52 | ++calls; 53 | }); 54 | 55 | utils.dragElement({ 56 | element: item.getElement(), 57 | x: 100, 58 | y: 100, 59 | onFinished: function () { 60 | assert.strictEqual(calls, 1, 'should be called only once during drag process'); 61 | teardown(); 62 | }, 63 | }); 64 | } 65 | ); 66 | })(this); 67 | -------------------------------------------------------------------------------- /tests/grid-events/dragMove.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid events'); 5 | 6 | QUnit.test('dragMove: should be triggered when item is dragged', function (assert) { 7 | assert.expect(8); 8 | 9 | var done = assert.async(); 10 | var container = utils.createGridElements(); 11 | var grid = new Muuri(container, { dragEnabled: true }); 12 | var item = grid.getItems()[0]; 13 | var calls = 0; 14 | var isStartCalled = false; 15 | var teardown = function () { 16 | grid.destroy(); 17 | container.parentNode.removeChild(container); 18 | done(); 19 | }; 20 | 21 | grid.on('dragStart', function () { 22 | isStartCalled = true; 23 | }); 24 | 25 | grid.on('dragMove', function (draggedItem, ev) { 26 | if (!calls) { 27 | assert.strictEqual(arguments.length, 2, 'callback: should have receive two arguments'); 28 | assert.strictEqual( 29 | draggedItem, 30 | item, 31 | 'callback: first argument should be the dragged item' 32 | ); 33 | assert.strictEqual(isStartCalled, true, 'callback: should be called after dragStart'); 34 | 35 | assert.strictEqual( 36 | utils.isDraggerEvent(ev), 37 | true, 38 | 'callback: second argument should be a Dragger event object' 39 | ); 40 | assert.strictEqual(ev.isFirst, false, 'event.isFirst should be false'); 41 | assert.strictEqual(ev.isFinal, false, 'event.isFinal should be false'); 42 | assert.strictEqual(ev.type, 'move', 'event.type should be "move"'); 43 | } 44 | ++calls; 45 | }); 46 | 47 | utils.dragElement({ 48 | element: item.getElement(), 49 | x: 100, 50 | y: 100, 51 | onFinished: function () { 52 | assert.strictEqual(calls > 1, true, 'should be called many times during drag process'); 53 | teardown(); 54 | }, 55 | }); 56 | }); 57 | })(this); 58 | -------------------------------------------------------------------------------- /tests/grid-events/dragReleaseEnd.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid events'); 5 | 6 | QUnit.test('dragReleaseEnd: should be triggered when item has positioned after drag', function ( 7 | assert 8 | ) { 9 | assert.expect(2); 10 | 11 | var done = assert.async(); 12 | var container = utils.createGridElements(); 13 | var grid = new Muuri(container, { dragEnabled: true }); 14 | var item = grid.getItems()[0]; 15 | var teardown = function () { 16 | grid.destroy(); 17 | container.parentNode.removeChild(container); 18 | done(); 19 | }; 20 | 21 | grid.on('dragReleaseEnd', function (draggedItem) { 22 | assert.strictEqual(arguments.length, 1, 'callback: should have receive one argument'); 23 | assert.strictEqual(draggedItem, item, 'callback: first argument should be the released item'); 24 | teardown(); 25 | }); 26 | 27 | utils.dragElement({ 28 | element: item.getElement(), 29 | x: 100, 30 | y: 100, 31 | }); 32 | }); 33 | })(this); 34 | -------------------------------------------------------------------------------- /tests/grid-events/dragReleaseStart.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid events'); 5 | 6 | QUnit.test('dragReleaseStart: should be triggered when item is released after drag', function ( 7 | assert 8 | ) { 9 | assert.expect(2); 10 | 11 | var done = assert.async(); 12 | var container = utils.createGridElements(); 13 | var grid = new Muuri(container, { dragEnabled: true }); 14 | var item = grid.getItems()[0]; 15 | var teardown = function () { 16 | grid.destroy(); 17 | container.parentNode.removeChild(container); 18 | done(); 19 | }; 20 | 21 | grid.on('dragReleaseStart', function (draggedItem) { 22 | assert.strictEqual(arguments.length, 1, 'callback: should have receive one argument'); 23 | assert.strictEqual(draggedItem, item, 'callback: first argument should be the released item'); 24 | }); 25 | 26 | grid.on('dragReleaseEnd', function () { 27 | teardown(); 28 | }); 29 | 30 | utils.dragElement({ 31 | element: item.getElement(), 32 | x: 100, 33 | y: 100, 34 | }); 35 | }); 36 | })(this); 37 | -------------------------------------------------------------------------------- /tests/grid-events/dragScroll.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid events'); 5 | 6 | QUnit.test('dragScroll: should be triggered when scroll occurs during drag process', function ( 7 | assert 8 | ) { 9 | assert.expect(4); 10 | 11 | var done = assert.async(); 12 | var docElem = document.documentElement; 13 | var body = document.body; 14 | var container = utils.createGridElements(); 15 | var grid = new Muuri(container, { 16 | dragEnabled: true, 17 | dragSortInterval: 100, 18 | dragSortPredicate: { 19 | threshold: 50, 20 | action: 'move', 21 | }, 22 | }); 23 | var item = grid.getItems()[0]; 24 | var calls = 0; 25 | var teardown = function () { 26 | grid.destroy(); 27 | container.parentNode.removeChild(container); 28 | utils.setStyles(docElem, { height: '' }); 29 | body.scrollTop = 0; 30 | done(); 31 | }; 32 | 33 | utils.setStyles(docElem, { height: '1000%' }); 34 | 35 | grid.on('dragStart', function () { 36 | body.scrollTop = 100; 37 | docElem.scrollTop = 100; 38 | }); 39 | 40 | grid.on('dragScroll', function (draggedItem, ev) { 41 | assert.strictEqual(arguments.length, 2, 'callback: should have receive two arguments'); 42 | assert.strictEqual(draggedItem, item, 'callback: first argument should be the dragged item'); 43 | assert.strictEqual( 44 | utils.isScrollEvent(ev), 45 | true, 46 | 'callback: second argument should be a scroll event object' 47 | ); 48 | ++calls; 49 | }); 50 | 51 | utils.dragElement({ 52 | element: item.getElement(), 53 | x: 0, 54 | y: 100, 55 | onFinished: function () { 56 | assert.strictEqual(calls, 1, 'should be called only once'); 57 | teardown(); 58 | }, 59 | }); 60 | }); 61 | })(this); 62 | -------------------------------------------------------------------------------- /tests/grid-events/dragStart.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid events'); 5 | 6 | QUnit.test( 7 | 'dragStart: should be triggered when dragging starts (in the end of the drag start process)', 8 | function (assert) { 9 | assert.expect(9); 10 | 11 | var done = assert.async(); 12 | var container = utils.createGridElements(); 13 | var grid = new Muuri(container, { 14 | dragEnabled: true, 15 | dragContainer: document.body, 16 | }); 17 | var item = grid.getItems()[0]; 18 | var calls = 0; 19 | var teardown = function () { 20 | grid.destroy(); 21 | container.parentNode.removeChild(container); 22 | done(); 23 | }; 24 | 25 | grid.on('dragStart', function (draggedItem, ev) { 26 | assert.strictEqual(arguments.length, 2, 'callback: should have receive two arguments'); 27 | assert.strictEqual( 28 | draggedItem, 29 | item, 30 | 'callback: first argument should be the dragged item' 31 | ); 32 | assert.strictEqual( 33 | utils.matches(draggedItem.getElement(), '.muuri-item-dragging'), 34 | true, 35 | 'should be called after dragging classname is set' 36 | ); 37 | assert.strictEqual( 38 | draggedItem.getElement().parentNode, 39 | document.body, 40 | 'should be called after dragged element is appended to it`s drag container' 41 | ); 42 | 43 | assert.strictEqual( 44 | utils.isDraggerEvent(ev), 45 | true, 46 | 'callback: second argument should be a Dragger event object' 47 | ); 48 | assert.strictEqual(ev.isFirst, true, 'event.isFirst should be true'); 49 | assert.strictEqual(ev.isFinal, false, 'event.isFinal should be false'); 50 | assert.strictEqual(ev.type, 'start', 'event.type should be "start"'); 51 | 52 | ++calls; 53 | }); 54 | 55 | utils.dragElement({ 56 | element: item.getElement(), 57 | x: 100, 58 | y: 100, 59 | onFinished: function () { 60 | assert.strictEqual(calls, 1, 'should be called only once during drag process'); 61 | teardown(); 62 | }, 63 | }); 64 | } 65 | ); 66 | })(this); 67 | -------------------------------------------------------------------------------- /tests/grid-events/draggerEvent.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid events'); 5 | 6 | QUnit.test('draggerEvent interface', function (assert) { 7 | assert.expect(35); 8 | 9 | var done = assert.async(); 10 | var container = utils.createGridElements(); 11 | var grid = new Muuri(container, { dragEnabled: true }); 12 | var item = grid.getItems()[0]; 13 | var evStart, evMove1, evMove2, evEnd; 14 | var startClientX, startClientY; 15 | var teardown = function () { 16 | grid.destroy(); 17 | container.parentNode.removeChild(container); 18 | done(); 19 | }; 20 | 21 | function assertDraggerEvent(draggerEvent) { 22 | var srcEvent = draggerEvent.srcEvent; 23 | var srcInterface; 24 | if (srcEvent.changedTouches) { 25 | for (var i = 0; i < srcEvent.changedTouches.length; i++) { 26 | if (srcEvent.changedTouches[i].identifier === draggerEvent.identifier) { 27 | srcInterface = srcEvent.changedTouches[i]; 28 | break; 29 | } 30 | } 31 | } else { 32 | srcInterface = srcEvent; 33 | } 34 | 35 | if (!srcInterface) { 36 | assert.strictEqual( 37 | true, 38 | false, 39 | 'No matching touch/event interface found from source event' 40 | ); 41 | } 42 | 43 | var dX = srcInterface.clientX - startClientX; 44 | var dY = srcInterface.clientY - startClientY; 45 | var dist = Math.sqrt(dX * dX + dY * dY); 46 | 47 | assert.strictEqual(draggerEvent.screenX, srcInterface.screenX, 'dragger event screenX'); 48 | assert.strictEqual(draggerEvent.screenY, srcInterface.screenY, 'dragger event screenY'); 49 | assert.strictEqual(draggerEvent.pageX, srcInterface.pageX, 'dragger event pageX'); 50 | assert.strictEqual(draggerEvent.pageY, srcInterface.pageY, 'dragger event pageY'); 51 | assert.strictEqual(draggerEvent.clientX, srcInterface.clientX, 'dragger event clientX'); 52 | assert.strictEqual(draggerEvent.clientY, srcInterface.clientY, 'dragger event clientY'); 53 | assert.strictEqual(draggerEvent.target, srcInterface.target, 'dragger event target'); 54 | assert.strictEqual(draggerEvent.deltaX, dX, 'dragger event deltaX'); 55 | assert.strictEqual(draggerEvent.deltaY, dY, 'dragger event deltaY'); 56 | assert.strictEqual(draggerEvent.distance, dist, 'dragger event distance'); 57 | if (draggerEvent.type === 'start') { 58 | assert.strictEqual(draggerEvent.deltaTime, 0, 'dragger event deltaTime'); 59 | } else { 60 | assert.strictEqual(draggerEvent.deltaTime > 0, true, 'dragger event deltaTime'); 61 | } 62 | } 63 | 64 | grid.on('dragStart', function (item, ev) { 65 | startClientX = ev.clientX; 66 | startClientY = ev.clientY; 67 | evStart = ev; 68 | assertDraggerEvent(ev); 69 | }); 70 | 71 | grid.on('dragMove', function (item, ev) { 72 | if (!evMove1) { 73 | evMove1 = ev; 74 | assertDraggerEvent(ev); 75 | } else if (!evMove2) { 76 | evMove2 = ev; 77 | } 78 | }); 79 | 80 | grid.on('dragEnd', function (item, ev) { 81 | evEnd = ev; 82 | assertDraggerEvent(ev); 83 | }); 84 | 85 | grid.on('dragReleaseEnd', function () { 86 | var hasUniqueEvents = 87 | evStart !== evMove1 && 88 | evStart !== evMove2 && 89 | evStart !== evEnd && 90 | evMove1 !== evMove2 && 91 | evMove1 !== evEnd && 92 | evMove2 !== evEnd; 93 | 94 | assert.strictEqual(hasUniqueEvents, true, 'event objects should not be pooled'); 95 | 96 | var hasSameId = 97 | evStart.identifier === evMove1.identifier && 98 | evStart.identifier === evMove2.identifier && 99 | evStart.identifier === evEnd.identifier; 100 | 101 | assert.strictEqual(hasSameId, true, 'identifier should be same for all events'); 102 | 103 | teardown(); 104 | }); 105 | 106 | utils.dragElement({ 107 | element: item.getElement(), 108 | x: 100, 109 | y: 100, 110 | }); 111 | }); 112 | })(this); 113 | -------------------------------------------------------------------------------- /tests/grid-events/filter.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid events'); 5 | 6 | QUnit.test('filter: should be triggered after grid.filter()', function (assert) { 7 | assert.expect(3); 8 | 9 | var container = utils.createGridElements(); 10 | var grid = new Muuri(container); 11 | var itemsToShow = grid.getItems([0, 1, 2, 3, 4]); 12 | var itemsToHide = grid.getItems([5, 6, 7, 8, 9]); 13 | var teardown = function () { 14 | grid.destroy(); 15 | container.parentNode.removeChild(container); 16 | }; 17 | 18 | grid.on('filter', function (shownItems, hiddenItems) { 19 | assert.strictEqual(arguments.length, 2, 'callback: should have two arguments'); 20 | assert.deepEqual( 21 | utils.sortedIdList(shownItems), 22 | utils.sortedIdList(itemsToShow), 23 | 'callback: array of shown items should be the first argument' 24 | ); 25 | assert.deepEqual( 26 | utils.sortedIdList(hiddenItems), 27 | utils.sortedIdList(itemsToHide), 28 | 'callback: array of hidden items should be the second argument' 29 | ); 30 | }); 31 | grid.filter(function (item) { 32 | return itemsToShow.indexOf(item) > -1; 33 | }); 34 | teardown(); 35 | }); 36 | })(this); 37 | -------------------------------------------------------------------------------- /tests/grid-events/hideEnd.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid events'); 5 | 6 | QUnit.test( 7 | 'hideEnd: should be triggered after grid.hide() (after the hiding is finished)', 8 | function (assert) { 9 | assert.expect(2); 10 | 11 | var done = assert.async(); 12 | var container = utils.createGridElements(); 13 | var grid = new Muuri(container); 14 | var teardown = function () { 15 | grid.destroy(); 16 | container.parentNode.removeChild(container); 17 | done(); 18 | }; 19 | 20 | grid.hide(grid.getItems(0), { layout: false, instant: true, syncWithLayout: false }); 21 | grid.on('hideEnd', function (items) { 22 | assert.strictEqual(arguments.length, 1, 'callback: should have one argument'); 23 | assert.deepEqual( 24 | utils.sortedIdList(items), 25 | utils.sortedIdList(grid.getItems([0, 1, 2])), 26 | 'callback: first argument should be an array of all the items that are were hidden' 27 | ); 28 | teardown(); 29 | }); 30 | grid.hide(grid.getItems([0, 1, 2]), { layout: false, syncWithLayout: false }); 31 | } 32 | ); 33 | })(this); 34 | -------------------------------------------------------------------------------- /tests/grid-events/hideStart.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid events'); 5 | 6 | QUnit.test( 7 | 'hideStart: should be triggered after grid.hide() (before the showing starts)', 8 | function (assert) { 9 | assert.expect(2); 10 | 11 | var container = utils.createGridElements(); 12 | var grid = new Muuri(container); 13 | var teardown = function () { 14 | grid.destroy(); 15 | container.parentNode.removeChild(container); 16 | }; 17 | 18 | grid.hide(grid.getItems(0), { layout: false, instant: true, syncWithLayout: false }); 19 | grid.on('hideStart', function (items) { 20 | assert.strictEqual(arguments.length, 1, 'callback: should have one argument'); 21 | assert.deepEqual( 22 | utils.sortedIdList(items), 23 | utils.sortedIdList(grid.getItems([0, 1, 2])), 24 | 'callback: first argument should be an array of all the items that are about to be hidden' 25 | ); 26 | }); 27 | grid.hide(grid.getItems([0, 1, 2]), { layout: false, syncWithLayout: false }); 28 | teardown(); 29 | } 30 | ); 31 | })(this); 32 | -------------------------------------------------------------------------------- /tests/grid-events/layoutAbort.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid events'); 5 | 6 | QUnit.test( 7 | 'layoutAbort: should be emitted before layoutStart if current layout process is aborted', 8 | function (assert) { 9 | assert.expect(8); 10 | 11 | var done = assert.async(); 12 | var container = utils.createGridElements(); 13 | var grid = new Muuri(container); 14 | var expectedItems = []; 15 | var firstItem = grid.getItems()[0]; 16 | var teardown = function () { 17 | grid.destroy(); 18 | container.parentNode.removeChild(container); 19 | done(); 20 | }; 21 | 22 | grid.on('layoutAbort', function (items) { 23 | assert.strictEqual(arguments.length, 1, 'callback: should have a single argument'); 24 | assert.deepEqual( 25 | utils.idList(items), 26 | utils.idList(expectedItems), 27 | 'callback: first argument should be an array of items that were active when the layout was triggered' 28 | ); 29 | }); 30 | 31 | grid.move(1, -1); 32 | 33 | // Abort #1 34 | expectedItems = utils.getActiveItems(grid); 35 | grid.move(1, -1); 36 | 37 | // Abort #2 38 | expectedItems = utils.getActiveItems(grid); 39 | grid.hide([firstItem]); 40 | 41 | // Abort #3 42 | expectedItems = utils.getActiveItems(grid); 43 | grid.show([firstItem]); 44 | 45 | // Abort #4 46 | expectedItems = utils.getActiveItems(grid); 47 | grid.hide([firstItem]); 48 | 49 | teardown(); 50 | } 51 | ); 52 | })(this); 53 | -------------------------------------------------------------------------------- /tests/grid-events/layoutEnd.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid events'); 5 | 6 | QUnit.test( 7 | 'layoutEnd: should be triggered after grid.layout() (after the items have positioned)', 8 | function (assert) { 9 | assert.expect(3); 10 | 11 | var done = assert.async(); 12 | var container = utils.createGridElements(); 13 | var grid = new Muuri(container); 14 | var isAnyItemPositioning = false; 15 | var expectedItems = []; 16 | var teardown = function () { 17 | grid.destroy(); 18 | container.parentNode.removeChild(container); 19 | done(); 20 | }; 21 | 22 | grid.on('layoutEnd', function (items) { 23 | items.forEach(function (item) { 24 | if (item.isPositioning()) { 25 | isAnyItemPositioning = true; 26 | } 27 | }); 28 | assert.strictEqual(arguments.length, 1, 'callback: should have a single argument'); 29 | assert.deepEqual( 30 | utils.sortedIdList(items), 31 | utils.sortedIdList(expectedItems), 32 | 'callback: first argument should be an array of items that were active when the layout was triggered' 33 | ); 34 | assert.strictEqual( 35 | isAnyItemPositioning, 36 | false, 37 | 'callback: none of the items in the first argument should be in positioning state' 38 | ); 39 | teardown(); 40 | }); 41 | grid.hide(grid.getItems(0), { instant: true, layout: false, syncWithLayout: false }); 42 | expectedItems = utils.getActiveItems(grid); 43 | grid.move(1, -1); 44 | } 45 | ); 46 | })(this); 47 | -------------------------------------------------------------------------------- /tests/grid-events/layoutStart.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid events'); 5 | 6 | QUnit.test( 7 | 'layoutStart: should be triggered after grid.layout() (before the items are positioned)', 8 | function (assert) { 9 | assert.expect(6); 10 | 11 | var container = utils.createGridElements(); 12 | var grid = new Muuri(container); 13 | var layoutId = grid._layout.id; 14 | var numEvents = 0; 15 | var teardown = function () { 16 | grid.destroy(); 17 | container.parentNode.removeChild(container); 18 | }; 19 | 20 | utils.setStyles(container, { height: '' }); 21 | grid.on('layoutStart', function (items, isInstant) { 22 | ++numEvents; 23 | if (numEvents === 1) { 24 | assert.strictEqual(arguments.length, 2, 'should have two arguments'); 25 | assert.deepEqual( 26 | utils.sortedIdList(items), 27 | utils.sortedIdList(utils.getActiveItems(grid)), 28 | 'first argument should be an array of the items that are about to be laid out' 29 | ); 30 | assert.strictEqual( 31 | isInstant, 32 | false, 33 | 'second argument should be false when layout was not called with instant flag' 34 | ); 35 | assert.notStrictEqual( 36 | grid._layout.id, 37 | layoutId, 38 | 'should be called after layout is created' 39 | ); 40 | assert.notStrictEqual( 41 | container.style.height, 42 | '', 43 | 'should be called after container dimensions are updated' 44 | ); 45 | } else if (numEvents === 2) { 46 | assert.strictEqual( 47 | isInstant, 48 | true, 49 | 'second argument should be true when layout was called with instant flag' 50 | ); 51 | } else { 52 | assert.strictEqual( 53 | true, 54 | false, 55 | 'there should be one event per layout call, even if ongoing layout is aborted' 56 | ); 57 | } 58 | }); 59 | grid.move(0, -1); 60 | grid.layout(true); 61 | teardown(); 62 | } 63 | ); 64 | })(this); 65 | -------------------------------------------------------------------------------- /tests/grid-events/move.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid events'); 5 | 6 | QUnit.test('move: should be triggered after grid.move()', function (assert) { 7 | assert.expect(7); 8 | 9 | var container = utils.createGridElements(); 10 | var grid = new Muuri(container); 11 | var item = grid.getItems()[0]; 12 | var teardown = function () { 13 | grid.destroy(); 14 | container.parentNode.removeChild(container); 15 | }; 16 | 17 | grid.on('move', function (data) { 18 | assert.strictEqual(arguments.length, 1, 'callback: should have one argument'); 19 | assert.strictEqual( 20 | Object.prototype.toString.call(data), 21 | '[object Object]', 22 | 'callback: first argument should be a plain object' 23 | ); 24 | assert.strictEqual( 25 | Object.keys(data).length, 26 | 4, 27 | 'callback: first argument should have 4 properties' 28 | ); 29 | assert.strictEqual( 30 | data.item, 31 | item, 32 | 'callback: first argument item property should be the moved item' 33 | ); 34 | assert.strictEqual( 35 | data.action, 36 | 'move', 37 | 'callback: first argument action property should be the correct action' 38 | ); 39 | assert.strictEqual( 40 | data.fromIndex, 41 | 0, 42 | 'callback: first argument fromIndex property should be the index where the item was moved from' 43 | ); 44 | assert.strictEqual( 45 | data.toIndex, 46 | 1, 47 | 'callback: first argument toIndex property should be the index where the item was moved to' 48 | ); 49 | }); 50 | grid.move(item, 1, { layout: false }); 51 | teardown(); 52 | }); 53 | 54 | QUnit.test('move: should be triggered when sorting occurs during drag', function (assert) { 55 | assert.expect(7); 56 | 57 | var done = assert.async(); 58 | var container = utils.createGridElements({ 59 | containerStyles: { 60 | position: 'relative', 61 | width: '70px', 62 | }, 63 | }); 64 | var grid = new Muuri(container, { 65 | dragEnabled: true, 66 | dragSortInterval: 100, 67 | dragSortPredicate: { 68 | threshold: 50, 69 | action: 'move', 70 | }, 71 | }); 72 | var item = grid.getItems()[0]; 73 | var teardown = function () { 74 | grid.destroy(); 75 | container.parentNode.removeChild(container); 76 | done(); 77 | }; 78 | 79 | grid.on('move', function (data) { 80 | assert.strictEqual(arguments.length, 1, 'callback: should receive one argument'); 81 | assert.strictEqual( 82 | Object.prototype.toString.call(data), 83 | '[object Object]', 84 | 'callback: the argument should be a plain object' 85 | ); 86 | assert.strictEqual( 87 | Object.keys(data).length, 88 | 4, 89 | 'callback: the argument should have 4 properties' 90 | ); 91 | assert.strictEqual( 92 | data.item, 93 | item, 94 | 'callback: the argument item property should be the moved item' 95 | ); 96 | assert.strictEqual( 97 | data.action, 98 | 'move', 99 | 'callback: the argument action property should be the correct action' 100 | ); 101 | assert.strictEqual( 102 | data.fromIndex, 103 | 0, 104 | 'callback: the argument fromIndex property should be the index where the item was moved from' 105 | ); 106 | assert.strictEqual( 107 | data.toIndex, 108 | 1, 109 | 'callback: the argument toIndex property should be the index where the item was moved to' 110 | ); 111 | }); 112 | 113 | utils.dragElement({ 114 | element: item.getElement(), 115 | x: 0, 116 | y: 70, 117 | onFinished: teardown, 118 | }); 119 | }); 120 | })(this); 121 | -------------------------------------------------------------------------------- /tests/grid-events/remove.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid events'); 5 | 6 | QUnit.test('remove: should be triggered after grid.remove()', function (assert) { 7 | assert.expect(3); 8 | 9 | var container = utils.createGridElements(); 10 | var grid = new Muuri(container); 11 | var removedIndices = [0, 1]; 12 | var removedItems = grid.getItems(removedIndices); 13 | var teardown = function () { 14 | grid.destroy(); 15 | container.parentNode.removeChild(container); 16 | }; 17 | 18 | grid.on('remove', function (items, indices) { 19 | assert.strictEqual(arguments.length, 2, 'callback: should have two arguments'); 20 | assert.deepEqual( 21 | utils.sortedIdList(items), 22 | utils.sortedIdList(removedItems), 23 | 'callback: first argument should be an array of the removed items' 24 | ); 25 | assert.deepEqual( 26 | indices, 27 | removedIndices, 28 | 'callback: second argument should be an array of the removed item indices' 29 | ); 30 | }); 31 | grid.remove(removedItems); 32 | teardown(); 33 | }); 34 | })(this); 35 | -------------------------------------------------------------------------------- /tests/grid-events/showEnd.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid events'); 5 | 6 | QUnit.test( 7 | 'showEnd: should be triggered after grid.show() (after the showing is finished)', 8 | function (assert) { 9 | assert.expect(2); 10 | 11 | var done = assert.async(); 12 | var container = utils.createGridElements(); 13 | var grid = new Muuri(container); 14 | var teardown = function () { 15 | grid.destroy(); 16 | container.parentNode.removeChild(container); 17 | done(); 18 | }; 19 | 20 | grid.on('showEnd', function (items) { 21 | assert.strictEqual(arguments.length, 1, 'callback: should have one argument'); 22 | assert.deepEqual( 23 | utils.sortedIdList(items), 24 | utils.sortedIdList(grid.getItems([0, 1, 2])), 25 | 'callback: first argument should be an array of all the items that are were shown' 26 | ); 27 | teardown(); 28 | }); 29 | grid.hide(grid.getItems([0, 1]), { layout: false, instant: true, syncWithLayout: false }); 30 | grid.show(grid.getItems([0, 1, 2]), { layout: false, syncWithLayout: false }); 31 | } 32 | ); 33 | })(this); 34 | -------------------------------------------------------------------------------- /tests/grid-events/showStart.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid events'); 5 | 6 | QUnit.test( 7 | 'showStart: should be triggered after grid.show() (just before the showing starts)', 8 | function (assert) { 9 | assert.expect(2); 10 | 11 | var container = utils.createGridElements(); 12 | var grid = new Muuri(container); 13 | var teardown = function () { 14 | grid.destroy(); 15 | container.parentNode.removeChild(container); 16 | }; 17 | 18 | grid.on('showStart', function (items) { 19 | assert.strictEqual(arguments.length, 1, 'callback: should have one argument'); 20 | assert.deepEqual( 21 | utils.sortedIdList(items), 22 | utils.sortedIdList(grid.getItems([0, 1, 2])), 23 | 'callback: first argument should be an array of all the items that are about to be shown' 24 | ); 25 | }); 26 | grid.hide(grid.getItems([0, 1]), { layout: false, instant: true, syncWithLayout: false }); 27 | grid.show(grid.getItems([0, 1, 2]), { layout: false, syncWithLayout: false }); 28 | teardown(); 29 | } 30 | ); 31 | })(this); 32 | -------------------------------------------------------------------------------- /tests/grid-events/sort.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid events'); 5 | 6 | QUnit.test('sort: should be triggered after grid.sort()', function (assert) { 7 | assert.expect(3); 8 | 9 | var container = utils.createGridElements(); 10 | var grid = new Muuri(container); 11 | var currentOrder = grid.getItems(); 12 | var newOrder = currentOrder.concat().reverse(); 13 | var teardown = function () { 14 | grid.destroy(); 15 | container.parentNode.removeChild(container); 16 | }; 17 | 18 | grid.on('sort', function (itemsNew, itemsPrev) { 19 | assert.strictEqual(arguments.length, 2, 'callback: should have two arguments'); 20 | assert.deepEqual( 21 | utils.sortedIdList(itemsNew), 22 | utils.sortedIdList(newOrder), 23 | 'callback: first argument should be an array of all the items in their new order' 24 | ); 25 | assert.deepEqual( 26 | utils.sortedIdList(itemsPrev), 27 | utils.sortedIdList(currentOrder), 28 | 'callback: second argument should be an array of all the items in their previous order' 29 | ); 30 | }); 31 | grid.sort(newOrder); 32 | teardown(); 33 | }); 34 | })(this); 35 | -------------------------------------------------------------------------------- /tests/grid-events/synchronize.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid events'); 5 | 6 | QUnit.test('synchronize: should be triggered after grid.synchronize()', function (assert) { 7 | assert.expect(1); 8 | 9 | var container = utils.createGridElements(); 10 | var grid = new Muuri(container); 11 | var teardown = function () { 12 | grid.destroy(); 13 | container.parentNode.removeChild(container); 14 | }; 15 | 16 | grid.on('synchronize', function () { 17 | assert.strictEqual(arguments.length, 0, 'callback: should have no arguments'); 18 | }); 19 | grid.synchronize(); 20 | teardown(); 21 | }); 22 | })(this); 23 | -------------------------------------------------------------------------------- /tests/grid-methods/destroy.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid methods'); 5 | 6 | QUnit.test('destroy: should return the instance', function (assert) { 7 | assert.expect(1); 8 | 9 | var container = utils.createGridElements(); 10 | var grid = new Muuri(container); 11 | var teardown = function () { 12 | grid.destroy(); 13 | container.parentNode.removeChild(container); 14 | }; 15 | 16 | assert.strictEqual(grid.destroy(), grid); 17 | teardown(); 18 | }); 19 | })(this); 20 | -------------------------------------------------------------------------------- /tests/grid-methods/getElement.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid methods'); 5 | 6 | QUnit.test('getElement: should return the container element', function (assert) { 7 | assert.expect(1); 8 | 9 | var container = utils.createGridElements(); 10 | var grid = new Muuri(container); 11 | var teardown = function () { 12 | grid.destroy(); 13 | container.parentNode.removeChild(container); 14 | }; 15 | 16 | assert.strictEqual(grid.getElement(), container); 17 | teardown(); 18 | }); 19 | })(this); 20 | -------------------------------------------------------------------------------- /tests/grid-methods/getItems.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | var idList = utils.idList; 4 | 5 | QUnit.module('Grid methods'); 6 | 7 | QUnit.test('getItems: should return the instance`s items', function (assert) { 8 | assert.expect(6); 9 | 10 | var done = assert.async(); 11 | var container = utils.createGridElements(); 12 | var grid = new Muuri(container); 13 | var items = grid.getItems(); 14 | var teardown = function () { 15 | grid.destroy(); 16 | container.parentNode.removeChild(container); 17 | done(); 18 | }; 19 | 20 | assert.notStrictEqual( 21 | idList(grid.getItems()), 22 | idList(grid._items), 23 | 'should return a new array and not a reference to the internal array' 24 | ); 25 | assert.deepEqual( 26 | idList(grid.getItems()), 27 | idList(grid._items), 28 | 'should return all items in correct order if no arguments are provided' 29 | ); 30 | assert.deepEqual( 31 | idList(grid.getItems(0)), 32 | idList([items[0]]), 33 | 'should allow providing an index as the first argument' 34 | ); 35 | assert.deepEqual( 36 | idList(grid.getItems(items[0].getElement())), 37 | idList([items[0]]), 38 | 'should allow providing an element as the first argument' 39 | ); 40 | assert.deepEqual( 41 | idList(grid.getItems(items[0])), 42 | idList([items[0]]), 43 | 'should allow providing an item as the first argument' 44 | ); 45 | assert.deepEqual( 46 | idList(grid.getItems([0, items[1].getElement(), items[2]])), 47 | idList([items[0], items[1], items[2]]), 48 | 'should allow providing an array of indices, elements and items as the first argument' 49 | ); 50 | 51 | teardown(); 52 | }); 53 | })(this); 54 | -------------------------------------------------------------------------------- /tests/grid-methods/hide.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | var idList = utils.idList; 4 | 5 | QUnit.module('Grid methods'); 6 | 7 | QUnit.test('hide: should return the instance', function (assert) { 8 | assert.expect(1); 9 | 10 | var container = utils.createGridElements(); 11 | var grid = new Muuri(container); 12 | var teardown = function () { 13 | grid.destroy(); 14 | container.parentNode.removeChild(container); 15 | }; 16 | 17 | assert.strictEqual(grid.hide(grid.getItems(0)), grid); 18 | teardown(); 19 | }); 20 | 21 | QUnit.test('hide: should accept an array of items as the first argument', function (assert) { 22 | assert.expect(2); 23 | 24 | var container = utils.createGridElements(); 25 | var grid = new Muuri(container); 26 | var items = grid.getItems(); 27 | var teardown = function () { 28 | grid.destroy(); 29 | container.parentNode.removeChild(container); 30 | }; 31 | 32 | assert.strictEqual( 33 | utils.getHiddenItems(grid).length, 34 | 0, 35 | 'there should be no hidden items before the tests start' 36 | ); 37 | grid.hide(items.slice(0, 3)); 38 | assert.deepEqual( 39 | idList(utils.getHiddenItems(grid)), 40 | idList(items.slice(0, 3)), 41 | 'should accept an array of items as the first argument' 42 | ); 43 | 44 | teardown(); 45 | }); 46 | 47 | QUnit.test('hide: should not hide instantly by default', function (assert) { 48 | assert.expect(1); 49 | 50 | var container = utils.createGridElements(); 51 | var grid = new Muuri(container); 52 | var items = grid.getItems(); 53 | var teardown = function () { 54 | grid.destroy(); 55 | container.parentNode.removeChild(container); 56 | }; 57 | 58 | grid.hide(items.slice(0, 1)); 59 | assert.deepEqual(idList(utils.getHidingItems(grid)), idList(items.slice(0, 1))); 60 | 61 | teardown(); 62 | }); 63 | 64 | QUnit.test('hide: should hide instantly if instant option is true', function (assert) { 65 | assert.expect(2); 66 | 67 | var container = utils.createGridElements(); 68 | var grid = new Muuri(container); 69 | var items = grid.getItems(); 70 | var teardown = function () { 71 | grid.destroy(); 72 | container.parentNode.removeChild(container); 73 | }; 74 | 75 | grid.hide(items.slice(0, 1), { instant: true }); 76 | assert.strictEqual(items[0].isHiding(), false); 77 | assert.strictEqual(items[0].isVisible(), false); 78 | 79 | teardown(); 80 | }); 81 | 82 | QUnit.test('hide: should call the onFinish callback once the animation is finished', function ( 83 | assert 84 | ) { 85 | assert.expect(5); 86 | 87 | var done = assert.async(); 88 | var container = utils.createGridElements(); 89 | var grid = new Muuri(container); 90 | var items = grid.getItems(); 91 | var argItems = null; 92 | var teardown = function () { 93 | grid.destroy(); 94 | container.parentNode.removeChild(container); 95 | done(); 96 | }; 97 | 98 | grid 99 | .on('hideEnd', function (completedItems) { 100 | assert.deepEqual( 101 | idList(completedItems), 102 | idList(argItems), 103 | 'callback: the received items should match the items of show event callback' 104 | ); 105 | teardown(); 106 | }) 107 | .hide(items.slice(0, 1), { 108 | onFinish: function (completedItems) { 109 | assert.strictEqual(arguments.length, 1, 'callback: should receive one argument'); 110 | assert.deepEqual( 111 | idList(completedItems), 112 | idList(items.slice(0, 1)), 113 | 'callback: should receive the hidden items as it`s first argument' 114 | ); 115 | assert.strictEqual( 116 | completedItems[0].isVisible(), 117 | false, 118 | 'callback: the received items should not be in "visible" state' 119 | ); 120 | assert.strictEqual( 121 | completedItems[0].isHiding(), 122 | false, 123 | 'callback: the received items should not be in "hiding" state' 124 | ); 125 | argItems = completedItems; 126 | }, 127 | }); 128 | }); 129 | })(this); 130 | -------------------------------------------------------------------------------- /tests/grid-methods/layout.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | var idList = utils.idList; 4 | 5 | QUnit.module('Grid methods'); 6 | 7 | QUnit.test('layout: should return the instance', function (assert) { 8 | assert.expect(1); 9 | 10 | var container = utils.createGridElements(); 11 | var grid = new Muuri(container); 12 | var teardown = function () { 13 | grid.destroy(); 14 | container.parentNode.removeChild(container); 15 | }; 16 | 17 | assert.strictEqual(grid.layout(), grid); 18 | teardown(); 19 | }); 20 | 21 | QUnit.test('layout: should not layout the items instantly by default', function (assert) { 22 | assert.expect(1); 23 | 24 | var container = utils.createGridElements(); 25 | var grid = new Muuri(container); 26 | var items = grid.getItems(); 27 | var teardown = function () { 28 | grid.destroy(); 29 | container.parentNode.removeChild(container); 30 | }; 31 | 32 | grid.move(0, -1, { layout: false }); 33 | grid.layout(); 34 | assert.strictEqual(items[0].isPositioning(), true); 35 | teardown(); 36 | }); 37 | 38 | QUnit.test('layout: should layout the items instantly if the first argument is true', function ( 39 | assert 40 | ) { 41 | assert.expect(1); 42 | 43 | var container = utils.createGridElements(); 44 | var grid = new Muuri(container); 45 | var items = grid.getItems(); 46 | var teardown = function () { 47 | grid.destroy(); 48 | container.parentNode.removeChild(container); 49 | }; 50 | 51 | grid.move(0, -1, { layout: false }); 52 | grid.layout(true); 53 | assert.strictEqual(items[0].isPositioning(), false); 54 | teardown(); 55 | }); 56 | 57 | QUnit.test( 58 | 'layout: should call the provided callback function after layout is finished', 59 | function (assert) { 60 | assert.expect(4); 61 | 62 | var done = assert.async(); 63 | var container = utils.createGridElements(); 64 | var grid = new Muuri(container); 65 | var teardown = function () { 66 | grid.destroy(); 67 | container.parentNode.removeChild(container); 68 | done(); 69 | }; 70 | 71 | grid.move(0, -1, { layout: false }).layout(function (items, isInterrupted) { 72 | assert.strictEqual(arguments.length, 2, 'callback: should have two arguments'); 73 | assert.deepEqual( 74 | idList(items), 75 | idList(utils.getActiveItems(grid)), 76 | 'callback: first argument should be an array of the positioned items (all active items)' 77 | ); 78 | assert.strictEqual( 79 | isInterrupted, 80 | false, 81 | 'callback: second argument should be a boolean that is true if the layout process was interrupted' 82 | ); 83 | assert.strictEqual( 84 | items[0].isPositioning(), 85 | false, 86 | 'callback: items should not be in positioning state' 87 | ); 88 | teardown(); 89 | }); 90 | } 91 | ); 92 | })(this); 93 | -------------------------------------------------------------------------------- /tests/grid-methods/move.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | var idList = utils.idList; 4 | 5 | QUnit.module('Grid methods'); 6 | 7 | QUnit.test('move: should return the instance', function (assert) { 8 | assert.expect(1); 9 | 10 | var container = utils.createGridElements(); 11 | var grid = new Muuri(container); 12 | var teardown = function () { 13 | grid.destroy(); 14 | container.parentNode.removeChild(container); 15 | }; 16 | 17 | assert.strictEqual(grid.move(0, -1), grid); 18 | teardown(); 19 | }); 20 | 21 | QUnit.test( 22 | 'move: should accept elements, items and indices as the first and second arguments', 23 | function (assert) { 24 | assert.expect(3); 25 | 26 | var container = utils.createGridElements(); 27 | var grid = new Muuri(container); 28 | var items = grid.getItems(); 29 | var move = function (array, fromIndex, toIndex) { 30 | array.splice(toIndex, 0, array.splice(fromIndex, 1)[0]); 31 | }; 32 | var teardown = function () { 33 | grid.destroy(); 34 | container.parentNode.removeChild(container); 35 | }; 36 | 37 | grid.move(0, 1); 38 | move(items, 0, 1); 39 | assert.deepEqual(idList(grid.getItems()), idList(items), 'should accept indices'); 40 | 41 | grid.move(items[0].getElement(), items[1].getElement()); 42 | move(items, 0, 1); 43 | assert.deepEqual(idList(grid.getItems()), idList(items), 'should accept elements'); 44 | 45 | grid.move(items[0], items[1]); 46 | move(items, 0, 1); 47 | assert.deepEqual(idList(grid.getItems()), idList(items), 'should accept items'); 48 | 49 | teardown(); 50 | } 51 | ); 52 | 53 | QUnit.test('move: should normalize negative indices to positive indices', function (assert) { 54 | assert.expect(3); 55 | 56 | var container = utils.createGridElements(); 57 | var grid = new Muuri(container); 58 | var items = grid.getItems(); 59 | var move = function (array, fromIndex, toIndex) { 60 | array.splice(toIndex, 0, array.splice(fromIndex, 1)[0]); 61 | }; 62 | var teardown = function () { 63 | grid.destroy(); 64 | container.parentNode.removeChild(container); 65 | }; 66 | 67 | grid.move(0, -1); 68 | move(items, 0, items.length - 1); 69 | assert.deepEqual(idList(grid.getItems()), idList(items), 'should normalize -1 to last index'); 70 | 71 | grid.move(0, -2); 72 | move(items, 0, items.length - 2); 73 | assert.deepEqual( 74 | idList(grid.getItems()), 75 | idList(items), 76 | 'should normalize -2 to second last index' 77 | ); 78 | 79 | grid.move(0, -1000); 80 | assert.deepEqual( 81 | idList(grid.getItems()), 82 | idList(items), 83 | 'should normalize too large negative index to 0' 84 | ); 85 | 86 | teardown(); 87 | }); 88 | 89 | QUnit.test('move: should not swap items by default', function (assert) { 90 | assert.expect(1); 91 | 92 | var container = utils.createGridElements(); 93 | var grid = new Muuri(container); 94 | var items = grid.getItems(); 95 | var move = function (array, fromIndex, toIndex) { 96 | array.splice(toIndex, 0, array.splice(fromIndex, 1)[0]); 97 | }; 98 | var teardown = function () { 99 | grid.destroy(); 100 | container.parentNode.removeChild(container); 101 | }; 102 | 103 | grid.move(0, 2); 104 | move(items, 0, 2); 105 | assert.deepEqual(idList(grid.getItems()), idList(items)); 106 | 107 | teardown(); 108 | }); 109 | 110 | QUnit.test('move: should swap items when action option is set to "swap"', function (assert) { 111 | assert.expect(2); 112 | 113 | var container = utils.createGridElements(); 114 | var grid = new Muuri(container); 115 | var items = grid.getItems(); 116 | var teardown = function () { 117 | grid.destroy(); 118 | container.parentNode.removeChild(container); 119 | }; 120 | 121 | grid.move(0, 2, { action: 'swap' }); 122 | assert.strictEqual(grid.getItems().indexOf(items[0]), 2); 123 | assert.strictEqual(grid.getItems().indexOf(items[2]), 0); 124 | 125 | teardown(); 126 | }); 127 | })(this); 128 | -------------------------------------------------------------------------------- /tests/grid-methods/off.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid methods'); 5 | 6 | QUnit.test('off: should return the instance', function (assert) { 7 | assert.expect(1); 8 | 9 | var container = utils.createGridElements(); 10 | var grid = new Muuri(container); 11 | var teardown = function () { 12 | grid.destroy(); 13 | container.parentNode.removeChild(container); 14 | }; 15 | 16 | assert.strictEqual( 17 | grid.off('foo', function () {}), 18 | grid 19 | ); 20 | teardown(); 21 | }); 22 | 23 | QUnit.test('off: should unbind an event listener', function (assert) { 24 | assert.expect(1); 25 | 26 | var container = utils.createGridElements(); 27 | var grid = new Muuri(container); 28 | var calls = 0; 29 | var callback1 = function () { 30 | ++calls; 31 | }; 32 | var callback2 = function () { 33 | ++calls; 34 | }; 35 | var teardown = function () { 36 | grid.destroy(); 37 | container.parentNode.removeChild(container); 38 | }; 39 | 40 | grid.on('synchronize', callback1); 41 | grid.on('synchronize', callback1); 42 | grid.on('synchronize', callback2); 43 | grid.off('synchronize', callback1); 44 | grid.synchronize(); 45 | assert.strictEqual( 46 | calls, 47 | 1, 48 | 'should unbind all the listeners from the event that match the provided callback' 49 | ); 50 | teardown(); 51 | }); 52 | })(this); 53 | -------------------------------------------------------------------------------- /tests/grid-methods/on.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid methods'); 5 | 6 | QUnit.test('on: should return the instance', function (assert) { 7 | assert.expect(1); 8 | 9 | var container = utils.createGridElements(); 10 | var grid = new Muuri(container); 11 | var teardown = function () { 12 | grid.destroy(); 13 | container.parentNode.removeChild(container); 14 | }; 15 | 16 | assert.strictEqual( 17 | grid.on('foo', function () {}), 18 | grid 19 | ); 20 | teardown(); 21 | }); 22 | 23 | QUnit.test('on: should bind an event listener', function (assert) { 24 | assert.expect(1); 25 | 26 | var container = utils.createGridElements(); 27 | var grid = new Muuri(container); 28 | var calls = 0; 29 | var callback = function () { 30 | ++calls; 31 | }; 32 | var teardown = function () { 33 | grid.destroy(); 34 | container.parentNode.removeChild(container); 35 | }; 36 | 37 | grid.on('synchronize', callback); 38 | grid.synchronize().synchronize().synchronize(); 39 | assert.strictEqual(calls, 3, 'should execute the listeners when event is emitted'); 40 | teardown(); 41 | }); 42 | })(this); 43 | -------------------------------------------------------------------------------- /tests/grid-methods/refreshItems.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid methods'); 5 | 6 | QUnit.test('refreshItems: should return the instance', function (assert) { 7 | assert.expect(1); 8 | 9 | var container = utils.createGridElements(); 10 | var grid = new Muuri(container); 11 | var teardown = function () { 12 | grid.destroy(); 13 | container.parentNode.removeChild(container); 14 | }; 15 | 16 | assert.strictEqual(grid.refreshItems(), grid); 17 | teardown(); 18 | }); 19 | 20 | QUnit.test('refreshItems: should update the cached dimensions of instance`s items', function ( 21 | assert 22 | ) { 23 | assert.expect(7); 24 | 25 | var container = utils.createGridElements({ 26 | itemCount: 5, 27 | itemStyles: { 28 | position: 'absolute', 29 | width: '50px', 30 | height: '50px', 31 | padding: '0px', 32 | border: '0px', 33 | margin: '10px', 34 | boxSizing: 'border-box', 35 | background: '#000', 36 | }, 37 | }); 38 | var grid = new Muuri(container); 39 | var items = grid.getItems(); 40 | var updateItemDimensions = function (items) { 41 | [].concat(items).forEach(function (item) { 42 | utils.setStyles(item.getElement(), { 43 | width: '10px', 44 | height: '20px', 45 | margin: '30px', 46 | }); 47 | }); 48 | }; 49 | var assertItemChange = function (items, msg) { 50 | [].concat(items).forEach(function (item) { 51 | var result = { 52 | margin: item.getMargin(), 53 | width: item.getWidth(), 54 | height: item.getHeight(), 55 | }; 56 | assert.deepEqual( 57 | result, 58 | { 59 | width: 10, 60 | height: 20, 61 | margin: { 62 | left: 30, 63 | right: 30, 64 | top: 30, 65 | bottom: 30, 66 | }, 67 | }, 68 | msg 69 | ); 70 | }); 71 | }; 72 | var teardown = function () { 73 | grid.destroy(); 74 | container.parentNode.removeChild(container); 75 | }; 76 | 77 | updateItemDimensions(items.slice(0, 2)); 78 | grid.refreshItems(items.slice(0, 2)); 79 | assertItemChange(items.slice(0, 2), 'should accept an array of items'); 80 | 81 | updateItemDimensions(items); 82 | grid.refreshItems(); 83 | assertItemChange(items, 'should refresh all items if no aguments are provided'); 84 | 85 | teardown(); 86 | }); 87 | })(this); 88 | -------------------------------------------------------------------------------- /tests/grid-methods/refreshSortData.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid methods'); 5 | 6 | QUnit.test('refreshSortData: should return the instance', function (assert) { 7 | assert.expect(1); 8 | 9 | var container = utils.createGridElements(); 10 | var grid = new Muuri(container); 11 | var teardown = function () { 12 | grid.destroy(); 13 | container.parentNode.removeChild(container); 14 | }; 15 | 16 | assert.strictEqual(grid.refreshSortData(), grid); 17 | teardown(); 18 | }); 19 | })(this); 20 | -------------------------------------------------------------------------------- /tests/grid-methods/remove.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | var idList = utils.idList; 4 | 5 | QUnit.module('Grid methods'); 6 | 7 | QUnit.test('remove: should return the removed items', function (assert) { 8 | assert.expect(1); 9 | 10 | var container = utils.createGridElements(); 11 | var grid = new Muuri(container); 12 | var removedItems = grid.getItems([0, 1]); 13 | var teardown = function () { 14 | grid.destroy(); 15 | container.parentNode.removeChild(container); 16 | }; 17 | 18 | assert.deepEqual(grid.remove(removedItems), removedItems); 19 | teardown(); 20 | }); 21 | 22 | QUnit.test('remove: should accept an array of items as the first argument', function (assert) { 23 | assert.expect(1); 24 | 25 | var container = utils.createGridElements(); 26 | var grid = new Muuri(container); 27 | var teardown = function () { 28 | grid.destroy(); 29 | container.parentNode.removeChild(container); 30 | }; 31 | 32 | grid.remove(grid.getItems([0, 1])); 33 | assert.strictEqual( 34 | grid.getItems().length, 35 | 8, 36 | 'should accept an array of items as the first argument' 37 | ); 38 | 39 | teardown(); 40 | }); 41 | 42 | QUnit.test('remove: should not remove the item elements by default', function (assert) { 43 | assert.expect(1); 44 | 45 | var container = utils.createGridElements(); 46 | var grid = new Muuri(container); 47 | var teardown = function () { 48 | grid.destroy(); 49 | container.parentNode.removeChild(container); 50 | }; 51 | 52 | grid.remove(grid.getItems([0, 1])); 53 | assert.strictEqual(container.children.length, 10); 54 | teardown(); 55 | }); 56 | 57 | QUnit.test( 58 | 'remove: should remove the item elements when removeElements option is set to true', 59 | function (assert) { 60 | assert.expect(1); 61 | 62 | var container = utils.createGridElements(); 63 | var grid = new Muuri(container); 64 | var teardown = function () { 65 | grid.destroy(); 66 | container.parentNode.removeChild(container); 67 | }; 68 | 69 | grid.remove(grid.getItems([0, 1]), { removeElements: true }); 70 | assert.strictEqual(container.children.length, 8); 71 | teardown(); 72 | } 73 | ); 74 | 75 | QUnit.test('remove: should automatically layout the grid after remove', function (assert) { 76 | assert.expect(1); 77 | 78 | var container = utils.createGridElements(); 79 | var grid = new Muuri(container); 80 | var teardown = function () { 81 | grid.destroy(); 82 | container.parentNode.removeChild(container); 83 | }; 84 | 85 | grid.on('layoutStart', function () { 86 | assert.strictEqual(true, true); 87 | teardown(); 88 | }); 89 | grid.remove(grid.getItems(0)); 90 | }); 91 | 92 | QUnit.test( 93 | 'remove: should not trigger layout after remove when layout option is set to false', 94 | function (assert) { 95 | assert.expect(0); 96 | 97 | var container = utils.createGridElements(); 98 | var grid = new Muuri(container); 99 | var teardown = function () { 100 | grid.destroy(); 101 | container.parentNode.removeChild(container); 102 | }; 103 | 104 | grid.on('layoutStart', function () { 105 | assert.strictEqual(true, false); 106 | }); 107 | grid.remove(grid.getItems(0), { layout: false }); 108 | teardown(); 109 | } 110 | ); 111 | 112 | QUnit.test( 113 | 'remove: should trigger unanimated layout after add when layout option is set to "instant"', 114 | function (assert) { 115 | assert.expect(1); 116 | 117 | var container = utils.createGridElements(); 118 | var grid = new Muuri(container); 119 | var teardown = function () { 120 | grid.destroy(); 121 | container.parentNode.removeChild(container); 122 | }; 123 | 124 | grid.on('layoutEnd', function () { 125 | assert.strictEqual(true, true); 126 | teardown(); 127 | }); 128 | grid.remove(grid.getItems(0), { layout: 'instant' }); 129 | } 130 | ); 131 | 132 | QUnit.test( 133 | 'remove: should trigger layout and call callback function after add when a callback function is provided to the layout option', 134 | function (assert) { 135 | assert.expect(2); 136 | 137 | var done = assert.async(); 138 | var container = utils.createGridElements(); 139 | var grid = new Muuri(container); 140 | var teardown = function () { 141 | grid.destroy(); 142 | container.parentNode.removeChild(container); 143 | done(); 144 | }; 145 | var args; 146 | 147 | grid.on('layoutEnd', function (items) { 148 | assert.notStrictEqual( 149 | args, 150 | items, 151 | 'layout callback items argument should not the same object as the layoutEnd event callback`s argument' 152 | ); 153 | assert.deepEqual( 154 | idList(args), 155 | idList(items), 156 | 'layout callback should receive the same items as the layoutEnd event callback' 157 | ); 158 | teardown(); 159 | }); 160 | 161 | grid.remove(grid.getItems(0), { 162 | layout: function (items) { 163 | args = items; 164 | }, 165 | }); 166 | } 167 | ); 168 | })(this); 169 | -------------------------------------------------------------------------------- /tests/grid-methods/show.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | var idList = utils.idList; 4 | 5 | QUnit.module('Grid methods'); 6 | 7 | QUnit.test('show: should return the instance', function (assert) { 8 | assert.expect(1); 9 | 10 | var container = utils.createGridElements(); 11 | var grid = new Muuri(container); 12 | var teardown = function () { 13 | grid.destroy(); 14 | container.parentNode.removeChild(container); 15 | }; 16 | 17 | assert.strictEqual(grid.show(grid.getItems(0)), grid); 18 | teardown(); 19 | }); 20 | 21 | QUnit.test('show: should accept an array of items as the first argument', function (assert) { 22 | assert.expect(2); 23 | 24 | var container = utils.createGridElements(); 25 | var grid = new Muuri(container); 26 | var items = grid.getItems(); 27 | var teardown = function () { 28 | grid.destroy(); 29 | container.parentNode.removeChild(container); 30 | }; 31 | 32 | grid.hide(items, { instant: true }); 33 | assert.strictEqual( 34 | utils.getVisibleItems(grid).length, 35 | 0, 36 | 'there should be no visible items before the tests start' 37 | ); 38 | 39 | grid.show(items.slice(0, 3)); 40 | assert.deepEqual( 41 | idList(utils.getVisibleItems(grid)), 42 | idList(items.slice(0, 3)), 43 | 'should accept an array of items as the first argument' 44 | ); 45 | 46 | teardown(); 47 | }); 48 | 49 | QUnit.test('show: should not show instantly by default', function (assert) { 50 | assert.expect(1); 51 | 52 | var container = utils.createGridElements(); 53 | var grid = new Muuri(container); 54 | var items = grid.getItems(); 55 | var teardown = function () { 56 | grid.destroy(); 57 | container.parentNode.removeChild(container); 58 | }; 59 | 60 | grid.hide(items, { instant: true }).show(items.slice(0, 1)); 61 | assert.deepEqual(idList(utils.getShowingItems(grid)), idList(items.slice(0, 1))); 62 | 63 | teardown(); 64 | }); 65 | 66 | QUnit.test('show: should show instantly if instant option is true', function (assert) { 67 | assert.expect(2); 68 | 69 | var container = utils.createGridElements(); 70 | var grid = new Muuri(container); 71 | var items = grid.getItems(); 72 | var teardown = function () { 73 | grid.destroy(); 74 | container.parentNode.removeChild(container); 75 | }; 76 | 77 | grid.hide(items, { instant: true }).show(items.slice(0, 1), { instant: true }); 78 | assert.deepEqual(items[0].isShowing(), false); 79 | assert.deepEqual(items[0].isVisible(), true); 80 | 81 | teardown(); 82 | }); 83 | 84 | QUnit.test('show: should call the onFinish callback once the animation is finished', function ( 85 | assert 86 | ) { 87 | assert.expect(5); 88 | 89 | var done = assert.async(); 90 | var container = utils.createGridElements(); 91 | var grid = new Muuri(container); 92 | var items = grid.getItems(); 93 | var argItems = null; 94 | var teardown = function () { 95 | grid.destroy(); 96 | container.parentNode.removeChild(container); 97 | done(); 98 | }; 99 | 100 | grid 101 | .on('showEnd', function (completedItems) { 102 | assert.deepEqual( 103 | idList(completedItems), 104 | idList(argItems), 105 | 'callback: the received items should match the items of show event callback' 106 | ); 107 | teardown(); 108 | }) 109 | .hide(items, { instant: true }) 110 | .show(grid.getItems(0), { 111 | onFinish: function (completedItems) { 112 | assert.strictEqual(arguments.length, 1, 'callback: should receive one argument'); 113 | assert.deepEqual( 114 | idList(completedItems), 115 | idList(items.slice(0, 1)), 116 | 'callback: should receive the shown items as it`s first argument' 117 | ); 118 | assert.strictEqual( 119 | completedItems[0].isVisible(), 120 | true, 121 | 'callback: the received items should be in "visible" state' 122 | ); 123 | assert.strictEqual( 124 | completedItems[0].isShowing(), 125 | false, 126 | 'callback: the received items should not be in "showing" state' 127 | ); 128 | argItems = completedItems; 129 | }, 130 | }); 131 | }); 132 | })(this); 133 | -------------------------------------------------------------------------------- /tests/grid-methods/synchronize.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid methods'); 5 | 6 | QUnit.test('synchronize: should return the instance', function (assert) { 7 | assert.expect(1); 8 | 9 | var container = utils.createGridElements(); 10 | var grid = new Muuri(container); 11 | var teardown = function () { 12 | grid.destroy(); 13 | container.parentNode.removeChild(container); 14 | }; 15 | 16 | assert.strictEqual(grid.synchronize(), grid); 17 | teardown(); 18 | }); 19 | 20 | QUnit.test('synchronize: should order the dom elements to match the order of items', function ( 21 | assert 22 | ) { 23 | assert.expect(2); 24 | 25 | var container = utils.createGridElements(); 26 | var grid = new Muuri(container); 27 | var elements = []; 28 | var teardown = function () { 29 | grid.destroy(); 30 | container.parentNode.removeChild(container); 31 | }; 32 | 33 | grid.move(0, -1, { layout: false }); 34 | elements = grid.getItems().map(function (item) { 35 | return item.getElement(); 36 | }); 37 | assert.notDeepEqual( 38 | [].slice.call(container.children), 39 | elements, 40 | 'elements should be out of sync after an item is moved' 41 | ); 42 | grid.synchronize(); 43 | assert.deepEqual( 44 | [].slice.call(container.children), 45 | elements, 46 | 'elements should be in sync after grid.synchronize() is called' 47 | ); 48 | teardown(); 49 | }); 50 | })(this); 51 | -------------------------------------------------------------------------------- /tests/grid-options/containerClass.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid options'); 5 | 6 | QUnit.test('containerClass: should define the classname for the container element', function ( 7 | assert 8 | ) { 9 | assert.expect(1); 10 | 11 | var container = utils.createGridElements(); 12 | var grid = new Muuri(container, { 13 | containerClass: 'foo', 14 | }); 15 | var teardown = function () { 16 | grid.destroy(); 17 | container.parentNode.removeChild(container); 18 | }; 19 | 20 | assert.strictEqual(utils.matches(container, '.foo'), true); 21 | teardown(); 22 | }); 23 | })(this); 24 | -------------------------------------------------------------------------------- /tests/grid-options/dragAutoScroll.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid options'); 5 | 6 | QUnit.test('dragAutoScroll: should scroll window vertically and horizontally', function (assert) { 7 | assert.expect(4 + 4 * 6); 8 | 9 | var done = assert.async(); 10 | var container = utils.createGridElements({ 11 | itemCount: 2, 12 | containerStyles: { 13 | position: 'absolute', 14 | left: '0px', 15 | top: '0px', 16 | width: '140px', 17 | }, 18 | }); 19 | 20 | // Create fixed drag container. 21 | var dragContainer = document.createElement('div'); 22 | dragContainer.style.position = 'fixed'; 23 | document.body.appendChild(dragContainer); 24 | 25 | // Make document body large so that window can scroll. 26 | document.body.style.position = 'relative'; 27 | document.body.style.height = '10000px'; 28 | document.body.style.width = '10000px'; 29 | 30 | // Init grid. 31 | var grid = new Muuri(container, { 32 | dragEnabled: true, 33 | dragSort: false, 34 | dragContainer: dragContainer, 35 | dragAutoScroll: { 36 | targets: [window], 37 | onStart: function (item, element, direction) { 38 | assert.ok(grid.getItems().indexOf(item) > -1, 'onStart item argument is grid item'); 39 | assert.strictEqual(element, window, 'onStart element argument is window'); 40 | assert.ok( 41 | [ 42 | Muuri.AutoScroller.LEFT, 43 | Muuri.AutoScroller.RIGHT, 44 | Muuri.AutoScroller.UP, 45 | Muuri.AutoScroller.DOWN, 46 | ].indexOf(direction) > -1, 47 | 'onStart direction argument is valid direction' 48 | ); 49 | }, 50 | onStop: function (item, element, direction) { 51 | assert.ok(grid.getItems().indexOf(item) > -1, 'onStop item argument is grid item'); 52 | assert.strictEqual(element, window, 'onStop element argument is window'); 53 | assert.ok( 54 | [ 55 | Muuri.AutoScroller.LEFT, 56 | Muuri.AutoScroller.RIGHT, 57 | Muuri.AutoScroller.UP, 58 | Muuri.AutoScroller.DOWN, 59 | ].indexOf(direction) > -1, 60 | 'onStop direction argument is valid direction' 61 | ); 62 | }, 63 | }, 64 | }); 65 | 66 | // Make sure window is not scrolled on init. 67 | var scrollX = 0; 68 | var scrollY = 0; 69 | window.scrollTo(scrollX, scrollY); 70 | assert.ok( 71 | window.pageXOffset === scrollX && window.pageYOffset === scrollY, 72 | 'window should not be scrolled on init' 73 | ); 74 | 75 | // Compute how much we need to drag the item and make sure that it is 76 | // possible to trigger auto-scroll. 77 | var item = grid.getItems()[0]; 78 | var itemRect = item.getElement().getBoundingClientRect(); 79 | var leftOffset = window.innerWidth - itemRect.right; 80 | var topOffset = window.innerHeight - itemRect.bottom; 81 | assert.ok(leftOffset > 0 && topOffset > 0, 'item can scroll the window'); 82 | 83 | // Define teardown procedure. 84 | var teardown = function () { 85 | grid.destroy(); 86 | container.parentNode.removeChild(container); 87 | dragContainer.parentNode.removeChild(dragContainer); 88 | document.body.style.height = ''; 89 | document.body.style.width = ''; 90 | document.body.style.position = ''; 91 | window.scrollTo(0, 0); 92 | done(); 93 | }; 94 | 95 | // Drag down right. 96 | utils.dragElement({ 97 | element: item.getElement(), 98 | x: leftOffset, 99 | y: topOffset, 100 | holdDuration: 300, 101 | onFinished: function () { 102 | assert.ok( 103 | window.pageXOffset > scrollX && window.pageYOffset > scrollY, 104 | 'window should be scrolled down and right' 105 | ); 106 | 107 | // Place container to the bottom-right corner of the body and scroll 108 | // window to the max. 109 | container.style.left = 'auto'; 110 | container.style.top = 'auto'; 111 | container.style.right = '0px'; 112 | container.style.bottom = '0px'; 113 | window.scrollTo(100000, 100000); 114 | scrollX = window.pageXOffset; 115 | scrollY = window.pageYOffset; 116 | 117 | // Compute how much we need to drag the item to left and top so that 118 | // auto-scroll will be triggered. 119 | item = grid.getItems()[1]; 120 | itemRect = item.getElement().getBoundingClientRect(); 121 | leftOffset = -itemRect.left; 122 | topOffset = -itemRect.top; 123 | 124 | // Drag up left. 125 | utils.dragElement({ 126 | element: item.getElement(), 127 | x: leftOffset, 128 | y: topOffset, 129 | holdDuration: 300, 130 | onFinished: function () { 131 | assert.ok( 132 | window.pageXOffset < scrollX && window.pageYOffset < scrollY, 133 | 'window should be scrolled up and left' 134 | ); 135 | teardown(); 136 | }, 137 | }); 138 | }, 139 | }); 140 | }); 141 | })(this); 142 | -------------------------------------------------------------------------------- /tests/grid-options/dragAxis.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid options'); 5 | 6 | QUnit.test('dragAxis: should allow dragging items on x and y axis by default', function (assert) { 7 | assert.expect(2); 8 | 9 | var done = assert.async(); 10 | var container = utils.createGridElements({ 11 | containerStyles: { 12 | position: 'relative', 13 | width: '70px', 14 | }, 15 | }); 16 | var grid = new Muuri(container, { 17 | dragEnabled: true, 18 | }); 19 | var item = grid.getItems()[0]; 20 | var teardown = function () { 21 | grid.destroy(); 22 | container.parentNode.removeChild(container); 23 | done(); 24 | }; 25 | var left = parseInt(item.getElement().getBoundingClientRect().left); 26 | var top = parseInt(item.getElement().getBoundingClientRect().top); 27 | 28 | grid.on('dragEnd', function () { 29 | var newLeft = parseInt(item.getElement().getBoundingClientRect().left); 30 | var newTop = parseInt(item.getElement().getBoundingClientRect().top); 31 | assert.strictEqual(newLeft, left + 70, 'left'); 32 | assert.strictEqual(newTop, top + 70, 'top'); 33 | }); 34 | 35 | utils.dragElement({ 36 | element: item.getElement(), 37 | x: 70, 38 | y: 70, 39 | onFinished: teardown, 40 | }); 41 | }); 42 | 43 | QUnit.test( 44 | 'dragAxis: when set to "xy" items should be moved on x-axis and y-axis', 45 | function (assert) { 46 | assert.expect(2); 47 | 48 | var done = assert.async(); 49 | var container = utils.createGridElements({ 50 | containerStyles: { 51 | position: 'relative', 52 | width: '70px', 53 | }, 54 | }); 55 | var grid = new Muuri(container, { 56 | dragEnabled: true, 57 | dragAxis: 'xy', 58 | }); 59 | var item = grid.getItems()[0]; 60 | var teardown = function () { 61 | grid.destroy(); 62 | container.parentNode.removeChild(container); 63 | done(); 64 | }; 65 | var left = parseInt(item.getElement().getBoundingClientRect().left); 66 | var top = parseInt(item.getElement().getBoundingClientRect().top); 67 | 68 | grid.on('dragEnd', function () { 69 | var newLeft = parseInt(item.getElement().getBoundingClientRect().left); 70 | var newTop = parseInt(item.getElement().getBoundingClientRect().top); 71 | assert.strictEqual(newLeft, left + 70, 'left'); 72 | assert.strictEqual(newTop, top + 70, 'top'); 73 | }); 74 | 75 | utils.dragElement({ 76 | element: item.getElement(), 77 | x: 70, 78 | y: 70, 79 | onFinished: teardown, 80 | }); 81 | } 82 | ); 83 | 84 | QUnit.test('dragAxis: when set to "x" items should be only moved on x-axis', function (assert) { 85 | assert.expect(2); 86 | 87 | var done = assert.async(); 88 | var container = utils.createGridElements({ 89 | containerStyles: { 90 | position: 'relative', 91 | width: '70px', 92 | }, 93 | }); 94 | var grid = new Muuri(container, { 95 | dragEnabled: true, 96 | dragAxis: 'x', 97 | }); 98 | var item = grid.getItems()[0]; 99 | var teardown = function () { 100 | grid.destroy(); 101 | container.parentNode.removeChild(container); 102 | done(); 103 | }; 104 | var left = parseInt(item.getElement().getBoundingClientRect().left); 105 | var top = parseInt(item.getElement().getBoundingClientRect().top); 106 | 107 | grid.on('dragEnd', function () { 108 | var newLeft = parseInt(item.getElement().getBoundingClientRect().left); 109 | var newTop = parseInt(item.getElement().getBoundingClientRect().top); 110 | assert.strictEqual(newLeft, left + 70, 'left'); 111 | assert.strictEqual(newTop, top, 'top'); 112 | }); 113 | 114 | utils.dragElement({ 115 | element: item.getElement(), 116 | x: 70, 117 | y: 70, 118 | onFinished: teardown, 119 | }); 120 | }); 121 | 122 | QUnit.test('dragAxis: when set to "y" items should be only moved on y-axis', function (assert) { 123 | assert.expect(2); 124 | 125 | var done = assert.async(); 126 | var container = utils.createGridElements({ 127 | containerStyles: { 128 | position: 'relative', 129 | width: '70px', 130 | }, 131 | }); 132 | var grid = new Muuri(container, { 133 | dragEnabled: true, 134 | dragAxis: 'y', 135 | }); 136 | var item = grid.getItems()[0]; 137 | var teardown = function () { 138 | grid.destroy(); 139 | container.parentNode.removeChild(container); 140 | done(); 141 | }; 142 | var left = parseInt(item.getElement().getBoundingClientRect().left); 143 | var top = parseInt(item.getElement().getBoundingClientRect().top); 144 | 145 | grid.on('dragEnd', function () { 146 | var newLeft = parseInt(item.getElement().getBoundingClientRect().left); 147 | var newTop = parseInt(item.getElement().getBoundingClientRect().top); 148 | assert.strictEqual(newLeft, left, 'left'); 149 | assert.strictEqual(newTop, top + 70, 'top'); 150 | }); 151 | 152 | utils.dragElement({ 153 | element: item.getElement(), 154 | x: 70, 155 | y: 70, 156 | onFinished: teardown, 157 | }); 158 | }); 159 | })(this); 160 | -------------------------------------------------------------------------------- /tests/grid-options/dragContainer.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid options'); 5 | 6 | QUnit.test('dragContainer: should be the grid container by default', function (assert) { 7 | assert.expect(1); 8 | 9 | var done = assert.async(); 10 | var container = utils.createGridElements(); 11 | var grid = new Muuri(container, { 12 | dragEnabled: true, 13 | }); 14 | var item = grid.getItems()[0]; 15 | var teardown = function () { 16 | grid.destroy(); 17 | container.parentNode.removeChild(container); 18 | done(); 19 | }; 20 | 21 | grid.on('dragStart', function () { 22 | assert.strictEqual(item.getElement().parentNode, container); 23 | }); 24 | 25 | utils.dragElement({ 26 | element: item.getElement(), 27 | x: 100, 28 | y: 100, 29 | onFinished: teardown, 30 | }); 31 | }); 32 | 33 | QUnit.test( 34 | 'dragContainer: should define the element the dragged item is appended to during drag', 35 | function (assert) { 36 | assert.expect(1); 37 | 38 | var done = assert.async(); 39 | var container = utils.createGridElements(); 40 | var grid = new Muuri(container, { 41 | dragEnabled: true, 42 | dragContainer: document.body, 43 | }); 44 | var item = grid.getItems()[0]; 45 | var teardown = function () { 46 | grid.destroy(); 47 | container.parentNode.removeChild(container); 48 | done(); 49 | }; 50 | 51 | grid.on('dragStart', function () { 52 | assert.strictEqual(item.getElement().parentNode, document.body); 53 | }); 54 | 55 | utils.dragElement({ 56 | element: item.getElement(), 57 | x: 100, 58 | y: 100, 59 | onFinished: teardown, 60 | }); 61 | } 62 | ); 63 | })(this); 64 | -------------------------------------------------------------------------------- /tests/grid-options/dragEnabled.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid options'); 5 | 6 | QUnit.test('dragEnabled: drag should be disabled by default', function (assert) { 7 | assert.expect(0); 8 | 9 | var done = assert.async(); 10 | var container = utils.createGridElements(); 11 | var grid = new Muuri(container); 12 | var item = grid.getItems()[0]; 13 | var teardown = function () { 14 | grid.destroy(); 15 | container.parentNode.removeChild(container); 16 | done(); 17 | }; 18 | 19 | grid.on('dragStart', function () { 20 | assert.strictEqual(true, false, 'drag should not be started'); 21 | }); 22 | 23 | utils.dragElement({ 24 | element: item.getElement(), 25 | x: 100, 26 | y: 100, 27 | onFinished: teardown, 28 | }); 29 | }); 30 | 31 | QUnit.test('dragEnabled: drag is enabled when provided true is provided', function (assert) { 32 | assert.expect(1); 33 | 34 | var done = assert.async(); 35 | var container = utils.createGridElements(); 36 | var grid = new Muuri(container, { 37 | dragEnabled: true, 38 | }); 39 | var item = grid.getItems()[0]; 40 | var teardown = function () { 41 | grid.destroy(); 42 | container.parentNode.removeChild(container); 43 | done(); 44 | }; 45 | 46 | grid.on('dragStart', function () { 47 | assert.strictEqual(true, true, 'drag should be started'); 48 | }); 49 | 50 | utils.dragElement({ 51 | element: item.getElement(), 52 | x: 100, 53 | y: 100, 54 | onFinished: teardown, 55 | }); 56 | }); 57 | })(this); 58 | -------------------------------------------------------------------------------- /tests/grid-options/hideDuration.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid options'); 5 | 6 | QUnit.test('hideDuration: should disable hide animation when set to 0', function (assert) { 7 | assert.expect(2); 8 | 9 | var container = utils.createGridElements(); 10 | var grid = new Muuri(container, { 11 | hideDuration: 0, 12 | }); 13 | var item = grid.getItems()[0]; 14 | var teardown = function () { 15 | grid.destroy(); 16 | container.parentNode.removeChild(container); 17 | }; 18 | 19 | grid.hide([item]); 20 | assert.strictEqual(item.isVisible(), false, 'item should be hidden'); 21 | assert.strictEqual(item.isHiding(), false, 'item should not be hiding'); 22 | teardown(); 23 | }); 24 | })(this); 25 | -------------------------------------------------------------------------------- /tests/grid-options/itemClass.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid options'); 5 | 6 | QUnit.test('itemClass: should define the classname for the item elements', function (assert) { 7 | assert.expect(1); 8 | 9 | var container = utils.createGridElements(); 10 | var grid = new Muuri(container, { 11 | itemClass: 'foo', 12 | }); 13 | var teardown = function () { 14 | grid.destroy(); 15 | container.parentNode.removeChild(container); 16 | }; 17 | 18 | assert.strictEqual(utils.matches(grid.getItems()[0].getElement(), '.foo'), true); 19 | teardown(); 20 | }); 21 | })(this); 22 | -------------------------------------------------------------------------------- /tests/grid-options/itemDraggingClass.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid options'); 5 | 6 | QUnit.test('itemDraggingClass: should define the classname for dragged item elements', function ( 7 | assert 8 | ) { 9 | assert.expect(3); 10 | 11 | var done = assert.async(); 12 | var container = utils.createGridElements({ itemCount: 3 }); 13 | var grid = new Muuri(container, { 14 | itemDraggingClass: 'foo', 15 | dragEnabled: true, 16 | }); 17 | var item = grid.getItems()[0]; 18 | var teardown = function () { 19 | grid.destroy(); 20 | container.parentNode.removeChild(container); 21 | done(); 22 | }; 23 | 24 | assert.strictEqual( 25 | utils.matches(item.getElement(), '.foo'), 26 | false, 27 | 'the classname should not be applied before dragging starts' 28 | ); 29 | 30 | grid.on('dragStart', function () { 31 | assert.strictEqual( 32 | utils.matches(item.getElement(), '.foo'), 33 | true, 34 | 'the classname should be applied when dragging starts' 35 | ); 36 | }); 37 | 38 | grid.on('dragEnd', function () { 39 | assert.strictEqual( 40 | utils.matches(item.getElement(), '.foo'), 41 | false, 42 | 'the classname should be removed when dragging ends' 43 | ); 44 | }); 45 | 46 | utils.dragElement({ 47 | element: item.getElement(), 48 | x: 100, 49 | y: 100, 50 | onFinished: teardown, 51 | }); 52 | }); 53 | })(this); 54 | -------------------------------------------------------------------------------- /tests/grid-options/itemHiddenClass.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid options'); 5 | 6 | QUnit.test('itemHiddenClass: should define the classname for hidden item elements', function ( 7 | assert 8 | ) { 9 | assert.expect(2); 10 | 11 | var container = utils.createGridElements(); 12 | var grid = new Muuri(container, { 13 | itemHiddenClass: 'foo', 14 | }); 15 | var teardown = function () { 16 | grid.destroy(); 17 | container.parentNode.removeChild(container); 18 | }; 19 | 20 | grid.hide(grid.getItems(0)); 21 | assert.strictEqual( 22 | utils.matches(grid.getItems()[0].getElement(), '.foo'), 23 | true, 24 | 'hidden items should have the classname' 25 | ); 26 | assert.strictEqual( 27 | utils.matches(grid.getItems()[1].getElement(), '.foo'), 28 | false, 29 | 'visible items should not have the classname' 30 | ); 31 | teardown(); 32 | }); 33 | })(this); 34 | -------------------------------------------------------------------------------- /tests/grid-options/itemPositioningClass.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid options'); 5 | 6 | QUnit.test( 7 | 'itemPositioningClass: should define the classname for positioning item elements', 8 | function (assert) { 9 | assert.expect(3); 10 | 11 | var done = assert.async(); 12 | var container = utils.createGridElements({ itemCount: 3 }); 13 | var grid = new Muuri(container, { 14 | itemPositioningClass: 'foo', 15 | }); 16 | var teardown = function () { 17 | grid.destroy(); 18 | container.parentNode.removeChild(container); 19 | done(); 20 | }; 21 | 22 | grid.move(0, -1, { action: 'swap' }); 23 | utils.raf(function () { 24 | assert.strictEqual( 25 | utils.matches(grid.getItems()[0].getElement(), '.foo'), 26 | true, 27 | 'first item should be positioning' 28 | ); 29 | assert.strictEqual( 30 | utils.matches(grid.getItems()[2].getElement(), '.foo'), 31 | true, 32 | 'last item should be positioning' 33 | ); 34 | assert.strictEqual( 35 | utils.matches(grid.getItems()[1].getElement(), '.foo'), 36 | false, 37 | 'second item should not be positioning' 38 | ); 39 | teardown(); 40 | }); 41 | } 42 | ); 43 | })(this); 44 | -------------------------------------------------------------------------------- /tests/grid-options/itemReleasingClass.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid options'); 5 | 6 | QUnit.test( 7 | 'itemReleasingClass: should define the classname for released item elements', 8 | function (assert) { 9 | assert.expect(3); 10 | 11 | var done = assert.async(); 12 | var container = utils.createGridElements({ itemCount: 3 }); 13 | var grid = new Muuri(container, { 14 | itemReleasingClass: 'foo', 15 | dragEnabled: true, 16 | }); 17 | var item = grid.getItems()[0]; 18 | var teardown = function () { 19 | grid.destroy(); 20 | container.parentNode.removeChild(container); 21 | done(); 22 | }; 23 | 24 | assert.strictEqual( 25 | utils.matches(item.getElement(), '.foo'), 26 | false, 27 | 'the classname should not be applied before release starts' 28 | ); 29 | 30 | grid.on('dragReleaseStart', function () { 31 | assert.strictEqual( 32 | utils.matches(item.getElement(), '.foo'), 33 | true, 34 | 'the classname should be applied when release starts' 35 | ); 36 | }); 37 | 38 | grid.on('dragReleaseEnd', function () { 39 | assert.strictEqual( 40 | utils.matches(item.getElement(), '.foo'), 41 | false, 42 | 'the classname should be removed when release ends' 43 | ); 44 | teardown(); 45 | }); 46 | 47 | utils.dragElement({ 48 | element: item.getElement(), 49 | x: 100, 50 | y: 100, 51 | }); 52 | } 53 | ); 54 | })(this); 55 | -------------------------------------------------------------------------------- /tests/grid-options/itemVisibleClass.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid options'); 5 | 6 | QUnit.test('itemVisibleClass: should define the classname for visible item elements', function ( 7 | assert 8 | ) { 9 | assert.expect(2); 10 | 11 | var container = utils.createGridElements(); 12 | var grid = new Muuri(container, { 13 | itemVisibleClass: 'foo', 14 | }); 15 | var teardown = function () { 16 | grid.destroy(); 17 | container.parentNode.removeChild(container); 18 | }; 19 | 20 | grid.hide(grid.getItems(0)); 21 | assert.strictEqual( 22 | utils.matches(grid.getItems()[0].getElement(), '.foo'), 23 | false, 24 | 'hidden items should not have the classname' 25 | ); 26 | assert.strictEqual( 27 | utils.matches(grid.getItems()[1].getElement(), '.foo'), 28 | true, 29 | 'visible items should have the classname' 30 | ); 31 | teardown(); 32 | }); 33 | })(this); 34 | -------------------------------------------------------------------------------- /tests/grid-options/items.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid options'); 5 | 6 | QUnit.test('items: should fetch all container`s child elements by default', function (assert) { 7 | assert.expect(1); 8 | 9 | var container = utils.createGridElements(); 10 | var grid = new Muuri(container); 11 | var teardown = function () { 12 | grid.destroy(); 13 | container.parentNode.removeChild(container); 14 | }; 15 | 16 | assert.deepEqual( 17 | grid.getItems().map(function (item) { 18 | return item.getElement(); 19 | }), 20 | [].slice.call(container.children) 21 | ); 22 | teardown(); 23 | }); 24 | 25 | QUnit.test( 26 | 'items: should fetch all container`s child elements that match the provided selector', 27 | function (assert) { 28 | assert.expect(1); 29 | 30 | var container = utils.createGridElements(); 31 | var children = [].slice.call(container.children); 32 | var targets = [0, 1, 2].map(function (i) { 33 | children[i].classList.add('foo'); 34 | return children[i]; 35 | }); 36 | container.classList.add('foo'); 37 | var grid = new Muuri(container, { 38 | items: '.foo', 39 | }); 40 | var teardown = function () { 41 | grid.destroy(); 42 | container.parentNode.removeChild(container); 43 | }; 44 | 45 | assert.deepEqual( 46 | grid.getItems().map(function (item) { 47 | return item.getElement(); 48 | }), 49 | targets 50 | ); 51 | teardown(); 52 | } 53 | ); 54 | 55 | QUnit.test('items: should accept a node list', function (assert) { 56 | assert.expect(1); 57 | 58 | var container = utils.createGridElements(); 59 | var children = [].slice.call(container.children); 60 | var targets = [0, 1, 2].map(function (i) { 61 | children[i].classList.add('foo'); 62 | return children[i]; 63 | }); 64 | var grid = new Muuri(container, { 65 | items: document.querySelectorAll('.foo'), 66 | }); 67 | var teardown = function () { 68 | grid.destroy(); 69 | container.parentNode.removeChild(container); 70 | }; 71 | 72 | assert.deepEqual( 73 | grid.getItems().map(function (item) { 74 | return item.getElement(); 75 | }), 76 | targets 77 | ); 78 | teardown(); 79 | }); 80 | 81 | QUnit.test('items: should accept an array of elements', function (assert) { 82 | assert.expect(1); 83 | 84 | var container = utils.createGridElements(); 85 | var children = [].slice.call(container.children); 86 | var targets = [0, 1, 2].map(function (i) { 87 | return children[i]; 88 | }); 89 | var grid = new Muuri(container, { 90 | items: targets, 91 | }); 92 | var teardown = function () { 93 | grid.destroy(); 94 | container.parentNode.removeChild(container); 95 | }; 96 | 97 | assert.deepEqual( 98 | grid.getItems().map(function (item) { 99 | return item.getElement(); 100 | }), 101 | targets 102 | ); 103 | teardown(); 104 | }); 105 | })(this); 106 | -------------------------------------------------------------------------------- /tests/grid-options/showDuration.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid options'); 5 | 6 | QUnit.test('showDuration: should disable show animation when set to 0', function (assert) { 7 | assert.expect(2); 8 | 9 | var container = utils.createGridElements(); 10 | var grid = new Muuri(container, { 11 | showDuration: 0, 12 | }); 13 | var item = grid.getItems()[0]; 14 | var teardown = function () { 15 | grid.destroy(); 16 | container.parentNode.removeChild(container); 17 | }; 18 | 19 | grid.hide([item], { instant: true }).show([item]); 20 | assert.strictEqual(item.isVisible(), true, 'item should be visible'); 21 | assert.strictEqual(item.isShowing(), false, 'item should not be showing'); 22 | teardown(); 23 | }); 24 | })(this); 25 | -------------------------------------------------------------------------------- /tests/grid-options/visibleStyles-hiddenStyles.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Grid options'); 5 | 6 | QUnit.test( 7 | 'visibleStyles/hiddenStyles: should change the visible/hidden state styles of items', 8 | function (assert) { 9 | assert.expect(2); 10 | 11 | var done = assert.async(); 12 | var container = utils.createGridElements(); 13 | var grid = new Muuri(container, { 14 | visibleStyles: { 15 | fontSize: '30px', 16 | }, 17 | hiddenStyles: { 18 | fontSize: '10px', 19 | }, 20 | }); 21 | var item = grid.getItems()[0]; 22 | var teardown = function () { 23 | grid.destroy(); 24 | container.parentNode.removeChild(container); 25 | done(); 26 | }; 27 | 28 | grid.hide([item], { 29 | onFinish: function () { 30 | var child = item.getElement().children[0]; 31 | assert.strictEqual(child.style.fontSize, '10px', 'item has correct hidden styles'); 32 | grid.show([item], { 33 | onFinish: function () { 34 | assert.strictEqual(child.style.fontSize, '30px', 'item has correct visible styles'); 35 | teardown(); 36 | }, 37 | }); 38 | }, 39 | }); 40 | } 41 | ); 42 | })(this); 43 | -------------------------------------------------------------------------------- /tests/index.js: -------------------------------------------------------------------------------- 1 | QUnit.config.reorder = false; 2 | 3 | Muuri.syncPacker = new Muuri.Packer(); 4 | Muuri.asyncPacker = Muuri.defaultPacker; 5 | Muuri.defaultPacker = Muuri.syncPacker; 6 | -------------------------------------------------------------------------------- /tests/item-methods/getElement.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Item methods'); 5 | 6 | QUnit.test('getElement: should return the instance`s associated DOM element', function (assert) { 7 | assert.expect(1); 8 | 9 | var container = utils.createGridElements(); 10 | var itemElement = container.children[1]; 11 | var grid = new Muuri(container); 12 | var item = grid.getItems(itemElement)[0]; 13 | var teardown = function () { 14 | grid.destroy(); 15 | container.parentNode.removeChild(container); 16 | }; 17 | 18 | assert.strictEqual(item.getElement(), itemElement); 19 | teardown(); 20 | }); 21 | })(this); 22 | -------------------------------------------------------------------------------- /tests/item-methods/getGrid.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Item methods'); 5 | 6 | QUnit.test('getGrid: should return the instance`s associated Grid instance', function (assert) { 7 | assert.expect(1); 8 | 9 | var container = utils.createGridElements(); 10 | var grid = new Muuri(container); 11 | var item = grid.getItems()[0]; 12 | var teardown = function () { 13 | grid.destroy(); 14 | container.parentNode.removeChild(container); 15 | }; 16 | 17 | assert.strictEqual(item.getGrid(), grid); 18 | teardown(); 19 | }); 20 | })(this); 21 | -------------------------------------------------------------------------------- /tests/item-methods/getHeight.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Item methods'); 5 | 6 | QUnit.test( 7 | 'getHeight: should return the instance element`s cached height that includes paddings and borders', 8 | function (assert) { 9 | assert.expect(2); 10 | 11 | var container = utils.createGridElements(); 12 | var grid = new Muuri(container); 13 | var item = grid.getItems()[0]; 14 | var teardown = function () { 15 | grid.destroy(); 16 | container.parentNode.removeChild(container); 17 | }; 18 | 19 | assert.strictEqual( 20 | item.getHeight(), 21 | 50, 22 | 'The returned height is equal to the element`s content height + top/bottom paddings + top/bottom borders size' 23 | ); 24 | item.getElement().style.padding = '0px'; 25 | assert.strictEqual( 26 | item.getHeight(), 27 | 50, 28 | 'The returned height is the cached height and not the element`s current height in DOM' 29 | ); 30 | teardown(); 31 | } 32 | ); 33 | })(this); 34 | -------------------------------------------------------------------------------- /tests/item-methods/getMargin.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Item methods'); 5 | 6 | QUnit.test('getMargin: should return the instance element`s cached margins', function (assert) { 7 | assert.expect(2); 8 | 9 | var container = utils.createGridElements(); 10 | var grid = new Muuri(container); 11 | var item = grid.getItems()[0]; 12 | var teardown = function () { 13 | grid.destroy(); 14 | container.parentNode.removeChild(container); 15 | }; 16 | 17 | assert.deepEqual( 18 | item.getMargin(), 19 | { left: 10, right: 10, top: 10, bottom: 10 }, 20 | 'The margins should be retrieved from the DOM on init' 21 | ); 22 | item.getElement().style.margin = '0px'; 23 | assert.deepEqual( 24 | item.getMargin(), 25 | { left: 10, right: 10, top: 10, bottom: 10 }, 26 | 'The returned margins are cached and not necessarilly the element`s current margins in DOM' 27 | ); 28 | teardown(); 29 | }); 30 | })(this); 31 | -------------------------------------------------------------------------------- /tests/item-methods/getPosition.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Item methods'); 5 | 6 | QUnit.test( 7 | 'getPosition: should return the instance element`s cached position in the grid', 8 | function (assert) { 9 | assert.expect(4); 10 | 11 | var container = utils.createGridElements({ 12 | containerStyles: { 13 | position: 'relative', 14 | width: '140px', 15 | }, 16 | }); 17 | var grid = new Muuri(container); 18 | var items = grid.getItems(); 19 | var itemA = items[0]; 20 | var itemB = items[1]; 21 | var itemC = items[2]; 22 | var itemD = items[3]; 23 | var teardown = function () { 24 | grid.destroy(); 25 | container.parentNode.removeChild(container); 26 | }; 27 | 28 | assert.deepEqual(itemA.getPosition(), { left: 0, top: 0 }); 29 | assert.deepEqual(itemB.getPosition(), { left: 70, top: 0 }); 30 | assert.deepEqual(itemC.getPosition(), { left: 0, top: 70 }); 31 | assert.deepEqual(itemD.getPosition(), { left: 70, top: 70 }); 32 | teardown(); 33 | } 34 | ); 35 | })(this); 36 | -------------------------------------------------------------------------------- /tests/item-methods/getWidth.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Item methods'); 5 | 6 | QUnit.test( 7 | 'getWidth: should return the instance element`s cached width that includes paddings and borders', 8 | function (assert) { 9 | assert.expect(2); 10 | 11 | var container = utils.createGridElements(); 12 | var grid = new Muuri(container); 13 | var item = grid.getItems()[0]; 14 | var teardown = function () { 15 | grid.destroy(); 16 | container.parentNode.removeChild(container); 17 | }; 18 | 19 | assert.strictEqual( 20 | item.getWidth(), 21 | 50, 22 | 'The returned width is equal to the element`s content width + left/right paddings + left/right borders size' 23 | ); 24 | item.getElement().style.padding = '0px'; 25 | assert.strictEqual( 26 | item.getWidth(), 27 | 50, 28 | 'The returned width is the cached width and not the element`s current width in DOM' 29 | ); 30 | teardown(); 31 | } 32 | ); 33 | })(this); 34 | -------------------------------------------------------------------------------- /tests/item-methods/isActive.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Item methods'); 5 | 6 | QUnit.test('isActive: should return true if the item is active and otherwise false', function ( 7 | assert 8 | ) { 9 | assert.expect(2); 10 | 11 | var container = utils.createGridElements(); 12 | var grid = new Muuri(container); 13 | var item = grid.getItems()[0]; 14 | var teardown = function () { 15 | grid.destroy(); 16 | container.parentNode.removeChild(container); 17 | }; 18 | 19 | assert.strictEqual( 20 | item.isActive(), 21 | true, 22 | 'An item should be active when the it`s initiated and it`s display value is set to block' 23 | ); 24 | grid.hide([item]); 25 | assert.strictEqual(item.isActive(), false, 'An item should not be active after hide is called'); 26 | teardown(); 27 | }); 28 | })(this); 29 | -------------------------------------------------------------------------------- /tests/item-methods/isDestroyed.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Item methods'); 5 | 6 | QUnit.test( 7 | 'isDestroyed: should return true if the item is destroyed and otherwise false', 8 | function (assert) { 9 | assert.expect(2); 10 | 11 | var container = utils.createGridElements(); 12 | var grid = new Muuri(container); 13 | var item = grid.getItems()[0]; 14 | var teardown = function () { 15 | grid.destroy(); 16 | container.parentNode.removeChild(container); 17 | }; 18 | 19 | assert.strictEqual( 20 | item.isDestroyed(), 21 | false, 22 | 'An item should not be destroyed before it is removed from the grid' 23 | ); 24 | grid.remove([item]); 25 | assert.strictEqual( 26 | item.isDestroyed(), 27 | true, 28 | 'An item should be destroyed after it is removed from the grid' 29 | ); 30 | teardown(); 31 | } 32 | ); 33 | })(this); 34 | -------------------------------------------------------------------------------- /tests/item-methods/isDragging.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Item methods'); 5 | 6 | QUnit.test('isDragging: should return true if the item is being dragged', function (assert) { 7 | assert.expect(4); 8 | 9 | var done = assert.async(); 10 | var container = utils.createGridElements(); 11 | var grid = new Muuri(container, { dragEnabled: true }); 12 | var item = grid.getItems()[0]; 13 | var teardown = function () { 14 | grid.destroy(); 15 | container.parentNode.removeChild(container); 16 | done(); 17 | }; 18 | 19 | function onDragStart() { 20 | grid.off('dragStart', onDragStart); 21 | assert.strictEqual( 22 | item.isDragging(), 23 | true, 24 | 'An item should be in dragging state when dragging starts' 25 | ); 26 | } 27 | 28 | function onDragMove() { 29 | grid.off('dragMove', onDragMove); 30 | assert.strictEqual( 31 | item.isDragging(), 32 | true, 33 | 'An item should be in dragging state when dragging' 34 | ); 35 | } 36 | 37 | function onDragEnd() { 38 | grid.off('dragEnd', onDragEnd); 39 | assert.strictEqual( 40 | item.isDragging(), 41 | false, 42 | 'An item should not be in dragging state after dragging has ended' 43 | ); 44 | } 45 | 46 | grid.on('dragStart', onDragStart).on('dragMove', onDragMove).on('dragEnd', onDragEnd); 47 | 48 | assert.strictEqual( 49 | item.isDragging(), 50 | false, 51 | 'An item should not be in dragging state when it`s not being dragged' 52 | ); 53 | 54 | utils.dragElement({ 55 | element: item.getElement(), 56 | x: 100, 57 | y: 100, 58 | onFinished: teardown, 59 | }); 60 | }); 61 | })(this); 62 | -------------------------------------------------------------------------------- /tests/item-methods/isHiding.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Item methods'); 5 | 6 | QUnit.test( 7 | 'isHiding: should return true if the item is animating to hidden and otherwise false', 8 | function (assert) { 9 | assert.expect(4); 10 | 11 | var done = assert.async(); 12 | var container = utils.createGridElements(); 13 | var grid = new Muuri(container); 14 | var item = grid.getItems()[0]; 15 | var teardown = function () { 16 | grid.destroy(); 17 | container.parentNode.removeChild(container); 18 | done(); 19 | }; 20 | 21 | assert.strictEqual( 22 | item.isHiding(), 23 | false, 24 | 'An item should not be in hiding state when the it`s visible' 25 | ); 26 | grid.hide([item], { 27 | onFinish: function () { 28 | assert.strictEqual( 29 | item.isHiding(), 30 | false, 31 | 'An item should not be in hiding state after it has finished the hide animation' 32 | ); 33 | grid.show([item]); 34 | assert.strictEqual( 35 | item.isHiding(), 36 | false, 37 | 'An item should not be in hiding state when it`s being animated to visible' 38 | ); 39 | teardown(); 40 | }, 41 | }); 42 | assert.strictEqual( 43 | item.isHiding(), 44 | true, 45 | 'An item should be in hiding state when the it`s being animated to hidden' 46 | ); 47 | } 48 | ); 49 | })(this); 50 | -------------------------------------------------------------------------------- /tests/item-methods/isPositioning.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Item methods'); 5 | 6 | QUnit.test( 7 | 'isPositioning: should return true if the item`s position is being animated', 8 | function (assert) { 9 | assert.expect(3); 10 | 11 | var done = assert.async(); 12 | var container = utils.createGridElements(); 13 | var grid = new Muuri(container); 14 | var item = grid.getItems()[0]; 15 | var teardown = function () { 16 | grid.destroy(); 17 | container.parentNode.removeChild(container); 18 | done(); 19 | }; 20 | 21 | assert.strictEqual( 22 | item.isPositioning(), 23 | false, 24 | 'An item should not be in positioning state when it`s position is not being animated' 25 | ); 26 | 27 | grid.move(item, -1, { 28 | layout: function () { 29 | assert.strictEqual( 30 | item.isPositioning(), 31 | false, 32 | 'An item should not be in positioning state after the positioning animation is finished' 33 | ); 34 | teardown(); 35 | }, 36 | }); 37 | 38 | assert.strictEqual( 39 | item.isPositioning(), 40 | true, 41 | 'An item should be in positioning state when it`s position is being animated' 42 | ); 43 | } 44 | ); 45 | })(this); 46 | -------------------------------------------------------------------------------- /tests/item-methods/isReleasing.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Item methods'); 5 | 6 | QUnit.test('isReleasing: should return true if the item is being released', function (assert) { 7 | assert.expect(6); 8 | 9 | var done = assert.async(); 10 | var container = utils.createGridElements(); 11 | var grid = new Muuri(container, { dragEnabled: true }); 12 | var item = grid.getItems()[0]; 13 | var teardown = function () { 14 | grid.destroy(); 15 | container.parentNode.removeChild(container); 16 | done(); 17 | }; 18 | 19 | function onDragStart() { 20 | grid.off('dragStart', onDragStart); 21 | assert.strictEqual( 22 | item.isReleasing(), 23 | false, 24 | 'An item should not be in releasing state when dragging starts' 25 | ); 26 | } 27 | 28 | function onDragMove() { 29 | grid.off('dragMove', onDragMove); 30 | assert.strictEqual( 31 | item.isReleasing(), 32 | false, 33 | 'An item should not be in releasing state when dragging' 34 | ); 35 | } 36 | 37 | function onDragEnd() { 38 | grid.off('dragEnd', onDragEnd); 39 | assert.strictEqual( 40 | item.isReleasing(), 41 | false, 42 | 'An item should not be in releasing state when drag ends' 43 | ); 44 | } 45 | 46 | function onDragReleaseStart() { 47 | grid.off('dragReleaseStart', onDragReleaseStart); 48 | assert.strictEqual( 49 | item.isReleasing(), 50 | true, 51 | 'An item should be in releasing state right after it has been released' 52 | ); 53 | } 54 | 55 | function onDragReleaseEnd() { 56 | grid.off('dragReleaseEnd', onDragReleaseEnd); 57 | assert.strictEqual( 58 | item.isReleasing(), 59 | false, 60 | 'An item should not be in releasing state right after releasing has ended' 61 | ); 62 | teardown(); 63 | } 64 | 65 | grid 66 | .on('dragStart', onDragStart) 67 | .on('dragMove', onDragMove) 68 | .on('dragEnd', onDragEnd) 69 | .on('dragReleaseStart', onDragReleaseStart) 70 | .on('dragReleaseEnd', onDragReleaseEnd); 71 | 72 | assert.strictEqual( 73 | item.isReleasing(), 74 | false, 75 | 'An item should not be in releasing state when it`s not being released' 76 | ); 77 | 78 | utils.dragElement({ 79 | element: item.getElement(), 80 | x: 100, 81 | y: 100, 82 | }); 83 | }); 84 | })(this); 85 | -------------------------------------------------------------------------------- /tests/item-methods/isShowing.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Item methods'); 5 | 6 | QUnit.test( 7 | 'isShowing: should return true if the item is animating to visible and otherwise false', 8 | function (assert) { 9 | assert.expect(5); 10 | 11 | var done = assert.async(); 12 | var container = utils.createGridElements(); 13 | var grid = new Muuri(container); 14 | var item = grid.getItems()[0]; 15 | var teardown = function () { 16 | grid.destroy(); 17 | container.parentNode.removeChild(container); 18 | done(); 19 | }; 20 | 21 | assert.strictEqual( 22 | item.isShowing(), 23 | false, 24 | 'An item should not be in showing state when the it`s visible' 25 | ); 26 | grid.hide([item], { 27 | onFinish: function () { 28 | assert.strictEqual( 29 | item.isShowing(), 30 | false, 31 | 'An item should not be in showing state when the it`s hidden' 32 | ); 33 | grid.show([item], { 34 | onFinish: function () { 35 | assert.strictEqual( 36 | item.isShowing(), 37 | false, 38 | 'An item should not be in showing state after it has finished the show animation' 39 | ); 40 | teardown(); 41 | }, 42 | }); 43 | assert.strictEqual( 44 | item.isShowing(), 45 | true, 46 | 'An item should be in showing state when it`s being animated to visible' 47 | ); 48 | }, 49 | }); 50 | assert.strictEqual( 51 | item.isShowing(), 52 | false, 53 | 'An item should not be in showing state when the it`s being animated to hidden' 54 | ); 55 | } 56 | ); 57 | })(this); 58 | -------------------------------------------------------------------------------- /tests/item-methods/isVisible.js: -------------------------------------------------------------------------------- 1 | (function (window) { 2 | var Muuri = window.Muuri; 3 | 4 | QUnit.module('Item methods'); 5 | 6 | QUnit.test('isVisible: should return true if the item is visible and otherwise false', function ( 7 | assert 8 | ) { 9 | assert.expect(2); 10 | 11 | var container = utils.createGridElements(); 12 | var grid = new Muuri(container); 13 | var item = grid.getItems()[0]; 14 | var teardown = function () { 15 | grid.destroy(); 16 | container.parentNode.removeChild(container); 17 | }; 18 | 19 | assert.strictEqual( 20 | item.isVisible(), 21 | true, 22 | 'An item should be visible when the it`s initiated and it`s display value is set to block' 23 | ); 24 | grid.hide([item]); 25 | assert.strictEqual( 26 | item.isVisible(), 27 | false, 28 | 'An item should not be visible after hide is called' 29 | ); 30 | teardown(); 31 | }); 32 | })(this); 33 | --------------------------------------------------------------------------------