├── .babelrc ├── .codeclimate.yml ├── .conventional-changelog-lintrc ├── .coveralls.yml ├── .csslintrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .gulprc ├── .npmrc ├── .stylelintrc ├── .travis.yml ├── .zuul.yml ├── browserlist ├── contributing.md ├── documentation ├── api.md └── roadmap.md ├── examples ├── cdn.md └── readme.md ├── gulpfile.js ├── license.md ├── package.json ├── public ├── contributing.html ├── documentation │ ├── api.html │ └── roadmap.html ├── examples │ ├── cdn.html │ ├── cdn.tpl │ ├── index.html │ └── readme.tpl ├── index.html ├── license.html └── static │ ├── index.css │ ├── index.js │ ├── jogwheel.png │ └── jogwheel.svg ├── readme.md ├── source ├── .eslintrc ├── _readme.tpl ├── contributing.tpl ├── documentation │ ├── api.tpl │ ├── roadmap.tpl │ └── static │ │ ├── index.css │ │ ├── index.js │ │ ├── jogwheel.png │ │ └── jogwheel.svg ├── examples │ ├── cdn.tpl │ └── readme.tpl ├── library │ ├── convert-animation-duration.js │ ├── convert-animation-iterations.js │ ├── create-trap.js │ ├── cssrule-enumerations.js │ ├── get-animation-properties.js │ ├── get-css-rules.js │ ├── get-declarations.js │ ├── get-defined-styles.js │ ├── get-keyframe-declarations.js │ ├── get-keyframes.js │ ├── get-mediaquery-media.js │ ├── get-player.js │ ├── get-vendor-prefix.js │ ├── index.js │ ├── init-player.js │ ├── parse-keyframe-key.js │ ├── remove-vendor-prefix.js │ ├── to-array.js │ └── transform-keyframe-declaration.js ├── license.tpl ├── scripts │ ├── pages-update.js │ ├── release-pull-request.js │ └── when-ci.js └── test │ ├── integration │ ├── .eslintrc │ ├── index.html │ ├── index.js │ ├── iteration-count │ │ ├── index.css │ │ ├── index.html │ │ └── index.js │ ├── keyword │ │ ├── index.css │ │ ├── index.html │ │ └── index.js │ ├── node-list │ │ ├── index.css │ │ ├── index.html │ │ └── index.js │ └── simple │ │ ├── index.css │ │ ├── index.html │ │ └── index.js │ └── unit │ ├── convert-animation-iterations.js │ ├── create-trap.js │ ├── fixtures │ ├── cross-domain-stylesheets.js │ ├── cross-domain-unsafe-stylesheets.js │ ├── filled-animation.js │ ├── keyword-animation-declaration.js │ ├── paused-animation.js │ ├── prefixes.js │ ├── running-animation.js │ ├── simple-animation-declaration.js │ └── slow-animation.js │ ├── get-animation-properties.js │ ├── get-css-rules.js │ ├── get-defined-styles.js │ ├── get-keyframe-declarations.js │ ├── get-keyframes.js │ ├── get-player.js │ ├── get-vendor-prefix.js │ ├── index.js │ ├── init-player.js │ ├── jogwheel-constructor.js │ ├── jogwheel-create.js │ ├── jogwheel-instance-pause.js │ ├── jogwheel-instance-play.js │ ├── jogwheel-instance-seek.js │ ├── jogwheel-instance-unplug.js │ ├── remove-vendor-prefix.js │ └── stubs │ ├── cross-domain-document.js │ ├── css-style-rules.js │ ├── document.js │ ├── element.js │ ├── keyword-document.js │ ├── node-list.js │ ├── player.js │ └── window.js └── tasks ├── .eslintrc ├── build.js ├── clean.js ├── copy-example.js ├── copy.js ├── css.js ├── documentation.js ├── helpers ├── on-error.js └── task.js ├── html.js ├── lint.js ├── list.js ├── pack.js ├── partials ├── badges.js ├── footer.js ├── header.js └── page-layout.js ├── static.js ├── test-css.js ├── test.js ├── transpile.js └── watch.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "stage": 0, 3 | "optional": ["regenerator"], 4 | "auxiliaryCommentBefore": "istanbul ignore next", 5 | "plugins": ["object-assign"], 6 | "sourceMaps": "inline" 7 | } 8 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | eslint: 4 | enabled: true 5 | ratings: 6 | paths: 7 | - "source/**/*.js" 8 | exclude_paths: 9 | - node_modules/**/* 10 | - public/**/* 11 | -------------------------------------------------------------------------------- /.conventional-changelog-lintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["angular"], 3 | "rules": { 4 | "header-max-length": [2, "always", 100] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service-name: travis-ci 2 | -------------------------------------------------------------------------------- /.csslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "adjoining-classes": false, 3 | "box-model": false, 4 | "box-sizing": false, 5 | "bulletproof-font-face": false, 6 | "compatible-vendor-prefixes": false, 7 | "display-property-grouping": false, 8 | "duplicate-background-images": false, 9 | "duplicate-properties": false, 10 | "empty-rules": false, 11 | "fallback-colors": false, 12 | "floats": false, 13 | "font-faces": false, 14 | "font-sizes": false, 15 | "gradients": false, 16 | "ids": false, 17 | "import": false, 18 | "important": false, 19 | "known-properties": false, 20 | "non-link-hover": false, 21 | "outline-none": false, 22 | "overqualified-elements": false, 23 | "qualified-headings": false, 24 | "regex-selectors": false, 25 | "shorthand": false, 26 | "star-property-hack": false, 27 | "text-indent": false, 28 | "underscore-property-hack": false, 29 | "vendor-prefix": false, 30 | "unique-headings": false, 31 | "universal-selector": false, 32 | "unqualified-attributes": false, 33 | "zero-units": false 34 | } 35 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | end_of_line = lf 3 | insert_final_newline = true 4 | trim_trailing_whitespace = true 5 | indent_style = tab 6 | 7 | [{.*rc,*.yml,*.md,package.json,*.svg}] 8 | indent_style = space 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "xo/esnext" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | *.swp 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | npm-debug.log 30 | 31 | # Build output 32 | distribution 33 | 34 | # Coverage 35 | .nyc_output 36 | 37 | # Sensitive config files 38 | .zuulrc 39 | 40 | # Temp files 41 | .tmp 42 | -------------------------------------------------------------------------------- /.gulprc: -------------------------------------------------------------------------------- 1 | { 2 | "paths":{ 3 | // sources 4 | "source":{ 5 | "entry": "source/library/index.js", 6 | "library": "source/library/**/*.js", 7 | "test": "source/test/**/*.js", 8 | "scripts": "source/scripts/**/*.js", 9 | "documentation": "source/**/*.tpl", 10 | "static": "source/**/*.html", 11 | "example": "source/examples/**/*", 12 | "public-static": "source/documentation/**/*.{png,svg}", 13 | "markdown": [ 14 | "**/*.md", 15 | "!node_modules/**/*", 16 | "!distribution/**/*" 17 | ], 18 | "test-css": "source/test/**/*.css", 19 | "public-css": "source/documentation/**/*.css", 20 | "public-js": "source/documentation/static/index.js" 21 | }, 22 | // clean targets 23 | "clean":{ 24 | "documentation": "*.md", 25 | "distribution": "distribution" 26 | }, 27 | // targets 28 | "target":{ 29 | "root": ".", 30 | "distribution": "distribution", 31 | "library": "distribution/library/", 32 | "test": "distribution/test/", 33 | "scripts": "distribution/scripts/", 34 | "public": "public", 35 | "example": "public/examples/" 36 | }, 37 | // executables 38 | "executable":{ 39 | "unit": "distribution/test/unit/index.js", 40 | "pages": "distribution/scripts/pages-update.js" 41 | }, 42 | // exclude from watch 43 | "exclude": { 44 | "markdown": "**/*.md" 45 | } 46 | }, 47 | "tasks": { 48 | "directory": "tasks", 49 | "public": [ 50 | "clean", 51 | "transpile", 52 | "test", 53 | "documentation", 54 | "lint", 55 | "build", 56 | "html", 57 | ["watch", ["default"]], 58 | ["list", ["help"]] 59 | ] 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | spin = false 2 | save-exact = true 3 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard", 3 | "rules": { 4 | "indentation": "tab" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | node_js: 7 | - 6 8 | before_script: 9 | - npm prune 10 | - 'curl -Lo travis_after_all.py https://git.io/vLSON' 11 | - npm run build 12 | - npm run commitmsg 13 | after_success: 14 | - python travis_after_all.py 15 | - export $(cat .to_export_back) 16 | - npm run coveralls 17 | - npm run semantic-release 18 | branches: 19 | except: 20 | - "/^v\\d+\\.\\d+\\.\\d+$/" 21 | env: 22 | global: 23 | - secure: lO1vDnxOltuOcUFQPcCokqiX6ih3X33s9CwYDKtwfMKKYBvTNuDa2UVa52eDwJdVpkYn2jGPBrOOJ4mJhjbYuw0/w/4SMyI+wdfmE2q9yC3rTVtSHkkKdqGn0aATyyPSbfJSkGBLF6+izqn00x3t9YfO6Aw9OhDljx30Qrb7UEYUV09YjchesygleBdSsyTv5zQZ1fROrhJEQu+jj+vRFcUeCjVYUA5yvRA6omCk0kw51cSSKTuzeXH0z6VnOP38ZJ2kPNpP0veof76jK5ZQ9glvGyFgj+ye4dbCZyP3b7rIlSRcfVj6S1gKTBq7Shuc1mQXN2it3NIpDSlTdZa4fCR153P8rnOpHTF6ZTsOiMB3Z3RY3Q7suPUFHPnlVbqyKIhcq15LDG0hm649VRIOGYnmYiI7yS0F9gilpGp1+eDYP5bbbst60dge/vWAXQGpXB2oN3YbrRtP8VQhjUTmWpXS/kp7nQY/1kVKRvPAaE5FjOWcyrvPDOaTcWiV3xtxQ6fFDEYiLnXUkKIG3Bp3XX7bScvo9bVAH1pw1rE70OLOie0cQGBOeQlhRnNHs/L3dAP2z6jz3xPZsHaG+Wv4SE+yg/TFaORxIgS63Zlp1xkv51G0sS1qvKo3H22FlQSzzEBAFFSSo5e/n8Pc2j4vPXEP5EjCJFCTqOTOSaG544g= 24 | - secure: N2nV0cGAk7txOAaaCIWlgs/0H7ciLyM+5auta+Kh8ZntHIlblTHv+3MHC0fGxjEUwbhLgDVNRyl2yuX5eWG2KumBEpl8A3cFRM/EkyhKjxTV7+WEh6lMzMO5t5NAhwi44dXKPxVOMS+ZZUw7b5xzmi42stLxwyL5VEMEv9VXAUzIvYV4uEtSRjQBRXKz3pIsxfETxTtkDIqe0yf20YGs8wGTU1TpuuZJWmjz4cMi/60dz/X6JRC5KQvpIVc99cHOajM+a0Ci6xi3iQ0wOnk8C0C9IqHHlsncy1lVL97mBkZJrywW8jtzLzAHocMmjjNj5hJN9M/yFUAzo+KXuuUKtW79Z+G7Pr5PIeiQget1PWrj1h2EyBec1hTN4gskB1Qc0gawNBa6fXOI0KARS52h6OZCxOqLsOPYup9xjLwsg0sbJI4Ngkbh1YgOPi1WfHmuSMH1j2GJFqq6acSZ7nq2VuzZ0Oyn4POD/w/c4eCZWpFHTI+vCrFyraf86IIsIpV92JQNqcSIWbsTCVRlJ4GSZoCK1tnTMKxj6dyM2oj2jhDCq+UIirqnGSwiJrj/ZbgIpIhQ0fYyHd4/lLPGZgP1WNndgOfhCE3M1etjDEFNPZ0I9KU/bSACgNuRKQACMHPWtad/VMsrlH2RgxshUuner85tyMOHZGvO7X5HmOPWqbw= 25 | notifications: 26 | email: false 27 | webhooks: 28 | urls: 29 | - https://webhooks.gitter.im/e/429941b00e1a3874f9a9 30 | on_success: always 31 | on_failure: always 32 | on_start: never 33 | -------------------------------------------------------------------------------- /.zuul.yml: -------------------------------------------------------------------------------- 1 | ui: tape 2 | browsers: 3 | - name: iphone 4 | version: 8.0 5 | - name: iphone 6 | version: 9.0 7 | - name: android 8 | version: latest 9 | - name: android 10 | version: 4.4 11 | - name: chrome 12 | version: -2..latest 13 | platform: Linux 14 | - name: firefox 15 | version: -2..latest 16 | platform: Linux 17 | - name: ie 18 | version: [10, 11] 19 | - name: microsoftedge 20 | version: latest 21 | - name: safari 22 | version: '7..latest' 23 | html: distribution/test/integration/index.html 24 | -------------------------------------------------------------------------------- /browserlist: -------------------------------------------------------------------------------- 1 | last 3 Chrome versions 2 | last 3 Firefox versions 3 | last 2 Explorer versions 4 | Safari >= 7 5 | last 2 Opera versions 6 | iOS >= 8 7 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | 2 |
3 |
Take control of your CSS keyframe animations
4 | 7 |

jogwheel

8 | 11 |
12 |
13 | 14 | 15 | Yeay! You want to contribute to jogwheel. That's amazing! 16 | To smoothen everyone's experience involved with the project please take note of the following guidelines and rules. 17 | 18 | ## Found an Issue? 19 | Thank you for reporting any issues you find. We do our best to test and make jogwheel as solid as possible, but any reported issue is a real help. 20 | 21 | > jogwheel issues 22 | 23 | [![Issues][issue-image]][issue-url] 24 | 25 | Please follow these guidelines when reporting issues: 26 | * Provide a title in the format of ` when ` 27 | * Tag your issue with the tag `bug` 28 | * Provide a short summary of what you are trying to do 29 | * Provide the log of the encountered error if applicable 30 | * Provide the exact version of jogwheel. Check `npm ls jogwheel` when in doubt 31 | * Be awesome and consider contributing a [pull request](#want-to-contribute) 32 | 33 | ## Want to contribute? 34 | You consider contributing changes to jogwheel – we dig that! 35 | Please consider these guidelines when filing a pull request: 36 | 37 | > jogwheel pull requests 38 | 39 | [![Pull requests][pr-image]][pr-url] 40 | 41 | * Follow the [Coding Rules](#coding-rules) 42 | * Follow the [Commit Rules](#commit-rules) 43 | * Make sure you rebased the current master branch when filing the pull request 44 | * Squash your commits when filing the pull request 45 | * Provide a short title with a maximum of 100 characters 46 | * Provide a more detailed description containing 47 | * What you want to achieve 48 | * What you changed 49 | * What you added 50 | * What you removed 51 | 52 | ## Coding Rules 53 | To keep the code base of jogwheel neat and tidy the following rules apply to every change 54 | 55 | > Coding standards 56 | 57 | ![Ecmascript version][ecma-image] [![Javascript coding style][codestyle-image]][codestyle-url] 58 | 59 | * [Happiness](/sindresorhus/xo) enforced via eslint 60 | * Use advanced language features where possible 61 | * JSdoc comments for everything 62 | * Favor micro library over swiss army knives (rimraf, ncp vs. fs-extra) 63 | * Coverage never drops below 90% 64 | * No change may lower coverage by more than 5% 65 | * Be awesome 66 | 67 | ## Commit Rules 68 | To help everyone with understanding the commit history of jogwheel the following commit rules are enforced. 69 | To make your life easier jogwheel is commitizen-friendly and provides the npm run-script `commit`. 70 | 71 | > Commit standards 72 | 73 | [![Commitizen friendly][commitizen-image]][commitizen-url] 74 | 75 | * [conventional-changelog](/commitizen/cz-conventional-changelog) 76 | * husky commit message hook available 77 | * present tense 78 | * maximum of 100 characters 79 | * message format of `$type($scope): $message` 80 | 81 | 82 | --- 83 | jogwheel `v1.4.5` is built by Mario Nebl and [contributors](./documentation/contributors.md) with :heart: 84 | and released under the [MIT License](./license.md). 85 | 86 | [npm-url]: https://www.npmjs.org/package/jogwheel 87 | [npm-image]: https://img.shields.io/npm/v/jogwheel.svg?style=flat-square 88 | [npm-dl-url]: https://www.npmjs.org/package/jogwheel 89 | [npm-dl-image]: http://img.shields.io/npm/dm/jogwheel.svg?style=flat-square 90 | 91 | [cdn-url]: https://wzrd.in/standalone/jogwheel@latest 92 | [cdn-image]: https://img.shields.io/badge/cdn-v1.4.5-5ec792.svg?style=flat-square 93 | 94 | [ci-url]: https://travis-ci.org/marionebl/jogwheel 95 | [ci-image]: https://img.shields.io/travis/marionebl/jogwheel/master.svg?style=flat-square 96 | 97 | [coverage-url]: https://coveralls.io/r/marionebl/jogwheel 98 | [coverage-image]: https://img.shields.io/coveralls/marionebl/jogwheel.svg?style=flat-square 99 | [climate-url]: https://codeclimate.com/github/marionebl/jogwheel 100 | [climate-image]: https://img.shields.io/codeclimate/github/marionebl/jogwheel.svg?style=flat-square 101 | 102 | [pr-url]: http://issuestats.com/github/marionebl/jogwheel 103 | [pr-image]: http://issuestats.com/github/marionebl/jogwheel/badge/pr?style=flat-square 104 | [issue-url]: undefined 105 | [issue-image]: http://issuestats.com/github/marionebl/jogwheel/badge/issue?style=flat-square 106 | 107 | [dependency-manager-image]: https://img.shields.io/badge/tracks%20with-greenkeeper-5ec792.svg?style=flat-square 108 | [dependency-manager-url]: https://github.com/greenkeeperio/greenkeeper 109 | [release-manager-image]: https://img.shields.io/badge/releases%20with-semantic--release-5ec792.svg?style=flat-square 110 | [release-manager-url]: https://github.com/semantic-release/semantic-release 111 | [ecma-image]: https://img.shields.io/badge/babel%20stage-0-5ec792.svg?style=flat-square 112 | [ecma-url]: https://github.com/babel/babel 113 | [codestyle-url]: https://github.com/sindresorhus/xo 114 | [codestyle-image]: https://img.shields.io/badge/code%20style-xo-5ec792.svg?style=flat-square 115 | [license-url]: ./license.md 116 | [license-image]: https://img.shields.io/badge/license-MIT-5ec792.svg?style=flat-square 117 | [commitizen-url]: http://commitizen.github.io/cz-cli/ 118 | [commitizen-image]: https://img.shields.io/badge/commitizen-friendly-5ec792.svg?style=flat-square 119 | 120 | [gitter-image]: https://img.shields.io/badge/gitter-join%20chat-5ec792.svg?style=flat-square 121 | [gitter-url]: https://gitter.im/sinnerschrader/patternplate 122 | 123 | -------------------------------------------------------------------------------- /documentation/api.md: -------------------------------------------------------------------------------- 1 | 2 |
3 |
Take control of your CSS keyframe animations
4 | 7 |

jogwheel API

8 | 11 |
12 |
13 | 14 | 15 | # constructor 16 | 17 | Creates a new jogwheel instance 18 | 19 | **Parameters** 20 | 21 | - `nodes` **Node or NodeList** Node or NodeList to instantiate on 22 | - `options` **object** Options object 23 | - `window` **[Window]** Global context to use (optional, default `global.window`) 24 | - `document` **[Document]** Document context to use (optional, default `global.window`) 25 | 26 | **Examples** 27 | 28 | ```javascript 29 | import jogwheel from 'jogwheel'; 30 | const element = document.querySelector('[data-animated]'); 31 | 32 | // Instantiate a jogwheel instance on element 33 | const wheel = new jogwheel(element); 34 | ``` 35 | 36 | Returns **jogwheel** jogwheel instance 37 | 38 | # JogWheel.prototype.durations 39 | 40 | Returns **array** durations used by jogwheel instance 41 | 42 | # JogWheel.prototype.players 43 | 44 | Returns **array** WebAnimationPlayer instances by jogwheel instance 45 | 46 | # JogWheel.prototype.playState 47 | 48 | Returns **string** playState, either `running` or `paused` 49 | 50 | # JogWheel.prototype.progress 51 | 52 | Returns **float** progress in fraction of 1 [0..1] 53 | 54 | # pause 55 | 56 | Pauses the animation 57 | 58 | **Examples** 59 | 60 | ```javascript 61 | import jogwheel from 'jogwheel'; 62 | const element = document.querySelector('[data-animated]'); 63 | 64 | // Instantiate a paused jogwheel instance on element 65 | const wheel = jogwheel.create(element, { 66 | paused: false 67 | }); 68 | 69 | // Pause the animation and reset it to animation start 70 | wheel.pause().seek(0); 71 | ``` 72 | 73 | Returns **jogwheel** jogwheel instance 74 | 75 | # play 76 | 77 | Plays the animation 78 | 79 | **Examples** 80 | 81 | ```javascript 82 | import jogwheel from 'jogwheel'; 83 | const element = document.querySelector('[data-animated]'); 84 | 85 | // Instantiate a paused jogwheel instance on element 86 | const wheel = jogwheel.create(element, { 87 | paused: true 88 | }); 89 | 90 | // Seek to middle of animation sequence and play 91 | wheel.seek(0.5).play(); 92 | ``` 93 | 94 | Returns **JogWheel** JogWheel instance 95 | 96 | # seek 97 | 98 | Seeks the timeline of the animation 99 | 100 | **Parameters** 101 | 102 | - `progress` **float** fraction of the animation timeline [0..1] 103 | 104 | **Examples** 105 | 106 | ```javascript 107 | import jogwheel from 'jogwheel'; 108 | const element = document.querySelector('[data-animated]'); 109 | 110 | // Instantiate a paused jogwheel instance on element 111 | const wheel = jogwheel.create(element, { 112 | paused: true 113 | }); 114 | 115 | // Keep track of scroll position 116 | let scrollTop = document.scrollTop; 117 | document.addEventListener('scroll', () => scrollTop = document.scrollTop); 118 | 119 | // Seek the animation [0..1] for scroll position of [0..300] 120 | function loop() { 121 | const fraction = Math.max((300 / scrollTop) - 1, 0); 122 | wheel.seek(fraction); 123 | window.requestAnimationFrame(loop); 124 | } 125 | 126 | // Start the render loop 127 | loop(); 128 | ``` 129 | 130 | Returns **jogwheel** jogwheel instance 131 | 132 | # create 133 | 134 | Creates a new jogwheel instance 135 | 136 | **Parameters** 137 | 138 | - `nodes` **Node or NodeList** Node or NodeList to instantiate on 139 | - `options` **object** Options object 140 | - `window` **[Window]** Global context to use (optional, default `global.window`) 141 | - `document` **[Document]** Document context to use (optional, default `global.window`) 142 | - `args` **...** 143 | 144 | **Examples** 145 | 146 | ```javascript 147 | import jogwheel from 'jogwheel'; 148 | const element = document.querySelector('[data-animated]'); 149 | : 150 | // Instantiate a paused jogwheel instance on element 151 | const wheel = jogwheel.create(element, { 152 | paused: true 153 | }); 154 | 155 | // Seek to middle of animation sequence 156 | wheel.seek(0.5); 157 | 158 | // Play the animation 159 | wheel.play(); 160 | ``` 161 | 162 | Returns **jogwheel** jogwheel instance 163 | 164 | 165 | 166 | --- 167 | jogwheel `v1.4.5` is built by Mario Nebl and [contributors](./documentation/contributors.md) with :heart: 168 | and released under the [MIT License](./license.md). 169 | 170 | [npm-url]: https://www.npmjs.org/package/jogwheel 171 | [npm-image]: https://img.shields.io/npm/v/jogwheel.svg?style=flat-square 172 | [npm-dl-url]: https://www.npmjs.org/package/jogwheel 173 | [npm-dl-image]: http://img.shields.io/npm/dm/jogwheel.svg?style=flat-square 174 | 175 | [cdn-url]: https://wzrd.in/standalone/jogwheel@latest 176 | [cdn-image]: https://img.shields.io/badge/cdn-v1.4.5-5ec792.svg?style=flat-square 177 | 178 | [ci-url]: https://travis-ci.org/marionebl/jogwheel 179 | [ci-image]: https://img.shields.io/travis/marionebl/jogwheel/master.svg?style=flat-square 180 | 181 | [coverage-url]: https://coveralls.io/r/marionebl/jogwheel 182 | [coverage-image]: https://img.shields.io/coveralls/marionebl/jogwheel.svg?style=flat-square 183 | [climate-url]: https://codeclimate.com/github/marionebl/jogwheel 184 | [climate-image]: https://img.shields.io/codeclimate/github/marionebl/jogwheel.svg?style=flat-square 185 | 186 | [pr-url]: http://issuestats.com/github/marionebl/jogwheel 187 | [pr-image]: http://issuestats.com/github/marionebl/jogwheel/badge/pr?style=flat-square 188 | [issue-url]: undefined 189 | [issue-image]: http://issuestats.com/github/marionebl/jogwheel/badge/issue?style=flat-square 190 | 191 | [dependency-manager-image]: https://img.shields.io/badge/tracks%20with-greenkeeper-5ec792.svg?style=flat-square 192 | [dependency-manager-url]: https://github.com/greenkeeperio/greenkeeper 193 | [release-manager-image]: https://img.shields.io/badge/releases%20with-semantic--release-5ec792.svg?style=flat-square 194 | [release-manager-url]: https://github.com/semantic-release/semantic-release 195 | [ecma-image]: https://img.shields.io/badge/babel%20stage-0-5ec792.svg?style=flat-square 196 | [ecma-url]: https://github.com/babel/babel 197 | [codestyle-url]: https://github.com/sindresorhus/xo 198 | [codestyle-image]: https://img.shields.io/badge/code%20style-xo-5ec792.svg?style=flat-square 199 | [license-url]: ./license.md 200 | [license-image]: https://img.shields.io/badge/license-MIT-5ec792.svg?style=flat-square 201 | [commitizen-url]: http://commitizen.github.io/cz-cli/ 202 | [commitizen-image]: https://img.shields.io/badge/commitizen-friendly-5ec792.svg?style=flat-square 203 | 204 | [gitter-image]: https://img.shields.io/badge/gitter-join%20chat-5ec792.svg?style=flat-square 205 | [gitter-url]: https://gitter.im/sinnerschrader/patternplate 206 | 207 | -------------------------------------------------------------------------------- /documentation/roadmap.md: -------------------------------------------------------------------------------- 1 | 2 |
3 |
Take control of your CSS keyframe animations
4 | 7 |

jogwheel Roadmap

8 | 11 |
12 |
13 | 14 | 15 | > Alive and kicking 16 | 17 | - [x] Basic project setup 18 | - [x] Basic working and tested implementation 19 | - [x] Speed up the Travis build process (max. 4 minutes) 20 | - [x] Test min/max browser versions per default 21 | - [ ] Use semantic-release for automatic npm releases 22 | - [ ] Make integration tests workable with phantomjs 23 | - [ ] Add `gh-pages` deployed automatically by Travis 24 | - [ ] Add interactive playground with live code editing for examples 25 | - [ ] Make all examples integration tests 26 | - [ ] Unit test documentation examples 27 | - [ ] Implement stubbed HTMLElement.prototype.style for React integration 28 | - [ ] Implement (configurable) matchMedia callbacks to read responsive styles correctly 29 | 30 | > Obsolute 31 | 32 | - [ ] ~~Run browser tests only for code changes vs. documentation~~ 33 | - [ ] ~~Run full cross-browser tests for relase-builds~~ 34 | 35 | 36 | --- 37 | jogwheel `v1.4.5` is built by Mario Nebl and [contributors](./documentation/contributors.md) with :heart: 38 | and released under the [MIT License](./license.md). 39 | 40 | [npm-url]: https://www.npmjs.org/package/jogwheel 41 | [npm-image]: https://img.shields.io/npm/v/jogwheel.svg?style=flat-square 42 | [npm-dl-url]: https://www.npmjs.org/package/jogwheel 43 | [npm-dl-image]: http://img.shields.io/npm/dm/jogwheel.svg?style=flat-square 44 | 45 | [cdn-url]: https://wzrd.in/standalone/jogwheel@latest 46 | [cdn-image]: https://img.shields.io/badge/cdn-v1.4.5-5ec792.svg?style=flat-square 47 | 48 | [ci-url]: https://travis-ci.org/marionebl/jogwheel 49 | [ci-image]: https://img.shields.io/travis/marionebl/jogwheel/master.svg?style=flat-square 50 | 51 | [coverage-url]: https://coveralls.io/r/marionebl/jogwheel 52 | [coverage-image]: https://img.shields.io/coveralls/marionebl/jogwheel.svg?style=flat-square 53 | [climate-url]: https://codeclimate.com/github/marionebl/jogwheel 54 | [climate-image]: https://img.shields.io/codeclimate/github/marionebl/jogwheel.svg?style=flat-square 55 | 56 | [pr-url]: http://issuestats.com/github/marionebl/jogwheel 57 | [pr-image]: http://issuestats.com/github/marionebl/jogwheel/badge/pr?style=flat-square 58 | [issue-url]: undefined 59 | [issue-image]: http://issuestats.com/github/marionebl/jogwheel/badge/issue?style=flat-square 60 | 61 | [dependency-manager-image]: https://img.shields.io/badge/tracks%20with-greenkeeper-5ec792.svg?style=flat-square 62 | [dependency-manager-url]: https://github.com/greenkeeperio/greenkeeper 63 | [release-manager-image]: https://img.shields.io/badge/releases%20with-semantic--release-5ec792.svg?style=flat-square 64 | [release-manager-url]: https://github.com/semantic-release/semantic-release 65 | [ecma-image]: https://img.shields.io/badge/babel%20stage-0-5ec792.svg?style=flat-square 66 | [ecma-url]: https://github.com/babel/babel 67 | [codestyle-url]: https://github.com/sindresorhus/xo 68 | [codestyle-image]: https://img.shields.io/badge/code%20style-xo-5ec792.svg?style=flat-square 69 | [license-url]: ./license.md 70 | [license-image]: https://img.shields.io/badge/license-MIT-5ec792.svg?style=flat-square 71 | [commitizen-url]: http://commitizen.github.io/cz-cli/ 72 | [commitizen-image]: https://img.shields.io/badge/commitizen-friendly-5ec792.svg?style=flat-square 73 | 74 | [gitter-image]: https://img.shields.io/badge/gitter-join%20chat-5ec792.svg?style=flat-square 75 | [gitter-url]: https://gitter.im/sinnerschrader/patternplate 76 | 77 | -------------------------------------------------------------------------------- /examples/cdn.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CDN example 5 | 6 | 47 | 48 |
Paused 0.5
49 |
Paused 0.5
50 |
Paused 0.5
51 | 52 | 53 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /examples/readme.md: -------------------------------------------------------------------------------- 1 | 2 |
3 |
Take control of your CSS keyframe animations
4 | 7 |

jogwheel Examples

8 | 11 |
12 |
13 | 14 | 15 | > Extensive examples are bound to land soon along with the gh-pages branch 16 | 17 | 18 | --- 19 | jogwheel `v1.4.5` is built by Mario Nebl and [contributors](./documentation/contributors.md) with :heart: 20 | and released under the [MIT License](./license.md). 21 | 22 | [npm-url]: https://www.npmjs.org/package/jogwheel 23 | [npm-image]: https://img.shields.io/npm/v/jogwheel.svg?style=flat-square 24 | [npm-dl-url]: https://www.npmjs.org/package/jogwheel 25 | [npm-dl-image]: http://img.shields.io/npm/dm/jogwheel.svg?style=flat-square 26 | 27 | [cdn-url]: https://wzrd.in/standalone/jogwheel@latest 28 | [cdn-image]: https://img.shields.io/badge/cdn-v1.4.5-5ec792.svg?style=flat-square 29 | 30 | [ci-url]: https://travis-ci.org/marionebl/jogwheel 31 | [ci-image]: https://img.shields.io/travis/marionebl/jogwheel/master.svg?style=flat-square 32 | 33 | [coverage-url]: https://coveralls.io/r/marionebl/jogwheel 34 | [coverage-image]: https://img.shields.io/coveralls/marionebl/jogwheel.svg?style=flat-square 35 | [climate-url]: https://codeclimate.com/github/marionebl/jogwheel 36 | [climate-image]: https://img.shields.io/codeclimate/github/marionebl/jogwheel.svg?style=flat-square 37 | 38 | [pr-url]: http://issuestats.com/github/marionebl/jogwheel 39 | [pr-image]: http://issuestats.com/github/marionebl/jogwheel/badge/pr?style=flat-square 40 | [issue-url]: undefined 41 | [issue-image]: http://issuestats.com/github/marionebl/jogwheel/badge/issue?style=flat-square 42 | 43 | [dependency-manager-image]: https://img.shields.io/badge/tracks%20with-greenkeeper-5ec792.svg?style=flat-square 44 | [dependency-manager-url]: https://github.com/greenkeeperio/greenkeeper 45 | [release-manager-image]: https://img.shields.io/badge/releases%20with-semantic--release-5ec792.svg?style=flat-square 46 | [release-manager-url]: https://github.com/semantic-release/semantic-release 47 | [ecma-image]: https://img.shields.io/badge/babel%20stage-0-5ec792.svg?style=flat-square 48 | [ecma-url]: https://github.com/babel/babel 49 | [codestyle-url]: https://github.com/sindresorhus/xo 50 | [codestyle-image]: https://img.shields.io/badge/code%20style-xo-5ec792.svg?style=flat-square 51 | [license-url]: ./license.md 52 | [license-image]: https://img.shields.io/badge/license-MIT-5ec792.svg?style=flat-square 53 | [commitizen-url]: http://commitizen.github.io/cz-cli/ 54 | [commitizen-image]: https://img.shields.io/badge/commitizen-friendly-5ec792.svg?style=flat-square 55 | 56 | [gitter-image]: https://img.shields.io/badge/gitter-join%20chat-5ec792.svg?style=flat-square 57 | [gitter-url]: https://gitter.im/sinnerschrader/patternplate 58 | 59 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This gulpfile bootstraps the tasks found in './tasks' 3 | * using the configuration in ./.gulprc 4 | * Please adapt configuration in ./gulprc 5 | */ 6 | var resolve = require('path').resolve; 7 | var rc = require('rc'); 8 | var gulp = require('gulp'); 9 | var util = require('gulp-util'); 10 | var minimist = require('minimist'); 11 | 12 | // Helpers 13 | var task = require('./tasks/helpers/task')(gulp); 14 | 15 | // Get configuration 16 | var config = rc('gulp', { 17 | paths: {}, 18 | tasks: { 19 | directory: 'tasks', 20 | public: [] 21 | } 22 | }); 23 | 24 | var cliOptions = minimist(process.argv.slice(2)); 25 | 26 | // Iterate gulp task config 27 | config.tasks.public.forEach(function(taskDefinition){ 28 | var isAliased = Array.isArray(taskDefinition); 29 | var taskName = isAliased ? taskDefinition[0] : taskDefinition; 30 | var taskAliases = isAliased ? (taskDefinition[1] || []).concat([taskName]) : [taskName]; 31 | var taskOptions = isAliased ? (taskDefinition[2] || {}) : {}; 32 | var taskFile = resolve(config.tasks.directory, taskName + '.js'); 33 | var taskFactory, taskFunction; 34 | 35 | try { 36 | taskFactory = require(taskFile); 37 | } catch(err) { 38 | util.log('Could not load task "' + taskName +'" from "' + taskFile + '":'); 39 | util.log(err); 40 | util.log(err.stack); 41 | return; 42 | } 43 | 44 | if (typeof taskFactory !== 'function') { 45 | util.log('Could not load task "' + taskName +'" from "' + taskFile + '", does not export factory function.'); 46 | util.log(err.stack); 47 | return; 48 | } 49 | 50 | try { 51 | taskFunction = taskFactory(gulp, config.paths, taskOptions, cliOptions); 52 | } catch(err) { 53 | util.log('Could not initialize task function "' + taskName +'" from "' + taskFile + '":'); 54 | util.log(err); 55 | util.log(err.stack); 56 | return; 57 | } 58 | 59 | task(taskFunction, taskAliases, taskOptions); 60 | }); 61 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | 2 |
3 |
Take control of your CSS keyframe animations
4 | 7 |

jogwheel

8 | 11 |
12 |
13 | 14 | 15 | The MIT License (MIT) 16 | 17 | Copyright (c) 2016 Mario Nebl and [contributors](./graphs/contributors) 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in all 27 | copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 35 | SOFTWARE. 36 | 37 | 38 | --- 39 | jogwheel `v1.4.5` is built by Mario Nebl and [contributors](./documentation/contributors.md) with :heart: 40 | and released under the [MIT License](./license.md). 41 | 42 | [npm-url]: https://www.npmjs.org/package/jogwheel 43 | [npm-image]: https://img.shields.io/npm/v/jogwheel.svg?style=flat-square 44 | [npm-dl-url]: https://www.npmjs.org/package/jogwheel 45 | [npm-dl-image]: http://img.shields.io/npm/dm/jogwheel.svg?style=flat-square 46 | 47 | [cdn-url]: https://wzrd.in/standalone/jogwheel@latest 48 | [cdn-image]: https://img.shields.io/badge/cdn-v1.4.5-5ec792.svg?style=flat-square 49 | 50 | [ci-url]: https://travis-ci.org/marionebl/jogwheel 51 | [ci-image]: https://img.shields.io/travis/marionebl/jogwheel/master.svg?style=flat-square 52 | 53 | [coverage-url]: https://coveralls.io/r/marionebl/jogwheel 54 | [coverage-image]: https://img.shields.io/coveralls/marionebl/jogwheel.svg?style=flat-square 55 | [climate-url]: https://codeclimate.com/github/marionebl/jogwheel 56 | [climate-image]: https://img.shields.io/codeclimate/github/marionebl/jogwheel.svg?style=flat-square 57 | 58 | [pr-url]: http://issuestats.com/github/marionebl/jogwheel 59 | [pr-image]: http://issuestats.com/github/marionebl/jogwheel/badge/pr?style=flat-square 60 | [issue-url]: undefined 61 | [issue-image]: http://issuestats.com/github/marionebl/jogwheel/badge/issue?style=flat-square 62 | 63 | [dependency-manager-image]: https://img.shields.io/badge/tracks%20with-greenkeeper-5ec792.svg?style=flat-square 64 | [dependency-manager-url]: https://github.com/greenkeeperio/greenkeeper 65 | [release-manager-image]: https://img.shields.io/badge/releases%20with-semantic--release-5ec792.svg?style=flat-square 66 | [release-manager-url]: https://github.com/semantic-release/semantic-release 67 | [ecma-image]: https://img.shields.io/badge/babel%20stage-0-5ec792.svg?style=flat-square 68 | [ecma-url]: https://github.com/babel/babel 69 | [codestyle-url]: https://github.com/sindresorhus/xo 70 | [codestyle-image]: https://img.shields.io/badge/code%20style-xo-5ec792.svg?style=flat-square 71 | [license-url]: ./license.md 72 | [license-image]: https://img.shields.io/badge/license-MIT-5ec792.svg?style=flat-square 73 | [commitizen-url]: http://commitizen.github.io/cz-cli/ 74 | [commitizen-image]: https://img.shields.io/badge/commitizen-friendly-5ec792.svg?style=flat-square 75 | 76 | [gitter-image]: https://img.shields.io/badge/gitter-join%20chat-5ec792.svg?style=flat-square 77 | [gitter-url]: https://gitter.im/sinnerschrader/patternplate 78 | 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jogwheel", 3 | "description": "Take control of your CSS keyframe animations", 4 | "main": "distribution/library/index.js", 5 | "jsnext:main": "source/library/index.js", 6 | "files": [ 7 | "source/library", 8 | "distribution/library" 9 | ], 10 | "scripts": { 11 | "start": "$npm_package_config_jogwheel_task_runner", 12 | "build": "$npm_package_config_jogwheel_task_runner build", 13 | "test": "$npm_package_config_jogwheel_when_ci --leader --trusted --no-pull-request && npm run test:remote || npm run test:local", 14 | "test:local": "parallelshell \"npm run test:local:unit\" \"npm run test:local:browser | tap-spec\" && npm run coverage:check", 15 | "test:local:unit": "$npm_package_config_jogwheel_task_runner test", 16 | "test:local:integration": "$npm_package_config_jogwheel_test_launcher $npm_package_config_jogwheel_integration_test_entry --phantom", 17 | "test:local:browser": "$npm_package_config_jogwheel_test_launcher $npm_package_config_jogwheel_unit_test_entry --phantom", 18 | "test:remote": "parallelshell \"npm run test:remote:unit\" \"npm run test:remote:browser\" && npm run test:remote:integration && npm run coverage:check", 19 | "test:remote:unit": "$npm_package_config_jogwheel_task_runner test", 20 | "test:remote:integration": "$npm_package_config_jogwheel_when_ci --leader --trusted --no-pull-request --changed=$npm_package_config_jogwheel_functional_files && $npm_package_config_jogwheel_test_launcher $npm_package_config_jogwheel_integration_test_entry || echo ''", 21 | "test:remote:browser": "$npm_package_config_jogwheel_when_ci --leader --trusted --no-pull-request --changed=$npm_package_config_jogwheel_functional_files && $npm_package_config_jogwheel_test_launcher $npm_package_config_jogwheel_unit_test_entry || echo ''", 22 | "coverage": "nyc tape distribution/test/unit/", 23 | "coverage:check": "npm run coverage && nyc check-coverage --lines 80 --functions 75 --branches 75", 24 | "coveralls": "$npm_package_config_jogwheel_when_ci --leader && (npm run coverage && nyc report --reporter=text-lcov | coveralls) || echo ''", 25 | "commit": "git-cz", 26 | "commitmsg": "$npm_package_config_jogwheel_when_ci && $npm_package_config_jogwheel_validate_commit --from=HEAD~1 || $npm_package_config_jogwheel_validate_commit -e", 27 | "prepush": "npm run test:local:unit && npm run coverage:check", 28 | "env": "env", 29 | "binaries": "ls node_modules/.bin/", 30 | "semantic-release": "(semantic-release pre && npm run semantic-release-pull-request && npm publish && semantic-release post) && GH_PAGES_ARCHIVE=\"true\" GH_PAGES_ADD=\"false\" npm run pages || GH_PAGES_ARCHIVE=\"false\" GH_PAGES_ADD=\"true\" npm run pages", 31 | "pages": "$npm_package_config_jogwheel_when_ci --leader --trusted --master && $npm_package_config_jogwheel_pages_update --pull-request || echo ''", 32 | "release-pull-request": "node distribution/scripts/release-pull-request.js", 33 | "semantic-release-pull-request": "not-in-install && ($npm_package_config_jogwheel_when_ci --leader --trusted && (npm run build && npm run release-pull-request))" 34 | }, 35 | "homepage": "https://github.com/marionebl/jogwheel#readme", 36 | "repository": { 37 | "type": "git", 38 | "url": "https://github.com/marionebl/jogwheel.git" 39 | }, 40 | "bugs": { 41 | "url": "https://github.com/marionebl/jogwheel/issues" 42 | }, 43 | "keywords": [ 44 | "css", 45 | "cssom", 46 | "keyframe", 47 | "keyframes", 48 | "animations", 49 | "webanimations", 50 | "static", 51 | "interpolation" 52 | ], 53 | "author": { 54 | "name": "Mario Nebl", 55 | "email": "hello@herebecode.com" 56 | }, 57 | "license": "MIT", 58 | "nyc": { 59 | "include": [ 60 | "distribution/library/**/*.js" 61 | ] 62 | }, 63 | "config": { 64 | "jogwheel": { 65 | "test-port": 1337, 66 | "task-runner": "gulp", 67 | "test-launcher": "zuul --no-coverage", 68 | "validate-commit": "conventional-changelog-lint", 69 | "when-ci": "node distribution/scripts/when-ci", 70 | "unit-test-entry": "distribution/test/unit/index.js", 71 | "integration-test-entry": "distribution/test/integration/index.js", 72 | "pages-update": "node distribution/scripts/pages-update.js", 73 | "functional-files": "package.json,.babelrc,.zuul.yml,browserslist,source/library/**/*,source/test/**/*", 74 | "public-files": "public/**/*,!public/**/*.tpl" 75 | }, 76 | "commitizen": { 77 | "path": "cz-conventional-changelog-lint" 78 | }, 79 | "documentation": { 80 | "slug": "marionebl/jogwheel", 81 | "bugs": "marionebl/jogwheel/issues", 82 | "icon": "", 83 | "color": "5ec792", 84 | "logo": "source/documentation/static/jogwheel.svg", 85 | "statichost": "https://cdn.rawgit.com", 86 | "host": "marionebl.github.io" 87 | }, 88 | "pages": { 89 | "slug": "marionebl/jogwheel", 90 | "branch": "gh-pages", 91 | "image": "static/jogwheel.svg", 92 | "svgicon": "static/jogwheel.svg", 93 | "pngicon": "static/jogwheel.png", 94 | "script": "static/index.js", 95 | "stylesheet": "static/index.css", 96 | "archives": "archives", 97 | "statichost": "https://cdn.rawgit.com" 98 | } 99 | }, 100 | "devDependencies": { 101 | "@marionebl/git-fs-repo": "0.1.1", 102 | "@marionebl/git-semver-tags": "1.1.7", 103 | "async": "1.5.2", 104 | "babel-core": "6.16.0", 105 | "babel-eslint": "6.0.0", 106 | "babel-plugin-object-assign": "1.2.1", 107 | "babel-polyfill": "6.5.0", 108 | "babel-regenerator-runtime": "6.2.0", 109 | "babel-runtime": "6.5.0", 110 | "babelify": "6.4.0", 111 | "browserify": "13.0.0", 112 | "chalk": "1.1.3", 113 | "commitizen": "2.8.1", 114 | "common-tags": "1.2.0", 115 | "conventional-changelog": "1.1.0", 116 | "conventional-changelog-cli": "1.2.0", 117 | "conventional-changelog-lint": "0.3.4", 118 | "coveralls": "2.11.8", 119 | "cz-conventional-changelog-lint": "0.1.3", 120 | "del": "2.2.0", 121 | "denodeify": "1.2.1", 122 | "documentation": "3.0.4", 123 | "dom-stub": "1.0.1", 124 | "emoji-parser": "0.1.1", 125 | "emojify.js": "1.1.0", 126 | "eslint": "1.10.3", 127 | "eslint-config-xo": "0.9.2", 128 | "eslint-plugin-babel": "3.2.0", 129 | "fn-name": "2.0.1", 130 | "github-api": "2.2.0", 131 | "globby": "4.0.0", 132 | "gulp": "3.9.1", 133 | "gulp-babel": "5.3.0", 134 | "gulp-cached": "1.1.0", 135 | "gulp-cssnext": "1.0.1", 136 | "gulp-data": "1.2.1", 137 | "gulp-documentation": "2.1.0", 138 | "gulp-eslint": "1.1.1", 139 | "gulp-ext-replace": "0.2.0", 140 | "gulp-istanbul": "0.10.4", 141 | "gulp-notify": "2.2.0", 142 | "gulp-plumber": "1.1.0", 143 | "gulp-remember": "0.3.1", 144 | "gulp-rename": "1.2.2", 145 | "gulp-sequence": "0.4.4", 146 | "gulp-shell": "0.5.2", 147 | "gulp-sourcemaps": "1.6.0", 148 | "gulp-tape": "git+https://github.com/marionebl/gulp-tape.git#93abadf0f130afed6c63910ba0acb790ca01aa37", 149 | "gulp-template": "4.0.0", 150 | "gulp-util": "3.0.7", 151 | "highlight.js": "9.2.0", 152 | "husky": "0.11.4", 153 | "in-publish": "2.0.0", 154 | "istanbul": "0.4.2", 155 | "js-git": "0.7.7", 156 | "lodash.flatten": "4.2.0", 157 | "lodash.merge": "4.4.0", 158 | "minimist": "1.2.0", 159 | "mkdirp": "0.5.1", 160 | "node-notifier": "4.4.0", 161 | "normalize.css": "5.0.0", 162 | "nyc": "6.4.0", 163 | "parallelshell": "2.0.0", 164 | "phantomjs": "2.1.7", 165 | "phantomjs-function-bind-polyfill": "1.0.0", 166 | "rc": "1.1.6", 167 | "remark": "4.2.1", 168 | "remark-gemoji": "1.0.0", 169 | "remark-highlight.js": "3.0.0", 170 | "remark-html": "3.0.0", 171 | "remark-inline-links": "3.0.0", 172 | "remark-lint": "4.0.0", 173 | "remark-slug": "4.1.0", 174 | "semantic-release": "4.3.5", 175 | "shelljs": "0.6.0", 176 | "string-humanize": "1.0.0", 177 | "stylelint": "5.4.0", 178 | "stylelint-config-standard": "4.0.1", 179 | "sync-request": "3.0.0", 180 | "tap-spec": "4.1.1", 181 | "tape": "4.5.1", 182 | "tape-catch": "1.0.4", 183 | "through2": "2.0.1", 184 | "unist-util-visit": "1.1.0", 185 | "vfile": "1.4.0", 186 | "vinyl-source-stream": "1.1.0", 187 | "watchify": "3.7.0", 188 | "web-animations-js": "2.2.0", 189 | "whatwg-fetch": "0.11.0", 190 | "zuul": "3.10.1" 191 | }, 192 | "dependencies": { 193 | "lodash.camelcase": "4.1.1", 194 | "lodash.uniq": "4.3.0" 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /public/contributing.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jogwheel - Take control of your CSS keyframe animations 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | Fork me on Github 17 | 18 | 29 |
30 |
31 |
Take control of your CSS keyframe animations
32 | 35 |

jogwheel

36 | 39 |
40 |
41 |

Yeay! You want to contribute to jogwheel. That's amazing! 42 | To smoothen everyone's experience involved with the project please take note of the following guidelines and rules.

43 |

Found an Issue?

44 |

Thank you for reporting any issues you find. We do our best to test and make jogwheel as solid as possible, but any reported issue is a real help.

45 |
46 |

jogwheel issues

47 |
48 |

Issues

49 |

Please follow these guidelines when reporting issues:

50 | 58 |

Want to contribute?

59 |

You consider contributing changes to jogwheel – we dig that! 60 | Please consider these guidelines when filing a pull request:

61 |
62 |

jogwheel pull requests

63 |
64 |

Pull requests

65 | 80 |

Coding Rules

81 |

To keep the code base of jogwheel neat and tidy the following rules apply to every change

82 |
83 |

Coding standards

84 |
85 |

Ecmascript version Javascript coding style

86 | 95 |

Commit Rules

96 |

To help everyone with understanding the commit history of jogwheel the following commit rules are enforced. 97 | To make your life easier jogwheel is commitizen-friendly and provides the npm run-script commit.

98 |
99 |

Commit standards

100 |
101 |

Commitizen friendly

102 | 109 |
110 |

jogwheel v1.4.5 is built by Mario Nebl and contributors with :heart: 111 | and released under the MIT License.

112 | 113 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /public/documentation/api.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jogwheel - Take control of your CSS keyframe animations 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | Fork me on Github 17 | 18 | 29 |
30 |
31 |
Take control of your CSS keyframe animations
32 | 35 |

jogwheel API

36 | 39 |
40 |
41 |

constructor

42 |

Creates a new jogwheel instance

43 |

Parameters

44 | 50 |

Examples

51 |
import jogwheel from 'jogwheel';
 52 | const element = document.querySelector('[data-animated]');
 53 | 
 54 | // Instantiate a jogwheel instance on element
 55 | const wheel = new jogwheel(element);
56 |

Returns jogwheel jogwheel instance

57 |

JogWheel.prototype.durations

58 |

Returns array durations used by jogwheel instance

59 |

JogWheel.prototype.players

60 |

Returns array WebAnimationPlayer instances by jogwheel instance

61 |

JogWheel.prototype.playState

62 |

Returns string playState, either running or paused

63 |

JogWheel.prototype.progress

64 |

Returns float progress in fraction of 1 [0..1]

65 |

pause

66 |

Pauses the animation

67 |

Examples

68 |
import jogwheel from 'jogwheel';
 69 | const element = document.querySelector('[data-animated]');
 70 | 
 71 | // Instantiate a paused jogwheel instance on element
 72 | const wheel = jogwheel.create(element, {
 73 | 	paused: false
 74 | });
 75 | 
 76 | // Pause the animation and reset it to animation start
 77 | wheel.pause().seek(0);
78 |

Returns jogwheel jogwheel instance

79 |

play

80 |

Plays the animation

81 |

Examples

82 |
import jogwheel from 'jogwheel';
 83 | const element = document.querySelector('[data-animated]');
 84 | 
 85 | // Instantiate a paused jogwheel instance on element
 86 | const wheel = jogwheel.create(element, {
 87 | 	paused: true
 88 | });
 89 | 
 90 | // Seek to middle of animation sequence and play
 91 | wheel.seek(0.5).play();
92 |

Returns JogWheel JogWheel instance

93 |

seek

94 |

Seeks the timeline of the animation

95 |

Parameters

96 | 99 |

Examples

100 |
import jogwheel from 'jogwheel';
101 | const element = document.querySelector('[data-animated]');
102 | 
103 | // Instantiate a paused jogwheel instance on element
104 | const wheel = jogwheel.create(element, {
105 | 	paused: true
106 | });
107 | 
108 | // Keep track of scroll position
109 | let scrollTop = document.scrollTop;
110 | document.addEventListener('scroll', () => scrollTop = document.scrollTop);
111 | 
112 | // Seek the animation [0..1] for scroll position of [0..300]
113 | function loop() {
114 | 	const fraction = Math.max((300 / scrollTop) - 1, 0);
115 | 	wheel.seek(fraction);
116 | 	window.requestAnimationFrame(loop);
117 | }
118 | 
119 | // Start the render loop
120 | loop();
121 |

Returns jogwheel jogwheel instance

122 |

create

123 |

Creates a new jogwheel instance

124 |

Parameters

125 | 132 |

Examples

133 |
import jogwheel from 'jogwheel';
134 | const element = document.querySelector('[data-animated]');
135 | :
136 | // Instantiate a paused jogwheel instance on element
137 | const wheel = jogwheel.create(element, {
138 | 	paused: true
139 | });
140 | 
141 | // Seek to middle of animation sequence
142 | wheel.seek(0.5);
143 | 
144 | // Play the animation
145 | wheel.play();
146 |

Returns jogwheel jogwheel instance

147 |
148 |

jogwheel v1.4.5 is built by Mario Nebl and contributors with :heart: 149 | and released under the MIT License.

150 | 151 | 154 | 155 | 156 | 157 | -------------------------------------------------------------------------------- /public/documentation/roadmap.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jogwheel - Take control of your CSS keyframe animations 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | Fork me on Github 17 | 18 | 29 |
30 |
31 |
Take control of your CSS keyframe animations
32 | 35 |

jogwheel Roadmap

36 | 39 |
40 |
41 |
42 |

Alive and kicking

43 |
44 | 58 |
59 |

Obsolute

60 |
61 | 65 |
66 |

jogwheel v1.4.5 is built by Mario Nebl and contributors with :heart: 67 | and released under the MIT License.

68 | 69 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /public/examples/cdn.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CDN example 5 | 6 | 47 | 48 |
Paused 0.5
49 |
Paused 0.5
50 |
Paused 0.5
51 | 52 | 53 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /public/examples/cdn.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CDN example 5 | 6 | 47 | 48 |
Paused 0.5
49 |
Paused 0.5
50 |
Paused 0.5
51 | 52 | 53 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /public/examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jogwheel - Take control of your CSS keyframe animations 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | Fork me on Github 17 | 18 | 29 |
30 |
31 |
Take control of your CSS keyframe animations
32 | 35 |

jogwheel Examples

36 | 39 |
40 |
41 |
42 |

Extensive examples are bound to land soon along with the gh-pages branch

43 |
44 |
45 |

jogwheel v1.4.5 is built by Mario Nebl and contributors with :heart: 46 | and released under the MIT License.

47 | 48 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /public/examples/readme.tpl: -------------------------------------------------------------------------------- 1 | <%= props.partials.header('', props.pkg.name + ' Examples') %> 2 | 3 | > Extensive examples are bound to land soon along with the gh-pages branch 4 | 5 | <%= props.partials.footer() %> 6 | -------------------------------------------------------------------------------- /public/license.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | jogwheel - Take control of your CSS keyframe animations 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | Fork me on Github 17 | 18 | 29 |
30 |
31 |
Take control of your CSS keyframe animations
32 | 35 |

jogwheel

36 | 39 |
40 |
41 |

The MIT License (MIT)

42 |

Copyright (c) 2016 Mario Nebl and contributors

43 |

Permission is hereby granted, free of charge, to any person obtaining a copy 44 | of this software and associated documentation files (the "Software"), to deal 45 | in the Software without restriction, including without limitation the rights 46 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 47 | copies of the Software, and to permit persons to whom the Software is 48 | furnished to do so, subject to the following conditions:

49 |

The above copyright notice and this permission notice shall be included in all 50 | copies or substantial portions of the Software.

51 |

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 52 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 53 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 54 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 55 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 56 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 57 | SOFTWARE.

58 |
59 |

jogwheel v1.4.5 is built by Mario Nebl and contributors with :heart: 60 | and released under the MIT License.

61 | 62 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /public/static/jogwheel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marionebl/jogwheel/b45da77b3eda1fe23fb65b9dffe127fbd2f94330/public/static/jogwheel.png -------------------------------------------------------------------------------- /public/static/jogwheel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 29 | 30 | 32 | 34 | 41 | 42 | -------------------------------------------------------------------------------- /source/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "xo/esnext" 3 | } 4 | -------------------------------------------------------------------------------- /source/_readme.tpl: -------------------------------------------------------------------------------- 1 | <%= props.partials.header('', '', 2 | [ 3 | 'About', 4 | 'Install', 5 | 'Usage', 6 | { 7 | name: 'Browser Support', 8 | href: '#browser-support' 9 | }, 10 | { 11 | name: 'API Documentation', 12 | href: './documentation/api.md' 13 | }, 14 | { 15 | name: 'Examples', 16 | href: './examples/readme.md' 17 | }, 18 | { 19 | name: 'Contributing', 20 | href: './contributing.md' 21 | } 22 | ] 23 | ) %> 24 | 25 | > Health 26 | 27 | <%= props.partials.badges(['ci']) %> 28 | <%= props.partials.badges(['coverage', 'climate']) %> 29 | 30 | > Availability 31 | 32 | <%= props.partials.badges(['npm', 'cdn', 'npm-dl']) %> 33 | 34 | > Activity 35 | 36 | <%= props.partials.badges(['pr', 'issue']) %> 37 | 38 | > Conventions and standards 39 | 40 | <%= props.partials.badges(['dependency-manager', 'release-manager', 'ecma', 'codestyle', 'license', 'commitizen']) %> 41 | 42 | ## About 43 | ${props.pkg.name} gives you the power to take full control of your CSS keyframe animations via JavaScript. 44 | 45 | - [x] **separation of concerns**: Declare animations with CSS 46 | - [x] **full control**: Play, pause and scrub your animations 47 | - [x] **animation sequences**: No brittle fiddling with animationEnd 48 | - [ ] world peace 49 | 50 | ## Install 51 | [${props.pkg.name}][npm-url] is available on npm. 52 | ``` 53 | npm install --save ${props.pkg.name} 54 | ``` 55 | 56 | ## Usage 57 | **:warning: Please note** ${props.pkg.name} assumes `Element.prototype.animate` is defined and returns a valid WebAnimationPlayer instance. 58 | To achieve this you will have to include a WebAnimation polyfill, [web-animations-js](https://github.com/web-animations/web-animations-js) by Google is recommended. 59 | 60 | The usage examples show recommended ways to include the polyfill. 61 | 62 | ### CommonJS 63 | ${props.pkg.name} exposes its API as CommonJS module. Using the export and bundling your JavaScript with browserify, webpack or rollup is **recommended**. 64 | 65 | ```js 66 | // import the polyfill 67 | import 'web-animations-js'; 68 | 69 | // import jogwheel 70 | import jogwheel from '${props.pkg.name}'; 71 | 72 | // Select target element 73 | const element = document.querySelector('[data-animated]'); 74 | 75 | // Construct jogwheel instance from element 76 | const player = jogwheel.create(element); 77 | 78 | // Jump halfway into the animation 79 | player.seek(0.5); 80 | ``` 81 | 82 | ### CDN 83 | ${props.pkg.name} provides prebundled downloads via [wzrd.in](https://wzrd.in/). 84 | Either embed or download the standalone bundle. Given you do not use a module system the standalone build will pollute `window.jogwheel`. This usage is viable but **not recommended**. 85 | 86 | * Development [${props.pkg.tag}](https://wzrd.in/debug-standalone/${props.pkg.name}@${props.pkg.tag}) 87 | * Production [${props.pkg.tag}](https://wzrd.in/standalone/${props.pkg.name}@${props.pkg.tag}) 88 | * Development [latest](https://wzrd.in/debug-standalone/${props.pkg.name}@latest) 89 | * Production [latest](https://wzrd.in/standalone/${props.pkg.name}@latest) 90 | 91 | **Fast track example** 92 | ```shell 93 | # Install cross-platform opn command 94 | npm install -g opn-cli 95 | 96 | # Download example 97 | curl -L ${examples.cdn} > jogwheel-example.html 98 | 99 | # Open example in default browser 100 | opn jogwheel-example.html 101 | ``` 102 | 103 | **All the code** 104 | ```html 105 | 106 | 107 | 108 | CDN example 109 | 110 | 151 | 152 |
Paused 0.5
153 |
Paused 0.5
154 |
Paused 0.5
155 | 156 | 157 | 169 | 170 | 171 | ``` 172 | 173 | --- 174 | See [API Documentation](./documentation/api.md) for details and [examples](./examples/readme.md) for more use cases. 175 | 176 | ## Browser support 177 | ${props.pkg.name} performs cross browser testing with SauceLabs 178 | 179 | [![Browser Support](https://saucelabs.com/browser-matrix/jogwheel-unit.svg)](https://saucelabs.com/u/jogwheel-unit) 180 | 181 | ## Limitations 182 | Depending on the WebAnimations implementation you choose there are some limitations for properties usable with ${props.pkg.name}. 183 | 184 | | Feature | Test | Issue | Blink | Gecko | `web-animations-js 2.1.4` | `web-animations-next 2.1.4` | 185 | |:--------------------------|:-----------:|:-----:|:---------:|:---------:|:-------------------------:|:---------------------------:| 186 | |`animation-timing-function`| [Link][1] | #161 | :warning: | :warning: | :warning: | :warning: | 187 | |`filter` | [Link][2] | #162 | :warning: | :warning: | :warning: | :warning: | 188 | 189 | 190 | ## Development 191 | You dig ${props.pkg.name} and want to submit a pull request? Awesome! 192 | Be sure to read the [contribution guide](./contributing.md) and you should be good to go. 193 | Here are some notes to get you coding real quick. 194 | 195 | ```shell 196 | # Clone repository, cd into it 197 | git clone ${props.pkg.repository.url} 198 | cd ${props.pkg.name} 199 | # Install npm dependencies 200 | npm install 201 | # Start the default build/watch task 202 | npm start 203 | ``` 204 | This will watch all files in `source` and start the appropriate tasks when changes are detected. 205 | 206 | ## Roadmap 207 | ${props.pkg.name} is up to a lot of good. This includes but is not limited to 208 | - [x] super-awesome cross-browser tests 209 | - [ ] unit-tested documentation code examples, because rust isn't the only language that can do cool dev convenience stuff 210 | - [ ] an interactive playground and animation editor 211 | - [ ] a plug-and-play react integration component 212 | 213 | --- 214 | See [Roadmap](./documentation/roadmap.md) for details. 215 | 216 | [1]: http://codepen.io/marionebl/pen/RrbzOO 217 | [2]: http://codepen.io/marionebl/pen/RrbzOO 218 | <%= props.partials.footer() %> 219 | -------------------------------------------------------------------------------- /source/contributing.tpl: -------------------------------------------------------------------------------- 1 | <%= props.partials.header('', '', [ 2 | { 3 | name: 'Report issues', 4 | href: '#found-an-issue' 5 | }, 6 | { 7 | name: 'Contribute', 8 | href: '#want-to-contribute' 9 | }, 10 | { 11 | name: 'Coding Rules', 12 | href: '#coding-rules' 13 | }, 14 | { 15 | name: 'Commit Rules', 16 | href: '#commit-rules' 17 | } 18 | ]) %> 19 | 20 | Yeay! You want to contribute to ${props.pkg.name}. That's amazing! 21 | To smoothen everyone's experience involved with the project please take note of the following guidelines and rules. 22 | 23 | ## Found an Issue? 24 | Thank you for reporting any issues you find. We do our best to test and make ${props.pkg.name} as solid as possible, but any reported issue is a real help. 25 | 26 | > ${props.pkg.name} issues 27 | 28 | [![Issues][issue-image]][issue-url] 29 | 30 | Please follow these guidelines when reporting issues: 31 | * Provide a title in the format of ` when ` 32 | * Tag your issue with the tag `bug` 33 | * Provide a short summary of what you are trying to do 34 | * Provide the log of the encountered error if applicable 35 | * Provide the exact version of ${props.pkg.name}. Check `npm ls ${props.pkg.name}` when in doubt 36 | * Be awesome and consider contributing a [pull request](#want-to-contribute) 37 | 38 | ## Want to contribute? 39 | You consider contributing changes to ${props.pkg.name} – we dig that! 40 | Please consider these guidelines when filing a pull request: 41 | 42 | > ${props.pkg.name} pull requests 43 | 44 | [![Pull requests][pr-image]][pr-url] 45 | 46 | * Follow the [Coding Rules](#coding-rules) 47 | * Follow the [Commit Rules](#commit-rules) 48 | * Make sure you rebased the current master branch when filing the pull request 49 | * Squash your commits when filing the pull request 50 | * Provide a short title with a maximum of 100 characters 51 | * Provide a more detailed description containing 52 | * What you want to achieve 53 | * What you changed 54 | * What you added 55 | * What you removed 56 | 57 | ## Coding Rules 58 | To keep the code base of ${props.pkg.name} neat and tidy the following rules apply to every change 59 | 60 | > Coding standards 61 | 62 | ![Ecmascript version][ecma-image] [![Javascript coding style][codestyle-image]][codestyle-url] 63 | 64 | * [Happiness](/sindresorhus/xo) enforced via eslint 65 | * Use advanced language features where possible 66 | * JSdoc comments for everything 67 | * Favor micro library over swiss army knives (rimraf, ncp vs. fs-extra) 68 | * Coverage never drops below 90% 69 | * No change may lower coverage by more than 5% 70 | * Be awesome 71 | 72 | ## Commit Rules 73 | To help everyone with understanding the commit history of ${props.pkg.name} the following commit rules are enforced. 74 | To make your life easier ${props.pkg.name} is commitizen-friendly and provides the npm run-script `commit`. 75 | 76 | > Commit standards 77 | 78 | [![Commitizen friendly][commitizen-image]][commitizen-url] 79 | 80 | * [conventional-changelog](/commitizen/cz-conventional-changelog) 81 | * husky commit message hook available 82 | * present tense 83 | * maximum of 100 characters 84 | * message format of `$type($scope): $message` 85 | 86 | <%= props.partials.footer() %> 87 | -------------------------------------------------------------------------------- /source/documentation/api.tpl: -------------------------------------------------------------------------------- 1 | <%= props.partials.header('', props.pkg.name + ' API') %> 2 | 3 | <%= docs.md %> 4 | 5 | <%= props.partials.footer() %> 6 | -------------------------------------------------------------------------------- /source/documentation/roadmap.tpl: -------------------------------------------------------------------------------- 1 | <%= props.partials.header('', props.pkg.name + ' Roadmap') %> 2 | 3 | > Alive and kicking 4 | 5 | - [x] Basic project setup 6 | - [x] Basic working and tested implementation 7 | - [x] Speed up the Travis build process (max. 4 minutes) 8 | - [x] Test min/max browser versions per default 9 | - [ ] Use semantic-release for automatic npm releases 10 | - [ ] Make integration tests workable with phantomjs 11 | - [ ] Add `gh-pages` deployed automatically by Travis 12 | - [ ] Add interactive playground with live code editing for examples 13 | - [ ] Make all examples integration tests 14 | - [ ] Unit test documentation examples 15 | - [ ] Implement stubbed HTMLElement.prototype.style for React integration 16 | - [ ] Implement (configurable) matchMedia callbacks to read responsive styles correctly 17 | 18 | > Obsolute 19 | 20 | - [ ] ~~Run browser tests only for code changes vs. documentation~~ 21 | - [ ] ~~Run full cross-browser tests for relase-builds~~ 22 | 23 | <%= props.partials.footer() %> 24 | -------------------------------------------------------------------------------- /source/documentation/static/index.css: -------------------------------------------------------------------------------- 1 | @import "normalize.css"; 2 | @import "highlight.js/styles/monokai.css"; 3 | @custom-media --medium (max-width: 30em); 4 | @custom-media --large (max-width: 60em); 5 | @custom-media --larger (max-width: 80em); 6 | @custom-selector :--githubteaser .jogwheel-teaser; 7 | @custom-selector :--mainnavigation .jogwheel-main-navigation; 8 | @custom-selector :--mainnavigationlist .jogwheel-main-navigation-list; 9 | @custom-selector :--header .jogwheel-header; 10 | @custom-selector :--claim .jogwheel-claim; 11 | @custom-selector :--logo .jogwheel-logo; 12 | @custom-selector :--name .jogwheel-name; 13 | @custom-selector :--navigation .jogwheel-navigation; 14 | @custom-selector :--archiveteaser .jogwheel-archive-teaser; 15 | @custom-selector :--saucelabs a[href="https://saucelabs.com/u/jogwheel-unit"]; 16 | @custom-selector :--lastseparator body > hr:last-of-type; 17 | @custom-selector :--lastparagraph body > p:last-of-type; 18 | 19 | @keyframes pan { 20 | 0% { 21 | transform: translateY(0); 22 | } 23 | 24 | 100% { 25 | transform: translateY(-100%); 26 | } 27 | } 28 | 29 | @keyframes fade { 30 | 0% { 31 | opacity: 0; 32 | } 33 | 34 | 100% { 35 | opacity: 1; 36 | } 37 | } 38 | 39 | :root { 40 | --primaryColor: rgb(94, 199, 146); 41 | --primaryDarkColor: rgb(131, 182, 156); 42 | --backgroundColor: #222; 43 | --textColor: #efefef; 44 | 45 | --fontCascade: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 46 | --sPadding: 20px; 47 | } 48 | 49 | * { 50 | box-sizing: border-box; 51 | } 52 | 53 | :root { 54 | padding: 0; 55 | margin: 0; 56 | background: var(--backgroundColor); 57 | font-family: var(--fontCascade); 58 | color: var(--textColor); 59 | } 60 | 61 | body { 62 | margin: 0 auto; 63 | padding: 0 var(--sPadding); 64 | min-width: 20em; 65 | max-width: 80em; 66 | border: 0 solid rgba(255, 255, 255, 0.05); 67 | border-left-width: 20px; 68 | border-right-width: 20px; 69 | box-sizing: content-box; 70 | } 71 | 72 | a, 73 | a:link, 74 | a:visited { 75 | text-decoration: none; 76 | color: var(--primaryColor); 77 | transition: 0.3s color ease-in-out; 78 | } 79 | 80 | a:hover, 81 | a:active { 82 | color: var(--primaryDarkColor); 83 | } 84 | 85 | img { 86 | max-width: 100%; 87 | } 88 | 89 | h1[id], 90 | h2[id], 91 | h3[id] { 92 | margin-top: 80px; 93 | } 94 | 95 | hr { 96 | border: 3px solid rgba(255, 255, 255, 0.05); 97 | margin-bottom: 0; 98 | margin-top: 20px; 99 | } 100 | 101 | blockquote { 102 | margin-bottom: 0; 103 | padding: 0.5em 1em 1em 1em; 104 | } 105 | 106 | blockquote p { 107 | margin: 0; 108 | } 109 | 110 | blockquote + p { 111 | padding: 0 1em 0.5em 1em; 112 | } 113 | 114 | blockquote, 115 | blockquote + p { 116 | border-left: 5px solid rgba(255, 255, 255, 0.2); 117 | margin-left: 0; 118 | margin-top: 0; 119 | } 120 | 121 | :--mainnavigation { 122 | width: 100%; 123 | margin: 0; 124 | padding: 0; 125 | background: var(--backgroundColor); 126 | overflow: auto; 127 | } 128 | 129 | :--mainnavigationlist { 130 | margin: 0; 131 | padding: 0; 132 | list-style-type: none; 133 | text-align: left; 134 | border-bottom: 1px solid var(--backgroundColor); 135 | } 136 | 137 | :--mainnavigationlist .jogwheel-item { 138 | display: inline-block; 139 | border-bottom: 7px solid transparent; 140 | transition: 0.3s border-color ease-in-out; 141 | } 142 | 143 | :--mainnavigationlist .jogwheel-item.jogwheel-item--active { 144 | border-color: var(--primaryColor); 145 | } 146 | 147 | :--mainnavigationlist a { 148 | padding: 20px 10px; 149 | text-align: center; 150 | } 151 | 152 | :--githubteaser { 153 | margin: 0 -var(--sPadding); 154 | text-align: center; 155 | background: #000; 156 | animation: pan 1s linear; 157 | animation-play-state: paused; 158 | animation-fill-mode: forwards; 159 | } 160 | 161 | :--githubteaser a { 162 | display: block; 163 | padding: 20px; 164 | transition: 0.3s background-color ease-in-out; 165 | } 166 | 167 | :--githubteaser a:hover { 168 | background: rgba(255, 255, 255, 0.1); 169 | } 170 | 171 | :--header { 172 | display: flex; 173 | flex-direction: column; 174 | padding-top: 60px; 175 | margin: 0 -var(--sPadding); 176 | background: var(--primaryColor); 177 | color: var(--backgroundColor); 178 | } 179 | 180 | :--logo { 181 | position: relative; 182 | z-index: 1; 183 | order: 1; 184 | } 185 | 186 | :--name { 187 | position: relative; 188 | z-index: 1; 189 | order: 2; 190 | margin: 0; 191 | padding: 40px 0 0; 192 | font-size: 50px; 193 | color: #fff; 194 | background: inherit; 195 | } 196 | 197 | :--claim { 198 | position: relative; 199 | z-index: 1; 200 | order: 3; 201 | margin: 0; 202 | padding: 0 0 60px 0; 203 | font-size: 25px; 204 | text-align: center; 205 | color: #eee; 206 | background: inherit; 207 | } 208 | 209 | :--navigation { 210 | display: none; 211 | } 212 | 213 | :--archiveteaser { 214 | padding: 0 20px 20px 20px; 215 | margin-left: -var(--sPadding); 216 | margin-right: -var(--sPadding); 217 | background: #fff; 218 | color: var(--backgroundColor); 219 | } 220 | 221 | :--saucelabs { 222 | display: block; 223 | width: 100%; 224 | max-width: 800px; 225 | margin: 20px auto; 226 | } 227 | 228 | :--saucelabs img { 229 | width: 100%; 230 | } 231 | 232 | :--lastseparator { 233 | display: none; 234 | } 235 | 236 | :--lastparagraph { 237 | margin-top: 160px; 238 | margin-bottom: 0; 239 | margin-left: -var(--sPadding); 240 | margin-right: -var(--sPadding); 241 | padding: 20px; 242 | background: #fff; 243 | color: var(--backgroundColor); 244 | } 245 | 246 | table td, 247 | table th { 248 | padding: 7.5px 10px; 249 | border: 1px solid rgba(255, 255, 255, 0.25); 250 | } 251 | 252 | table tr:nth-child(even) td { 253 | background: rgba(255, 255, 255, 0.05); 254 | } 255 | 256 | .emoji { 257 | display: inline-block; 258 | vertical-align: middle; 259 | width: 20px; 260 | height: 20px; 261 | max-width: none; 262 | background: transparent; 263 | } 264 | 265 | @media (--medium) { 266 | body { 267 | border: none; 268 | } 269 | table { 270 | display: block; 271 | } 272 | thead { 273 | display: none; 274 | } 275 | tr { 276 | display: block; 277 | } 278 | table tr:nth-child(even) td { 279 | background: none; 280 | } 281 | table td { 282 | display: block; 283 | padding: 5px 0; 284 | border: none; 285 | text-align: left; 286 | } 287 | table td:last-child { 288 | padding-bottom: 15px; 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /source/documentation/static/index.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import 'web-animations-js/web-animations-next.min.js'; 3 | import jogwheel from '../../library'; 4 | 5 | /* let state = { 6 | scrollTop: 0, 7 | teaserBottom: 0, 8 | teaserHeight: 0 9 | }; 10 | 11 | function clamp(input, min = 0, max = 1) { 12 | return Math.max(min, Math.min(max, input)); 13 | } 14 | 15 | function round(input, precision = 2) { 16 | return Math.round(input * (10 ^ precision)) / (10 ^ precision); 17 | } 18 | 19 | function equals(a = [], b = []) { 20 | let result = true; 21 | 22 | if (a.length !== b.length) { 23 | return false; 24 | } 25 | 26 | for (let i = 0; i < a.length; i += 1) { 27 | for (let j = 0; j < b.length; j += 1) { 28 | result = a[i] === b[i]; 29 | if (!result) { 30 | return result; 31 | } 32 | } 33 | } 34 | 35 | return result; 36 | } 37 | 38 | function distinct(fn, thisArg = this) { 39 | let memo; 40 | return function (...args) { // eslint-disable-line no-extra-bind 41 | if (!equals(memo, args)) { 42 | memo = args; 43 | return fn.apply(thisArg, args); // eslint-disable-line prefer-reflect 44 | } 45 | }; 46 | } 47 | 48 | function loop(options) { 49 | return function frame() { 50 | const fraction = clamp(1 - state.teaserHeight - state.scrollTop / state.teaserHeight, 0, 1); 51 | console.log(fraction); 52 | options.seekTeaser(fraction); 53 | 54 | options.window.requestAnimationFrame(frame); 55 | }; 56 | } 57 | 58 | function measure(context, data = {}) { 59 | return () => { 60 | const scrollTop = context.document.body.scrollTop; 61 | 62 | state = { 63 | ...state, 64 | ...data, 65 | scrollTop 66 | }; 67 | }; 68 | } 69 | 70 | function main(window, document) { 71 | const logoWheel = jogwheel.create(document.querySelector('.jogwheel-logo')); 72 | logoWheel.play(); 73 | 74 | const teaser = document.querySelector('.jogwheel-teaser'); 75 | const teaserWheel = jogwheel.create(teaser); 76 | teaserWheel.pause(); 77 | 78 | const options = { 79 | window, document, 80 | logo: logoWheel, 81 | teaser: teaserWheel, 82 | seekTeaser: distinct(teaserWheel.seek, teaserWheel) 83 | }; 84 | 85 | const teaserRect = teaser.getBoundingClientRect(); 86 | const data = { 87 | teaserHeight: teaserRect.height, 88 | teaserBottom: teaserRect.bottom 89 | }; 90 | 91 | document.addEventListener('scroll', measure(options, data)); 92 | 93 | measure(options, data)(); 94 | loop(options)(); 95 | 96 | window.jogwheel = jogwheel; 97 | window.teaser = teaserWheel; 98 | } 99 | 100 | main(global, global.document); */ 101 | -------------------------------------------------------------------------------- /source/documentation/static/jogwheel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marionebl/jogwheel/b45da77b3eda1fe23fb65b9dffe127fbd2f94330/source/documentation/static/jogwheel.png -------------------------------------------------------------------------------- /source/documentation/static/jogwheel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 29 | 30 | 32 | 34 | 41 | 42 | -------------------------------------------------------------------------------- /source/examples/cdn.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | CDN example 5 | 6 | 47 | 48 |
Paused 0.5
49 |
Paused 0.5
50 |
Paused 0.5
51 | 52 | 53 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /source/examples/readme.tpl: -------------------------------------------------------------------------------- 1 | <%= props.partials.header('', props.pkg.name + ' Examples') %> 2 | 3 | > Extensive examples are bound to land soon along with the gh-pages branch 4 | 5 | <%= props.partials.footer() %> 6 | -------------------------------------------------------------------------------- /source/library/convert-animation-duration.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts CSS duration string to integer holding duration in milliseconds 3 | * @param CSSAnimationDuration {string} [CSSAnimationDuration='0s'] The CSS animation duration string to convert 4 | * @return {integer} The duration of the css animation string in milliseconds 5 | * @private 6 | */ 7 | export default function convertAnimationDuration(CSSAnimationDuration = '0s') { 8 | const [unit, factor] = CSSAnimationDuration.indexOf('ms') > -1 ? ['ms', 1] : ['s', 1000]; 9 | const trimmed = CSSAnimationDuration.replace(unit, '').trim(); 10 | const duration = trimmed[0] === '.' ? `0${trimmed}` : trimmed; 11 | const converted = parseFloat(duration, 10); 12 | 13 | return typeof converted === 'number' ? 14 | converted * factor : 15 | 0; 16 | } 17 | -------------------------------------------------------------------------------- /source/library/convert-animation-iterations.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts CSS animation iteration count string to integer 3 | * @param CSSIterationCount {string} [CSSIterationCount='1'] CSS animation iteration count 4 | * @return {integer} 5 | * @private 6 | */ 7 | export default function convertAnimationIterations(CSSIterationCount = '1') { 8 | if (CSSIterationCount === 'infinite') { 9 | return Infinity; 10 | } 11 | 12 | const converted = parseInt(CSSIterationCount, 10); 13 | 14 | return typeof converted === 'number' ? 15 | converted : 16 | 1; 17 | } 18 | -------------------------------------------------------------------------------- /source/library/create-trap.js: -------------------------------------------------------------------------------- 1 | /** 2 | * createTrap 3 | * Traps writes to property targetName on host and calls handler instead 4 | * 5 | * @param host {object} object holding the trapped object as property 6 | * @param prisonerName {string} propertyName of the object ot trap 7 | * @param warden {function} handler function to execute instead of setter builtin 8 | * @returns prison {object} host with trapped targetName property 9 | * @private 10 | */ 11 | export default function createTrap(host, prisonerName, warden) { 12 | const prison = {...host}; 13 | const cell = {...(host[prisonerName] || {})}; 14 | 15 | Object.defineProperty(prison, prisonerName, { // eslint-disable-line prefer-reflect 16 | configurable: true, 17 | enumerable: prison.propertyIsEnumerable(prisonerName), 18 | 19 | get() { 20 | const trap = {...cell}; 21 | 22 | setTimeout(() => { 23 | for (const key in trap) { 24 | if (trap.hasOwnProperty(key)) { 25 | if (cell[key] !== trap[key]) { 26 | cell[key] = warden(host, key, trap[key]); 27 | } 28 | } 29 | } 30 | }); 31 | 32 | return trap; 33 | } 34 | }); 35 | 36 | return prison; 37 | } 38 | -------------------------------------------------------------------------------- /source/library/cssrule-enumerations.js: -------------------------------------------------------------------------------- 1 | // CSSRule type enums 2 | export default { 3 | unknown: 0, 4 | style: 1, 5 | charset: 2, 6 | import: 3, 7 | media: 4, 8 | fontface: 5, 9 | page: 6, 10 | keyframes: 7, 11 | keyframe: 8, 12 | namespace: 9, 13 | counter: 11, 14 | supports: 12, 15 | document: 13, 16 | fontfeature: 14, 17 | viewport: 15, 18 | region: 16 19 | }; 20 | -------------------------------------------------------------------------------- /source/library/get-animation-properties.js: -------------------------------------------------------------------------------- 1 | import {prefix} from './get-vendor-prefix'; 2 | 3 | const propertyNames = [ 4 | 'name', 5 | 'duration', 6 | 'iterationCount', 7 | 'timingFunction', 8 | 'fillMode', 9 | 'playState', 10 | 'delay' 11 | ]; 12 | 13 | /** 14 | * Returns applicable animation properties for a given node 15 | * @param {Node} node Node to read animation properties from 16 | * @param {Window} window Global context to use 17 | * @return {Object} Applicable animation properties for node in window 18 | * @private 19 | */ 20 | export default function getAnimationProperties(node, window = global.window, document = global.document) { 21 | const styles = window.getComputedStyle(node); 22 | 23 | return propertyNames.reduce((properties, propertyName) => { 24 | const cssName = `animation${propertyName[0].toUpperCase()}${propertyName.slice(1)}`; 25 | properties[propertyName] = styles[prefix(cssName, window, document)]; 26 | return properties; 27 | }, {}); 28 | } 29 | -------------------------------------------------------------------------------- /source/library/get-css-rules.js: -------------------------------------------------------------------------------- 1 | import toArray from './to-array'; 2 | 3 | /** 4 | * get cssRules for styleSheet 5 | * @param {string} styleSheet - styleSheet to extract cssRules from 6 | * @return {array} cssRules for styleSheet 7 | * @private 8 | */ 9 | export default function getCSSRules(styleSheet) { 10 | try { 11 | return toArray(styleSheet.cssRules || []); 12 | } catch (err) { 13 | console.warn(`Error while reading cssRules from StyleSheet "${styleSheet.href || 'local'}".`); 14 | console.error(err); 15 | return []; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /source/library/get-declarations.js: -------------------------------------------------------------------------------- 1 | import cssRuleEnumerations from './cssrule-enumerations'; 2 | import toArray from './to-array'; 3 | 4 | /** 5 | * Gets all CSSRules of type typeName and matching the predecate from rules 6 | * @param {string} [typeName='style'] - CSSRule type to search for, valid types: 7 | * unknown, style, charset, import, media, fontface, page, keyframes, keyframe, namespace, counter, supports, document, fontfeature, viewport, region 8 | * @param {array} [rules=[]] - Array of CSSRules to search 9 | * @param {function} [predecate=Boolean] - Predecate function to filter matches 10 | * @return {array} Array of matching CSSRules 11 | * @private 12 | */ 13 | export default function getDeclarations(typeName = 'style', rules = [], predecate = Boolean) { 14 | // Get target type enum 15 | const type = cssRuleEnumerations[typeName]; 16 | 17 | return toArray(rules) 18 | // filter by rule type 19 | .filter(rule => rule.type === type) 20 | // filter with user-provided predecate 21 | .filter(predecate) 22 | // unwrap cssRules 23 | .map(rule => rule.cssRules) 24 | // flatten cssRules 25 | .reduce((results, cssRules) => { 26 | return [ 27 | ...results, 28 | ...toArray(cssRules) 29 | ]; 30 | }, []); 31 | } 32 | -------------------------------------------------------------------------------- /source/library/get-defined-styles.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gets map of defined styles from CSS2Properties object 3 | * @param {CSS2Properties} properties CSS2Properties object to return defined styles from 4 | * @return {object} plain object containing defined styles as key value pairs 5 | * @private 6 | */ 7 | export default function getDefinedStyles(properties) { 8 | const styles = {}; 9 | 10 | for (let i = properties.length - 1; i >= 0; i -= 1) { 11 | const name = properties.item(i); 12 | const value = properties.getPropertyValue(name); 13 | if (value !== 'initial') { 14 | styles[name] = value; 15 | } 16 | } 17 | 18 | return styles; 19 | } 20 | -------------------------------------------------------------------------------- /source/library/get-keyframe-declarations.js: -------------------------------------------------------------------------------- 1 | import getDeclarations from './get-declarations'; 2 | 3 | /** 4 | * Gets all KeyFrameRule declarations attached to CSS animationName preset in rules 5 | * @param {string} animationName - CSS animationName to search KeyFrameRule declarations for 6 | * @param {array} rules - Array of CSSRules to search 7 | * @return {array} Array of matching KeyFrameRules 8 | * @private 9 | */ 10 | export default function getKeyframeDeclarations(animationName, rules) { 11 | // Filter for KeyFrameRules matching an animationName 12 | return getDeclarations('keyframes', rules, rule => rule.name === animationName); 13 | } 14 | -------------------------------------------------------------------------------- /source/library/get-keyframes.js: -------------------------------------------------------------------------------- 1 | import toArray from './to-array'; 2 | import getCSSRules from './get-css-rules'; 3 | import getKeyframeDeclarations from './get-keyframe-declarations'; 4 | import transformKeyframeDeclaration from './transform-keyframe-declaration'; 5 | 6 | /** 7 | * Gets webanimation keyframes attached to a CSS animationName 8 | * @param {string} animationName - CSS animationName to search keyframes for 9 | * @param {StyleSheetList} list of stylesheets to search in 10 | * @return {array} Array of webanimation keyframes attached to animationName 11 | * @private 12 | */ 13 | export default function getKeyframes(animationName, styleSheets) { 14 | // Collect CSSRules present in the document 15 | const CSSRules = toArray(styleSheets) 16 | .reduce((results, styleSheet) => [...results, ...getCSSRules(styleSheet)], []); 17 | 18 | // Filter CSSRules for KeyFrameRules 19 | return getKeyframeDeclarations(animationName, CSSRules) 20 | // Transform KeyFrameRules to web animation compatible format 21 | .map(transformKeyframeDeclaration) 22 | // Flatten mulitdimensional array of transformed keyframes 23 | .reduce((results, declaration) => { 24 | const amend = Array.isArray(declaration) ? declaration : [declaration]; 25 | return [...results, ...amend]; 26 | }, []); 27 | } 28 | -------------------------------------------------------------------------------- /source/library/get-mediaquery-media.js: -------------------------------------------------------------------------------- 1 | import uniq from 'lodash.uniq'; 2 | 3 | import toArray from './to-array'; 4 | import cssRuleEnumerations from './cssrule-enumerations'; 5 | import getCSSRules from './get-css-rules'; 6 | 7 | /** 8 | * Gets media query rules from styleSheets 9 | * @param {CSSStyleSheetList} styleSheets - StyleSheetList to search in 10 | * @returns {Array} Array of media query rule media texts 11 | * @private 12 | */ 13 | export default function getMediaqueryMedia(styleSheets) { 14 | // Collect CSSRules present in document 15 | const CSSRules = toArray(styleSheets) 16 | .reduce((results, styleSheet) => [...results, ...getCSSRules(styleSheet)], []); 17 | 18 | // Get all media query declarations and return array of media rules 19 | const type = cssRuleEnumerations.media; 20 | 21 | return uniq( 22 | CSSRules 23 | // filter for media queries 24 | .filter(rule => rule.type === type) 25 | // map to media rules 26 | .map(rule => rule.media.mediaText) 27 | // filter not all media query 28 | .filter(media => media !== 'not all') 29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /source/library/get-player.js: -------------------------------------------------------------------------------- 1 | import getKeyframes from './get-keyframes'; 2 | import getAnimationProperties from './get-animation-properties'; 3 | import convertAnimationDuration from './convert-animation-duration'; 4 | import convertAnimationIterations from './convert-animation-iterations'; 5 | import initPlayer from './init-player'; 6 | 7 | /** 8 | * Gets a web animation player based on the currently assigned CSS animation 9 | * @param element {HTMLElement} DOM element to scan for an applied CSS animation 10 | * @param settings {object} Settings object passed to jogwheel instance 11 | * @param window {Window} [window=global.window] Global context to use 12 | * @param document {Document} [document=global.window] Document context to use 13 | * @return {Object} `player` and `duration` attached to element 14 | * @private 15 | */ 16 | export default function getPlayer(element, settings, window = global.window, document = global.document) { 17 | // Read all animation related styles from element, respect prefixes 18 | const { 19 | name, 20 | duration, 21 | iterationCount, 22 | timingFunction, 23 | fillMode, 24 | playState, 25 | delay 26 | } = getAnimationProperties(element, window, document); 27 | 28 | // Generate keyframes based on the assigned animationName 29 | const keyframes = getKeyframes(name, document.styleSheets) 30 | .sort((a, b) => a.offset - b.offset); 31 | 32 | // Construct options for the webanimation player instance 33 | const options = { 34 | id: name, 35 | composite: 'replace', 36 | iterationComposite: 'replace', 37 | duration: convertAnimationDuration(duration), 38 | delay: convertAnimationDuration(delay), 39 | iterations: convertAnimationIterations(iterationCount), 40 | fill: fillMode, 41 | easing: timingFunction, 42 | playState, 43 | ...settings 44 | }; 45 | 46 | // Instantiate player instance 47 | const player = initPlayer(element, keyframes, options, options.render, window, document); 48 | 49 | return { 50 | player, 51 | duration: options.duration 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /source/library/get-vendor-prefix.js: -------------------------------------------------------------------------------- 1 | const cacheKey = '__jogwheel_vendor_prefix_cache'; 2 | 3 | /** 4 | * Gets the primary CSS vendor prefix for the current or provided document environment 5 | * @param {Window} [window = global.window] The window context to use 6 | * @param {Document} [document = global.document] The document context to use 7 | * @return {string} The primary CSS vendor prefix 8 | * @private 9 | */ 10 | export default function getVendorPrefix(window = global.window, document = global.document) { 11 | // Replace this when we get WeakMaps 12 | if (document[cacheKey]) { 13 | return document[cacheKey]; 14 | } 15 | 16 | document[cacheKey] = ''; 17 | 18 | const prefixes = /^(Moz|Webkit|ms)(?=[A-Z])/i; 19 | const element = document.body; 20 | 21 | for (const property in element.style) { // eslint-disable-line guard-for-in 22 | if (prefixes.test(property)) { 23 | document[cacheKey] = property.match(prefixes)[0]; 24 | return document[cacheKey]; 25 | } 26 | } 27 | 28 | return document[cacheKey]; 29 | } 30 | 31 | /** 32 | * Prefixes a given CSS property if needed in the current or provided document environment 33 | * @param {string} propertyName CSS property to prefix 34 | * @param {Window} [window = global.window] The window context to use 35 | * @param {Document} [document = global.document] The document context to use 36 | * @return {string} The prefixed version of the CSS property 37 | * @private 38 | */ 39 | export function prefix(propertyName, window = global.window, document = global.document) { 40 | const element = document.body; 41 | const prefix = getVendorPrefix(window, document); 42 | 43 | if (prefix === '') { 44 | return propertyName; 45 | } 46 | 47 | if (propertyName in element.style) { 48 | return propertyName; 49 | } 50 | 51 | return `${prefix}${propertyName[0].toUpperCase()}${propertyName.slice(1)}`; 52 | } 53 | -------------------------------------------------------------------------------- /source/library/init-player.js: -------------------------------------------------------------------------------- 1 | import {prefix} from './get-vendor-prefix'; 2 | import createTrap from './create-trap'; 3 | 4 | /** 5 | * isNativeFunction 6 | * Tests if fn is a native function 7 | * @param fn {function} function to test 8 | * @return {bool} 9 | * @private 10 | */ 11 | function isNativeFunction(fn) { 12 | if (typeof fn !== 'function') { 13 | return false; 14 | } 15 | 16 | return Function.prototype.toString.call(fn).match(/\{\s*\[native code\]\s*\}/); // eslint-disable-line prefer-reflect 17 | } 18 | 19 | /** 20 | * Initialize a WebAnimationsPlayer instance 21 | * @param {HTMLElement} element HTMLElement to instantiate on 22 | * @param {array} keyframes keyframes passed to render 23 | * @param {object} options options passed to render 24 | * @param {function} [render] render function used to apply interpolated inline styles 25 | * @param window {Window} [window=global.window] Global context to use 26 | * @param document {Document} [document=global.window] Document context to use 27 | * @return {object} WebAnimationsPlayer instance 28 | * @private 29 | */ 30 | export default function initPlayer(element, keyframes, options, render, window = global.window, document = global.document) { 31 | // Gracefully handle cases where element.animate is not defined 32 | if (typeof element.animate !== 'function') { 33 | const {HTMLElement = {}} = window; 34 | const {prototype: ElementPrototype = {}} = HTMLElement; 35 | const {animateMethod} = ElementPrototype; 36 | const animateAvailable = typeof animateMethod === 'function'; 37 | 38 | // Log warnings in development mode 39 | if (process.env.NODE_ENV !== 'production') { 40 | const polyFillMessage = animateAvailable === false ? [ 41 | `Did you include a WebAnimation polyfill?`, 42 | `https://git.io/vVV3x` 43 | ] : []; 44 | 45 | const message = [ 46 | `Initializing JogWheel on an object without animate method`, 47 | `falling back to noop WebAnimationsPlayer instance.`, 48 | ...polyFillMessage 49 | ]; 50 | 51 | console.warn(...message); 52 | } 53 | 54 | element.animate = () => { 55 | return { 56 | ...element, 57 | currentTime: 0, 58 | play() {}, 59 | pause() {} 60 | }; 61 | }; 62 | } 63 | 64 | // Create a proxy for the playerElement if needed 65 | // - no native implementation 66 | // - render function is given 67 | const isNative = isNativeFunction(element.animate); 68 | const hasRenderCallback = typeof render === 'function'; 69 | 70 | const playerElement = isNative === false && hasRenderCallback ? 71 | createTrap(element, 'style', render) : 72 | element; 73 | 74 | // Log warnings in development mode 75 | if (process.env.NODE_ENV !== 'production') { 76 | 77 | } 78 | 79 | // Create the WebAnimationPlayer instance 80 | const player = playerElement.animate(keyframes, options); 81 | 82 | // Detach the former animation to prevent problems with polyfill 83 | playerElement.style[prefix('animationName', window, document)] = `__jogwheelName-${options.name}`; 84 | player.__jogwheelName = options.name; 85 | 86 | // Pause or play the webanimation player instance based on CSS animationPlayState 87 | if (options.playState === 'paused') { 88 | player.pause(); 89 | } else { 90 | player.play(); 91 | } 92 | 93 | return player; 94 | } 95 | -------------------------------------------------------------------------------- /source/library/parse-keyframe-key.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Parses KeyFrameRule.keyText to an array of integers holding keyframe percentages 3 | * @param {string} keyText KeyFrameRule.keyText to parse 4 | * @return {array} Array of percentages for this KeyFrameRule 5 | * @private 6 | */ 7 | export default function parseKeyframeKey(keyText) { 8 | // Split multivalue key, 9 | return keyText.split(',') 10 | // Trim any remaining whitespace 11 | .map(key => key.trim()) 12 | // "Understand" CSS keyText keywords 13 | .map(key => key 14 | .replace('from', '0') 15 | .replace('to', '100')) 16 | // Remove any math symbols 17 | .map(key => key.replace('%', '')) 18 | // Parse to integer 19 | .map(key => parseInt(key, 10)); 20 | } 21 | -------------------------------------------------------------------------------- /source/library/remove-vendor-prefix.js: -------------------------------------------------------------------------------- 1 | const prefixes = [ 2 | 'ms', 3 | 'webkit', 4 | 'moz' 5 | ]; 6 | 7 | /** 8 | * remove vendor prefixes from CSSPropertyNames 9 | * @param {string} propertyName prefixed property name 10 | * @return {string} unprefixed property name 11 | * @private 12 | */ 13 | export default function removeVendorPrefix(propertyName = '') { 14 | const fragments = propertyName.split('-'); 15 | 16 | if (prefixes.indexOf(fragments[1]) > -1) { 17 | return fragments.slice(2).join('-'); 18 | } 19 | 20 | return propertyName; 21 | } 22 | -------------------------------------------------------------------------------- /source/library/to-array.js: -------------------------------------------------------------------------------- 1 | const empty = []; 2 | 3 | /** 4 | * Cast array-like objects and collections to Array 5 | * @param {Object} arrayLike array-like to cast to Array 6 | * @return {Array} Array cast from arrayLike 7 | * @private 8 | */ 9 | export default function toArray(arrayLike) { 10 | return empty.slice.call(arrayLike); // eslint-disable-line prefer-reflect 11 | } 12 | -------------------------------------------------------------------------------- /source/library/transform-keyframe-declaration.js: -------------------------------------------------------------------------------- 1 | import camelCase from 'lodash.camelcase'; 2 | 3 | import parseKeyframeKey from './parse-keyframe-key'; 4 | import getDefinedStyles from './get-defined-styles'; 5 | import removeVendorPrefix from './remove-vendor-prefix'; 6 | 7 | /** 8 | * Normalize as cssPropertyName to its unprefixed, camelcased form 9 | * @param {string} propertyName 10 | * @return {string} 11 | * @private 12 | */ 13 | function normalizePropertyName(propertyName) { 14 | return camelCase(removeVendorPrefix(propertyName)); 15 | } 16 | 17 | /** 18 | * Transforms KeyFrameRule to array of web animation compatible keyframes 19 | * @param {Object} keyFrameRule KeyFrameRule to transform 20 | * @return {Array} Array of webanimation keyframes 21 | * @private 22 | */ 23 | export default function transformKeyframeDeclaration(keyFrameRule) { 24 | // Convert keyFrame.keyText to integers holding percentage of keyframe 25 | const percentages = parseKeyframeKey(keyFrameRule.keyText); 26 | const style = getDefinedStyles(keyFrameRule.style); 27 | 28 | // Normalize to unprefixed styles 29 | const normalizedStyles = Object.keys(style).reduce((result, propertyName) => { 30 | const name = normalizePropertyName(propertyName); 31 | result[name] = style[propertyName]; 32 | return result; 33 | }, {}); 34 | 35 | return percentages.map(percentage => { 36 | return { 37 | // Convert percentage to fraction of 1 for webanimation compat 38 | offset: percentage / 100, 39 | // Mixin with extracted keyframe styling 40 | ...normalizedStyles 41 | }; 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /source/license.tpl: -------------------------------------------------------------------------------- 1 | <%= props.partials.header('', '') %> 2 | 3 | The MIT License (MIT) 4 | 5 | Copyright (c) ${new Date().getFullYear()} ${props.pkg.author.name} and [contributors](./graphs/contributors) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | <%= props.partials.footer() %> 26 | -------------------------------------------------------------------------------- /source/scripts/pages-update.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import denodeify from 'denodeify'; 3 | import chalk from 'chalk'; 4 | import minimist from 'minimist'; 5 | import shell from 'shelljs'; 6 | import Github from 'github-api'; 7 | import pkg from '../../package.json'; 8 | 9 | async function main() { 10 | const start = Date.now(); 11 | const hash = shell.exec(`git rev-parse --short HEAD`, {silent: true}).output.split('\n')[0]; 12 | 13 | const head = `gh-pages-update-${hash}`; 14 | const remote = process.env.GH_TOKEN ? 15 | `https://${process.env.GH_TOKEN}@github.com/${pkg.config.documentation.slug}.git` : 16 | `origin`; 17 | 18 | if (process.env.CI) { 19 | shell.exec(`git config user.email "${pkg.author.email}"`, {silent: true}); 20 | shell.exec(`git config user.name "${pkg.author.name}"`, {silent: true}); 21 | } 22 | 23 | if (process.env.GH_PAGES_ADD === 'true') { 24 | const add = shell.exec(`git add public`, {silent: true}); 25 | 26 | if (add.code === 0) { 27 | console.log(` ${chalk.green('✔')} added gh-pages changes`); 28 | } else { 29 | throw new Error(`failed to add gh-pages changes:\n${add.output}`); 30 | } 31 | 32 | const count = shell.exec(`git diff --cached --numstat`).output.split('\n').length; 33 | 34 | if (count === 0) { 35 | console.log(` ${chalk.yellow('⚠')} No file staged for commit and push, skipping`); 36 | const timestamp = chalk.gray(` [${Date.now() - start}ms]`); 37 | return ` ${chalk.green('✔')} pages-update executed successfully. ${timestamp}\n`; 38 | } 39 | 40 | console.log(` ${chalk.green('✔')} ${count} files staged for commit and push`); 41 | 42 | const commit = shell.exec(`git commit -m "docs: ${hash} master → gh-pages"`, {silent: true}); 43 | 44 | if (commit.code === 0) { 45 | console.log(` ${chalk.green('✔')} commited changes`); 46 | } else { 47 | throw new Error(`failed to commit changes:\n${commit.output}`); 48 | } 49 | } 50 | 51 | console.log(` ${chalk.gray('⧗')} pushing to github.com/${pkg.config.documentation.slug}#${head}.`); 52 | const push = shell.exec(`git subtree --prefix=public/ push ${remote} ${head}`, {silent: true}); 53 | 54 | if (push.code === 0) { 55 | console.log(` ${chalk.green('✔')} pushed to github.com/${pkg.config.documentation.slug}#${head}.`); 56 | } else { 57 | throw new Error(`failed pushing to github.com/${pkg.config.documentation.slug}#${head}:\n${push.output}`); 58 | } 59 | 60 | const title = `docs: ${hash} master → gh-pages`; 61 | const base = 'gh-pages'; 62 | 63 | if (process.env.CI && process.env.GH_TOKEN) { 64 | console.log(` ${chalk.gray('⧗')} submitting pull request "${head} → gh-pages" via oauth`); 65 | const github = new Github({ 66 | auth: 'oauth', 67 | token: process.env.GH_TOKEN 68 | }); 69 | 70 | const repositoryNames = pkg.config.pages.slug.split('/'); 71 | const repository = github.getRepo(...repositoryNames); 72 | 73 | await denodeify(repository.createPullRequest)({ 74 | title, 75 | base, 76 | head 77 | }); 78 | console.log(` ${chalk.green('✔')} submitted pull request "${head} → gh-pages" via oauth`); 79 | } else { 80 | console.log(` ${chalk.gray('⧗')} submitting pull request "${head} → gh-pages" via hub`); 81 | const pr = shell.exec(`hub pull-request -f -m "${title}" -b ${base} -h ${head}`, {silent: true}); 82 | 83 | if (pr.code === 0) { 84 | console.log(` ${chalk.green('✔')} submitted pull request "${head} → gh-pages" via hub: ${pr.output.split('\n')[0]}`); 85 | } else { 86 | console.log(` ${chalk.red('✖')} failed to submit pull request "${head} → gh-pages" via hub`); 87 | console.log(pr.output); 88 | } 89 | } 90 | 91 | const timestamp = chalk.gray(` [${Date.now() - start}ms]`); 92 | return ` ${chalk.green('✔')} pages-update executed successfully. ${timestamp}\n`; 93 | } 94 | 95 | main(minimist(process.argv.slice(2))) 96 | .then(message => console.log(message)) 97 | .catch(err => { 98 | console.log(err); 99 | console.error(` ${chalk.red('✖')} pages-update failed.\n`); 100 | console.trace(err); 101 | setTimeout(() => { 102 | throw new Error(err); 103 | }, 0); 104 | }); 105 | -------------------------------------------------------------------------------- /source/scripts/release-pull-request.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import path from 'path'; 3 | import denodeify from 'denodeify'; 4 | import shell from 'shelljs'; 5 | import Github from 'github-api'; 6 | import chalk from 'chalk'; 7 | import conventionalChangelog from 'conventional-changelog'; 8 | import minimist from 'minimist'; 9 | import {stripIndent} from 'common-tags'; 10 | import gitFsRepository from '@marionebl/git-fs-repo'; 11 | import gitSemverTags from 'git-semver-tags'; 12 | 13 | import pkg from '../../package.json'; 14 | 15 | const semverTags = denodeify(gitSemverTags); 16 | 17 | function getRepository() { 18 | return new Promise((resolve, reject) => { 19 | const dbPath = path.join(process.cwd(), '.git'); 20 | gitFsRepository(dbPath, (error, git) => { 21 | if (error) { 22 | return reject(error); 23 | } 24 | resolve(git); 25 | }); 26 | }); 27 | } 28 | 29 | async function getCommitMessage() { 30 | const repository = await getRepository(); 31 | const {hash} = repository.ref('HEAD'); 32 | const find = denodeify(repository.find.bind(repository)); 33 | const commit = await find(hash); 34 | const message = commit.message(); 35 | return message; 36 | } 37 | 38 | function getChangelog() { 39 | return new Promise((resolve, reject) => { 40 | const data = []; 41 | 42 | conventionalChangelog({ 43 | preset: 'angular' 44 | }) 45 | .on('data', chunk => { 46 | data.push(chunk); 47 | }) 48 | .on('end', () => { 49 | resolve(data.join('')); 50 | }) 51 | .on('error', error => { 52 | reject(error); 53 | }); 54 | }); 55 | } 56 | 57 | async function getVersion() { 58 | const [version] = await semverTags(); 59 | return version; 60 | } 61 | 62 | async function main() { 63 | const start = Date.now(); 64 | const version = await getVersion(); 65 | const head = `release/${version}`; 66 | 67 | const remote = process.env.GH_TOKEN ? 68 | `https://${process.env.GH_TOKEN}@github.com/${pkg.config.documentation.slug}.git` : 69 | `origin`; 70 | 71 | const title = `chore: release version ${version}`; 72 | const base = 'master'; 73 | 74 | const message = await getCommitMessage(); 75 | const gettingChangeLog = getChangelog(); 76 | 77 | if (message.startsWith(title)) { 78 | console.log(` ${chalk.green('✔')} detected release build "${message}", exiting with code 0 and handing off to semantic-release.`); 79 | const timestamp = chalk.gray(` [${Date.now() - start}ms]`); 80 | console.log(` ${chalk.green('✔')} release-pull-request executed successfully. ${timestamp}\n`); 81 | process.exit(0); 82 | } 83 | 84 | if (process.env.CI) { 85 | shell.exec(`git config user.email "${pkg.author.email}"`, {silent: true}); 86 | shell.exec(`git config user.name "${pkg.author.name}"`, {silent: true}); 87 | } 88 | 89 | const add = shell.exec(`git add *.md documentation/ examples/ public/`, {silent: true}); 90 | 91 | if (add.code === 0) { 92 | console.log(` ${chalk.green('✔')} added docs and gh-pages changes`); 93 | } else { 94 | throw new Error(`failed to add docs and gh-pages changes:\n${add.output}`); 95 | } 96 | 97 | const commit = shell.exec(`git commit -m "${title}"`, {silent: true}); 98 | 99 | if (commit.code === 0) { 100 | console.log(` ${chalk.green('✔')} commited changes to "${title}"`); 101 | } else { 102 | console.log(` ${chalk.yellow('⚠')} failed to commit changes to "${title}"`); 103 | console.log(` ${chalk.green('✔')} hand off to semantic-release`); 104 | process.exit(0); 105 | } 106 | 107 | console.log(` ${chalk.gray('⧗')} pushing to github.com/${pkg.config.documentation.slug}#${head}.`); 108 | const push = shell.exec(`git push ${remote} HEAD:refs/heads/${head}`, {silent: true}); 109 | 110 | if (push.code === 0) { 111 | console.log(` ${chalk.green('✔')} pushed to github.com/${pkg.config.documentation.slug}#${head}.`); 112 | } else { 113 | throw new Error(`failed pushing to "${title}":\n${push.output}`); 114 | } 115 | 116 | if (process.env.CI && process.env.GH_TOKEN) { 117 | console.log(` ${chalk.gray('⧗')} submitting pull request "${title}" via oauth`); 118 | const github = new Github({ 119 | auth: 'oauth', 120 | token: process.env.GH_TOKEN 121 | }); 122 | const repositoryNames = pkg.config.pages.slug.split('/'); 123 | const repository = github.getRepo(...repositoryNames); 124 | const changelog = await gettingChangeLog; 125 | 126 | const body = stripIndent` 127 | This release includes the following changes: 128 | ${changelog} 129 | `; 130 | 131 | try { 132 | const createPullRequest = denodeify(repository.createPullRequest.bind(repository)); 133 | await createPullRequest({ 134 | title, 135 | base, 136 | body, 137 | head 138 | }); 139 | 140 | console.log(` ${chalk.green('✔')} submitted pull request "${title}" via oauth`); 141 | console.log(` ${chalk.green('✔')} Exiting with code 1 to prevent semantic-release from publishing`); 142 | const timestamp = chalk.gray(` [${Date.now() - start}ms]`); 143 | console.log(` ${chalk.green('✔')} release-pull-request successfully. ${timestamp}\n`); 144 | process.exit(1); 145 | } catch (err) { 146 | console.error(` ${chalk.red('✖')} pull request "${title}" failed`); 147 | console.log(` ${chalk.gray('⧗')} deleting branch "${head}"`); 148 | const remove = shell.exec(`git push ${remote} --delete ${head}`, {silent: true}); 149 | if (remove.code === 0) { 150 | console.log(` ${chalk.green('✔')} removed github.com/${pkg.config.documentation.slug}#${head}.`); 151 | } else { 152 | console.log(` ${chalk.red('✖')} failed to remove github.com/${pkg.config.documentation.slug}#${head}`); 153 | } 154 | throw err; 155 | } 156 | console.log(` ${chalk.green('✔')} submitted pull request "${title}" via oauth`); 157 | } else { 158 | console.log(` ${chalk.gray('⧗')} submitting pull request "${title}" via hub`); 159 | const pr = shell.exec(`hub pull-request -f -m "${title}" -b ${base} -h ${head}`, {silent: true}); 160 | 161 | if (pr.code === 0) { 162 | console.log(` ${chalk.green('✔')} submitted pull request "${title}" via hub: ${pr.output.split('\n')[0]}`); 163 | console.log(` ${chalk.green('✔')} Exiting with code 1 to prevent semantic-release from publishing`); 164 | const timestamp = chalk.gray(` [${Date.now() - start}ms]`); 165 | console.log(` ${chalk.green('✔')} release-pull-request successfully. ${timestamp}\n`); 166 | process.exit(1); 167 | } else { 168 | throw new Error(`failed to submit pull request "${title}":\n${pr.output}`); 169 | } 170 | } 171 | 172 | const timestamp = chalk.gray(` [${Date.now() - start}ms]`); 173 | return ` ${chalk.green('✔')} release-pull-request successfully. ${timestamp}\n`; 174 | } 175 | 176 | main(minimist(process.argv.slice(2))) 177 | .then(message => console.log(message)) 178 | .catch(err => { 179 | console.log(err); 180 | console.error(` ${chalk.red('✖')} release-pull-request failed.\n`); 181 | console.trace(err); 182 | setTimeout(() => { 183 | throw new Error(err); 184 | }, 0); 185 | }); 186 | -------------------------------------------------------------------------------- /source/scripts/when-ci.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import chalk from 'chalk'; 3 | import minimist from 'minimist'; 4 | import globby from 'globby'; 5 | import shell from 'shelljs'; 6 | 7 | function isLeader(jobNumber = '') { 8 | const fragments = jobNumber.split('.'); 9 | return fragments[fragments.length - 1] === '1'; 10 | } 11 | 12 | function isTrusted() { 13 | return process.env.TRAVIS_SECURE_ENV_VARS === 'true'; 14 | } 15 | 16 | function isNoPullRequest() { 17 | return process.env.TRAVIS_PULL_REQUEST === 'false'; 18 | } 19 | 20 | async function main(options) { 21 | const job = process.env.TRAVIS_JOB_NUMBER; 22 | 23 | if (!job) { 24 | console.log(` ${chalk.yellow('⚠')} Skipping, "$TRAVIS_JOB_NUMBER" is not defined.`); 25 | throw new Error(1); 26 | } else { 27 | console.log(` ${chalk.green('✔')} Job number is "${job}".`); 28 | } 29 | 30 | if (options.leader) { 31 | if (!isLeader(job)) { 32 | console.log(` ${chalk.yellow('⚠')} Skipping, job "${job}" is not the leader. ${chalk.gray('[--leader]')}`); 33 | throw new Error(1); 34 | } else if (options.leader) { 35 | console.log(` ${chalk.green('✔')} Job "${job}" is the leader. ${chalk.gray('[--leader]')}`); 36 | } 37 | } 38 | 39 | if (options['pull-request'] === false) { 40 | if (!isNoPullRequest()) { 41 | console.log(` ${chalk.yellow('⚠')} Skipping, "${job}" is a pull request. ${chalk.gray('[--no-pull-request]')}`); 42 | throw new Error(1); 43 | } else { 44 | console.log(` ${chalk.green('✔')} Job "${job}" is no pull request. ${chalk.gray('[--no-pull-request]')}`); 45 | } 46 | } 47 | 48 | if (options.trusted) { 49 | if (!isTrusted(job)) { 50 | console.log(` ${chalk.yellow('⚠')} Skipping, job "${job}" has no secure env variables. ${chalk.grey('[--trusted]')}`); 51 | throw new Error(1); 52 | } else { 53 | console.log(` ${chalk.green('✔')} Job "${job}" has secure env variables. ${chalk.grey('[--trusted]')}`); 54 | } 55 | } 56 | 57 | if (options.master) { 58 | if (process.env.TRAVIS_BRANCH !== "master") { 59 | console.log(` ${chalk.yellow('⚠')} Skipping, job "${job}" on branch "${process.env.TRAVIS_BRANCH}", not master. ${chalk.grey('[--master]')}`); 60 | throw new Error(1); 61 | } else if (!isNoPullRequest()) { 62 | console.log(` ${chalk.yellow('⚠')} Skipping, job "${job}" on branch "master", but pull-request. ${chalk.grey('[--master]')}`); 63 | throw new Error(1); 64 | } else { 65 | console.log(` ${chalk.green('✔')} Job "${job}" is on branch master. ${chalk.grey('[--trusted]')}`); 66 | } 67 | } 68 | 69 | if (options.changed) { 70 | const pattern = typeof options.changed === 'string' ? options.changed.split(',') : ['**/*', '!node_modules/**']; 71 | 72 | if (!process.env.TRAVIS_COMMIT) { 73 | console.log(` ${chalk.yellow('⚠')} Changed argument "${options.changed}" given, but "process.env.TRAVIS_COMMIT" is not defined, ignoring "changed" filter. ${chalk.grey('[--changed]')}`); 74 | throw new Error(1); 75 | } else { 76 | const command = `git diff-tree --no-commit-id --name-only -r ${process.env.TRAVIS_COMMIT}`; 77 | const changedFiles = shell.exec(command, {silent: true}).output.split('\n'); 78 | const searchedFiles = await globby(pattern); 79 | const intersection = searchedFiles.filter((searchedFile) => changedFiles.indexOf(searchedFile) > -1); 80 | 81 | if (intersection.length > 0 || intersection.length === 0 && options.changed === true) { 82 | console.log(` ${chalk.green('✔')} Commit ${process.env.TRAVIS_COMMIT} has ${intersection.length} changed files matching ${pattern}. ${chalk.grey('[--changed]')}`); 83 | } else { 84 | console.log(` ${chalk.yellow('⚠')} Commit ${process.env.TRAVIS_COMMIT} has no changed files matching ${pattern}. ${chalk.grey('[--changed]')}`); 85 | throw new Error(1); 86 | } 87 | } 88 | } 89 | 90 | if (options.dirty) { 91 | const pattern = typeof options.dirty === 'string' ? options.dirty.split(',') : ['**/*', '!node_modules/**']; 92 | 93 | const command = `git status --porcelain | sed s/^...//`; 94 | const changedFiles = shell.exec(command, {silent: true}).output.split('\n'); 95 | const searchedFiles = await globby(pattern); 96 | const intersection = searchedFiles.filter((searchedFile) => changedFiles.indexOf(searchedFile) > -1); 97 | 98 | if (intersection.length > 0 || intersection.length === 0 && options.changed === true) { 99 | console.log(` ${chalk.green('✔')} ${intersection.length} dirty files matching ${pattern}. ${chalk.grey('[--dirty]')}`); 100 | } else { 101 | console.log(` ${chalk.yellow('⚠')} No dirty files matching ${pattern}. ${chalk.grey('[--dirty]')}`); 102 | throw new Error(1); 103 | } 104 | } 105 | } 106 | 107 | const args = minimist(process.argv.slice(2)); 108 | 109 | main(args) 110 | .catch(err => { 111 | if (err.message !== '1') { 112 | console.error(err.message); 113 | console.trace(err.trace); 114 | } 115 | process.exit(1); 116 | }); 117 | -------------------------------------------------------------------------------- /source/test/integration/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /source/test/integration/index.html: -------------------------------------------------------------------------------- 1 | 99 | 106 | -------------------------------------------------------------------------------- /source/test/integration/index.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import 'web-animations-js'; 3 | import 'whatwg-fetch'; 4 | import tape from 'tape'; 5 | 6 | import jogwheel from '../../library'; 7 | 8 | const tests = [ 9 | 'simple', 10 | 'keyword', 11 | 'iteration-count', 12 | 'node-list' 13 | ]; 14 | const base = './distribution/test/integration'; 15 | 16 | const mimeTypes = { 17 | 'text/css': (container, content) => { 18 | const style = document.createElement('style'); 19 | style.innerHTML = content; 20 | container.appendChild(style); 21 | return style; 22 | }, 23 | 'text/html': (container, content) => { 24 | const element = document.createElement('div'); 25 | element.innerHTML = content; 26 | container.appendChild(element.firstChild); 27 | return element; 28 | }, 29 | 'application/javascript': (container, content) => { 30 | const script = document.createElement('script'); 31 | script.text = content; 32 | script.type = 'text/javascript'; 33 | container.appendChild(script); 34 | return script; 35 | } 36 | }; 37 | 38 | function getInject(stage) { 39 | return async function inject(asset) { 40 | const contentType = asset.headers.get('content-type'); 41 | const mimeType = contentType.split(';')[0]; 42 | const injector = mimeTypes[mimeType]; 43 | return injector(stage, await asset.text()); 44 | }; 45 | } 46 | 47 | function read() { 48 | return JSON.parse(localStorage.getItem('jogwheel')) || {}; 49 | } 50 | 51 | function save(state) { 52 | const previous = read(); 53 | localStorage.setItem('jogwheel', JSON.stringify({ 54 | ...previous, 55 | ...state 56 | })); 57 | } 58 | 59 | function onStateChange(e) { 60 | save({ 61 | visible: e.target.checked 62 | }); 63 | } 64 | 65 | async function main() { 66 | const state = document.querySelector('[data-stage-state]'); 67 | const stage = document.querySelector('[data-stage-demos]'); 68 | const handle = document.querySelector('[data-stage-handle]'); 69 | const img = document.createElement('img'); 70 | img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; 71 | 72 | state.checked = read().visible || false; 73 | onStateChange({target: state}); 74 | state.addEventListener('change', onStateChange); 75 | 76 | handle.addEventListener('dragstart', e => { 77 | state.checked = true; 78 | onStateChange({target: state}); 79 | e.dataTransfer.setDragImage(img, 0, 0); 80 | }); 81 | 82 | handle.addEventListener('drag', e => { 83 | const height = window.innerHeight - e.pageY; 84 | 85 | if (Math.abs(e.pageY - stage.getBoundingClientRect().top) > 50) { 86 | return; 87 | } 88 | 89 | stage.style.height = `${height}px`; 90 | }); 91 | 92 | handle.addEventListener('dragend', e => { 93 | const height = window.innerHeight - e.pageY; 94 | 95 | if (Math.abs(e.pageY - stage.getBoundingClientRect().top) > 50) { 96 | return; 97 | } 98 | 99 | stage.style.height = `${height}px`; 100 | save({height}); 101 | }); 102 | 103 | const containers = []; 104 | 105 | tape('integration', async function(t) { // eslint-disable-line no-loop-func 106 | t.plan(tests.length); 107 | 108 | for (const test of tests) { 109 | const container = document.createElement('div'); 110 | container.setAttribute('data-stage-demo-container', 'data-stage-demo-container'); 111 | container.setAttribute('class', 'demo-pending'); 112 | const frame = document.createElement('iframe'); 113 | frame.setAttribute('data-stage-demo-frame', 'data-stage-demo-frame'); 114 | 115 | const headline = document.createElement('h4'); 116 | headline.innerHTML = test; 117 | 118 | container.appendChild(headline); 119 | container.appendChild(frame); 120 | stage.appendChild(container); 121 | containers.push(container); 122 | 123 | try { 124 | /* eslint-disable no-loop-func */ 125 | const onload = async function () { 126 | const frameDocument = frame.contentDocument || frame.contentWindow.document; 127 | const inject = getInject(frameDocument.body); 128 | 129 | // fetch test styling 130 | const cssURI = [base, test, `index.css`].join('/'); 131 | const cssLoading = fetch(cssURI); 132 | 133 | // fetch test markup 134 | const htmlURI = [base, test, `index.html`].join('/'); 135 | const htmlLoading = fetch(htmlURI); 136 | 137 | // fetch test javascript 138 | const jsURI = [base, test, `index.js`].join('/'); 139 | const jsLoading = fetch(jsURI); 140 | 141 | // await css and html, inject them 142 | const css = await cssLoading; 143 | const html = await htmlLoading; 144 | 145 | await inject(css); 146 | await inject(html); 147 | 148 | // inject js when css and html is injected 149 | const js = await jsLoading; 150 | const code = await js.text(); 151 | 152 | frame.contentWindow.resize = function (size) { 153 | frame.style.width = size; 154 | }; 155 | frame.contentWindow.__jogwheel = jogwheel; 156 | frame.contentWindow.__jogWheelTape = t.test; 157 | frame.contentWindow.___jogWheelElement = { 158 | animate: HTMLElement.prototype.animate, 159 | getAnimations: HTMLElement.prototype.getAnimations 160 | }; 161 | 162 | frame.contentWindow.eval(` 163 | var __jogwheel_originalRequire = require; 164 | function require(module) { 165 | if (module === 'tape') { 166 | return window.__jogWheelTape; 167 | } else if (module.indexOf('web-animations-js') > -1) { 168 | HTMLElement.prototype.animate = window.___jogWheelElement.animate; 169 | HTMLElement.prototype.getAnimations = window.___jogWheelElement.getAnimations 170 | return; 171 | } else if (module === 'jogwheel') { 172 | return window.__jogwheel; 173 | } else { 174 | return __jogwheel_originalRequire(module); 175 | } 176 | } 177 | ${code} 178 | `); 179 | }; 180 | 181 | frame.onload = onload; 182 | if (frame.contentDocument.readyState === 'complete') { 183 | onload(); 184 | } 185 | /* eslint-disable */ 186 | } catch (err) { 187 | console.error(err); 188 | container.setAttribute('class', `demo-failed`); 189 | t.fail(err); 190 | } 191 | } 192 | }); 193 | 194 | const poller = setInterval(() => { 195 | const ends = window.zuul_msg_bus.filter(message => message.type === 'test_end'); 196 | const done = window.zuul_msg_bus.filter(message => message.type === 'done'); 197 | 198 | ends.forEach((end, index) => { 199 | const className = end.passed ? 'passed' : 'failed'; 200 | const container = containers[index - 1]; 201 | 202 | if (container) { 203 | container.setAttribute('class', `demo-${className}`); 204 | } 205 | }); 206 | 207 | if (done.length > 0) { 208 | clearInterval(poller); 209 | } 210 | }, 50); 211 | } 212 | 213 | main(); 214 | -------------------------------------------------------------------------------- /source/test/integration/iteration-count/index.css: -------------------------------------------------------------------------------- 1 | @keyframes spin { 2 | from { 3 | transform: rotate(0); 4 | } 5 | to { 6 | transform: rotate(-360deg); 7 | } 8 | } 9 | 10 | [data-animated] { 11 | height: 100px; 12 | width: 100px; 13 | line-height: 100px; 14 | text-align: center; 15 | border-radius: 50%; 16 | background: wheat; 17 | animation: spin 1s linear; 18 | animation-iteration-count: 100; 19 | animation-play-state: paused; 20 | animation-fill-mode: both; 21 | } 22 | 23 | @media screen and (min-width: 1000px) { 24 | [data-animated] { 25 | height: 150px; 26 | width: 150px; 27 | animation-name: none; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /source/test/integration/iteration-count/index.html: -------------------------------------------------------------------------------- 1 |
2 | Iteration Count 3 |
4 | -------------------------------------------------------------------------------- /source/test/integration/iteration-count/index.js: -------------------------------------------------------------------------------- 1 | import 'web-animations-js'; 2 | 3 | const tape = require('tape'); 4 | const jogwheel = require('jogwheel'); 5 | 6 | tape('iteration-count', t => { 7 | const element = document.querySelector('[data-animated]'); 8 | const wheel = jogwheel.create(element, {}, window, document); 9 | 10 | t.doesNotThrow(() => { 11 | wheel.seek(0.5); 12 | }); 13 | 14 | t.doesNotThrow(() => { 15 | wheel.play(); 16 | }); 17 | 18 | window.resize('1001px'); 19 | t.end(); 20 | }); 21 | -------------------------------------------------------------------------------- /source/test/integration/keyword/index.css: -------------------------------------------------------------------------------- 1 | @keyframes keyword { 2 | from, to { 3 | opacity: 0; 4 | } 5 | 6 | 50% { 7 | opacity: 1; 8 | } 9 | } 10 | 11 | [data-animated] { 12 | height: 100px; 13 | width: 100px; 14 | line-height: 100px; 15 | text-align: center; 16 | border-radius: 50%; 17 | background: wheat; 18 | animation: simple 3s infinite; 19 | animation-fill-mode: both; 20 | } 21 | -------------------------------------------------------------------------------- /source/test/integration/keyword/index.html: -------------------------------------------------------------------------------- 1 |
2 | Keyword 3 |
4 | -------------------------------------------------------------------------------- /source/test/integration/keyword/index.js: -------------------------------------------------------------------------------- 1 | import 'web-animations-js'; 2 | const tape = require('tape'); 3 | 4 | tape('keyword integration', t => { 5 | console.log('Keyword!'); 6 | t.end(); 7 | }); 8 | -------------------------------------------------------------------------------- /source/test/integration/node-list/index.css: -------------------------------------------------------------------------------- 1 | @keyframes spin { 2 | from { 3 | transform: rotate(0); 4 | } 5 | to { 6 | transform: rotate(-360deg); 7 | } 8 | } 9 | 10 | [data-animated] { 11 | display: inline-block; 12 | height: 100px; 13 | width: 100px; 14 | overflow: hidden; 15 | white-space: nowrap; 16 | line-height: 100px; 17 | text-align: center; 18 | border-radius: 50%; 19 | background: wheat; 20 | animation: spin 1s linear; 21 | animation-iteration-count: 100; 22 | animation-play-state: paused; 23 | animation-fill-mode: both; 24 | } 25 | 26 | [data-animated="item-2"] { 27 | animation-duration: 30s; 28 | } 29 | 30 | [data-animated="item-2"] { 31 | animation-duration: .3s; 32 | } 33 | -------------------------------------------------------------------------------- /source/test/integration/node-list/index.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Item 1 4 |
5 |
6 | Item 2 7 |
8 |
9 | Item 3 10 |
11 |
12 | -------------------------------------------------------------------------------- /source/test/integration/node-list/index.js: -------------------------------------------------------------------------------- 1 | import 'web-animations-js'; 2 | 3 | const tape = require('tape'); 4 | const jogwheel = require('jogwheel'); 5 | 6 | tape('node-list', t => { 7 | const elements = document.querySelectorAll('[data-animated]'); 8 | const wheel = jogwheel.create(elements, {}, window, document); 9 | 10 | t.doesNotThrow(() => { 11 | wheel.seek(0.5); 12 | }); 13 | 14 | t.doesNotThrow(() => { 15 | wheel.play(); 16 | }); 17 | 18 | t.end(); 19 | }); 20 | -------------------------------------------------------------------------------- /source/test/integration/simple/index.css: -------------------------------------------------------------------------------- 1 | @keyframes simple { 2 | 0%, 100% { 3 | opacity: 0; 4 | } 5 | 6 | 50% { 7 | opacity: 1; 8 | } 9 | } 10 | 11 | @keyframes simplebig { 12 | from, to { 13 | transform: scale(0); 14 | } 15 | 16 | 50% { 17 | transform: scale(1); 18 | } 19 | } 20 | 21 | [data-animated] { 22 | height: 100px; 23 | width: 100px; 24 | line-height: 100px; 25 | text-align: center; 26 | border-radius: 50%; 27 | background: wheat; 28 | animation: simple 3s; 29 | animation-iteration-count: infinite; 30 | animation-fill-mode: both; 31 | } 32 | 33 | @media screen and (min-width: 1000px) { 34 | [data-animated] { 35 | height: 150px; 36 | width: 150px; 37 | animation-name: simplebig; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /source/test/integration/simple/index.html: -------------------------------------------------------------------------------- 1 |
2 | Simple 3 |
4 | -------------------------------------------------------------------------------- /source/test/integration/simple/index.js: -------------------------------------------------------------------------------- 1 | import 'web-animations-js'; 2 | 3 | const tape = require('tape'); 4 | const jogwheel = require('jogwheel'); 5 | 6 | const element = document.querySelector('[data-animated]'); 7 | 8 | tape('simple integration', t => { 9 | let actual = {}; 10 | 11 | const wheel = jogwheel.create(element, { 12 | render(element, styles) { 13 | actual = styles; 14 | Object.keys(styles) 15 | .filter(propertyName => propertyName !== 'length') 16 | .forEach(propertyName => element.style[propertyName] = styles[propertyName]); 17 | } 18 | }, window, document); 19 | 20 | t.comment('Pausing and seeking to 0'); 21 | t.doesNotThrow(() => wheel.pause(), 'Pausing does not throw'); 22 | t.doesNotThrow(() => wheel.seek(0), 'Seeking does not throw'); 23 | 24 | window.setTimeout(() => { 25 | let value = actual.opacity || window.getComputedStyle(element).opacity; 26 | t.equals(value, '0', 'Should leave element with opacity of 0'); 27 | 28 | t.comment('Seeking to 0.5'); 29 | wheel.seek(0.5); 30 | 31 | window.setTimeout(() => { 32 | value = actual.opacity || window.getComputedStyle(element).opacity; 33 | t.notEqual(value, '0', 'Should leave element with opacity other than 0'); 34 | 35 | t.comment('Seeking to 1'); 36 | wheel.seek(1); 37 | window.setTimeout(() => { 38 | value = actual.opacity || window.getComputedStyle(element).opacity; 39 | t.equals(value, '0', 'Should leave element with opacity of 0'); 40 | 41 | t.comment('Playing the animation'); 42 | wheel.play(); 43 | 44 | const previous = actual.opacity || window.getComputedStyle(element).opacity; 45 | 46 | setTimeout(() => { // eslint-disable-line max-nested-callbacks 47 | value = actual.opacity || window.getComputedStyle(element).opacity; 48 | t.notEqual(value, previous, 'Should leave element with different opacity after timeout'); 49 | 50 | window.resize('1001px'); 51 | t.end(); 52 | }, 300); 53 | }, 300); 54 | }, 300); 55 | }, 300); 56 | }); 57 | -------------------------------------------------------------------------------- /source/test/unit/convert-animation-iterations.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape'; 2 | import convertAnimationIterations from '../../library/convert-animation-iterations.js'; 3 | 4 | tape('convert-animation-iterations', t => { 5 | t.equal( 6 | typeof convertAnimationIterations(), 7 | 'number', 8 | 'should return a number' 9 | ); 10 | 11 | t.equal( 12 | convertAnimationIterations(), 13 | 1, 14 | 'should default to 1' 15 | ); 16 | 17 | t.equal( 18 | convertAnimationIterations('infinite'), 19 | Infinity, 20 | 'should return Infinity for "infinite"' 21 | ); 22 | 23 | t.end(); 24 | }); 25 | -------------------------------------------------------------------------------- /source/test/unit/create-trap.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape'; 2 | import createTrap from '../../library/create-trap'; 3 | 4 | tape('create-trap', t => { 5 | t.equals( 6 | typeof createTrap({}, 'trap', () => {}), 7 | 'object', 8 | 'should return an object' 9 | ); 10 | 11 | t.equals( 12 | typeof createTrap({}, 'trap', () => {}).trap, 13 | 'object', 14 | 'should return an object with trapped property of type object' 15 | ); 16 | 17 | t.test('when writing a trapped property object', test => { 18 | const host = {trap: {trapped: true}}; 19 | const calls = []; 20 | 21 | const prison = createTrap(host, 'trap', (...args) => { 22 | calls.push(args); 23 | }); 24 | 25 | prison.trap.trapped = false; 26 | 27 | t.ok( 28 | prison.trap.trapped, 29 | 'should not write property on trapped object' 30 | ); 31 | 32 | setTimeout(() => { 33 | test.equals( 34 | calls.length, 35 | 2, 36 | 'should call the warden function twice' 37 | ); 38 | 39 | test.deepEquals( 40 | calls[0][0], 41 | host, 42 | 'should be called with correct host object' 43 | ); 44 | 45 | test.deepEquals( 46 | calls[1][0], 47 | host, 48 | 'should be called with correct host object' 49 | ); 50 | 51 | test.equals( 52 | calls[0][1], 53 | 'trapped', 54 | 'should be called with correct propertyName' 55 | ); 56 | 57 | test.equals( 58 | calls[1][1], 59 | 'trapped', 60 | 'should be called with correct propertyName' 61 | ); 62 | 63 | test.end(); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /source/test/unit/fixtures/cross-domain-stylesheets.js: -------------------------------------------------------------------------------- 1 | export default [{ 2 | cssRules: null 3 | }]; 4 | 5 | export const keyframes = []; 6 | export const animation = []; 7 | -------------------------------------------------------------------------------- /source/test/unit/fixtures/cross-domain-unsafe-stylesheets.js: -------------------------------------------------------------------------------- 1 | export default [{ 2 | get cssRules() { 3 | throw new Error('SecurityError: The operation is insecure.'); 4 | } 5 | }]; 6 | 7 | export const keyframes = []; 8 | export const animation = []; 9 | -------------------------------------------------------------------------------- /source/test/unit/fixtures/filled-animation.js: -------------------------------------------------------------------------------- 1 | export default { 2 | animationName: 'filled-animation', 3 | animationPlayState: 'running', 4 | animationTimingFunction: 'linear', 5 | animationDuration: '1s', 6 | animationDelay: '.5s', 7 | animationFillMode: 'both', 8 | animationIterationCount: '10' 9 | }; 10 | -------------------------------------------------------------------------------- /source/test/unit/fixtures/keyword-animation-declaration.js: -------------------------------------------------------------------------------- 1 | import cssStyleRules from '../stubs/css-style-rules'; 2 | 3 | export default [{ 4 | cssRules: [{ 5 | name: 'default-animation', 6 | type: 7, 7 | cssRules: [ 8 | { 9 | keyText: 'from, to', 10 | style: cssStyleRules({ 11 | 'height': '0', 12 | 'width': '0', 13 | 'margin-top': '10px', 14 | 'length': 3 15 | }) 16 | }, 17 | { 18 | keyText: '50%', 19 | style: cssStyleRules({ 20 | 'height': '100px', 21 | 'width': '100px', 22 | 'margin-top': '20px', 23 | 'length': 3 24 | }) 25 | } 26 | ] 27 | }] 28 | }]; 29 | 30 | export const keyframes = [ 31 | [ 32 | { 33 | offset: 0, 34 | height: '0', 35 | width: '0', 36 | marginTop: '10px' 37 | }, 38 | { 39 | offset: 1, 40 | height: '0', 41 | width: '0', 42 | marginTop: '20px' 43 | } 44 | ], 45 | { 46 | offset: 0.5, 47 | height: '100px', 48 | width: '100px', 49 | marginTop: '10px' 50 | } 51 | ]; 52 | 53 | export const animation = [ 54 | { 55 | offset: 0, 56 | height: '0', 57 | width: '0', 58 | marginTop: '10px' 59 | }, 60 | { 61 | offset: 1, 62 | height: '0', 63 | width: '0', 64 | marginTop: '10px' 65 | }, 66 | { 67 | offset: 0.5, 68 | height: '100px', 69 | width: '100px', 70 | marginTop: '20px' 71 | } 72 | ]; 73 | -------------------------------------------------------------------------------- /source/test/unit/fixtures/paused-animation.js: -------------------------------------------------------------------------------- 1 | export default { 2 | animationName: 'default-animation', 3 | animationPlayState: 'paused', 4 | animationTimingFunction: 'linear', 5 | animationDuration: '300ms' 6 | }; 7 | -------------------------------------------------------------------------------- /source/test/unit/fixtures/prefixes.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | prefix: 'Moz', 4 | unprefixed: 'transition', 5 | prefixed: 'MozTransition', 6 | available: 'animationName', 7 | style: { 8 | animationName: null, 9 | MozTransition: null 10 | } 11 | }, 12 | { 13 | prefix: 'Webkit', 14 | unprefixed: 'transition', 15 | prefixed: 'WebkitTransition', 16 | available: 'transitionDuration', 17 | style: { 18 | transitionDuration: null, 19 | WebkitTransition: null 20 | } 21 | }, 22 | { 23 | prefix: 'ms', 24 | unprefixed: 'transition', 25 | prefixed: 'msTransition', 26 | available: 'borderRadius', 27 | style: { 28 | borderRadius: null, 29 | msTransition: null 30 | } 31 | } 32 | ]; 33 | -------------------------------------------------------------------------------- /source/test/unit/fixtures/running-animation.js: -------------------------------------------------------------------------------- 1 | export default { 2 | animationName: 'default-animation', 3 | animationPlayState: 'running', 4 | animationTimingFunction: 'linear', 5 | animationDuration: '.3s' 6 | }; 7 | -------------------------------------------------------------------------------- /source/test/unit/fixtures/simple-animation-declaration.js: -------------------------------------------------------------------------------- 1 | import cssStyleRules from '../stubs/css-style-rules'; 2 | 3 | export default [{ 4 | cssRules: [{ 5 | name: 'default-animation', 6 | type: 7, 7 | cssRules: [ 8 | { 9 | keyText: '0%', 10 | style: cssStyleRules({ 11 | height: '0', 12 | width: '0', 13 | length: 2 14 | }) 15 | }, 16 | { 17 | keyText: '100%', 18 | style: cssStyleRules({ 19 | height: '100px', 20 | width: '100px', 21 | length: 2 22 | }) 23 | } 24 | ] 25 | }] 26 | }]; 27 | 28 | export const keyframes = [ 29 | { 30 | offset: 0, 31 | height: '0', 32 | width: '0' 33 | }, 34 | { 35 | offset: 1, 36 | height: '100px', 37 | width: '100px' 38 | } 39 | ]; 40 | 41 | export const animation = [ 42 | { 43 | offset: 0, 44 | height: '0', 45 | width: '0' 46 | }, 47 | { 48 | offset: 1, 49 | height: '100px', 50 | width: '100px' 51 | } 52 | ]; 53 | -------------------------------------------------------------------------------- /source/test/unit/fixtures/slow-animation.js: -------------------------------------------------------------------------------- 1 | export default { 2 | animationName: 'default-animation', 3 | animationPlayState: 'running', 4 | animationTimingFunction: 'linear', 5 | animationDuration: '1s' 6 | }; 7 | -------------------------------------------------------------------------------- /source/test/unit/get-animation-properties.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape'; 2 | import getAnimationProperties from '../../library/get-animation-properties.js'; 3 | 4 | import elementStub from './stubs/element.js'; 5 | import windowStub from './stubs/window.js'; 6 | import documentStub from './stubs/document.js'; 7 | 8 | import pausedAnimation from './fixtures/paused-animation'; 9 | import filledAnimation from './fixtures/filled-animation'; 10 | 11 | tape('get-animation-properties', t => { 12 | t.throws(() => { 13 | getAnimationProperties(); 14 | }, 'should throw when called without arguments'); 15 | 16 | t.doesNotThrow(() => { 17 | getAnimationProperties(elementStub, windowStub, documentStub); 18 | }, 'should not throw when called with arguments'); 19 | 20 | const pausedElement = { 21 | ...elementStub, 22 | style: { 23 | ...elementStub.style, 24 | ...pausedAnimation 25 | } 26 | }; 27 | 28 | const pausedProperties = getAnimationProperties(pausedElement, windowStub, documentStub); 29 | 30 | t.deepEqual( 31 | Object.keys(pausedProperties), 32 | [ 33 | 'name', 34 | 'duration', 35 | 'iterationCount', 36 | 'timingFunction', 37 | 'fillMode', 38 | 'playState', 39 | 'delay' 40 | ], 41 | 'should return an object with the expected property values'); 42 | 43 | t.deepEqual( 44 | pausedProperties, 45 | { 46 | name: 'default-animation', 47 | duration: '300ms', 48 | delay: undefined, 49 | iterationCount: undefined, 50 | timingFunction: 'linear', 51 | fillMode: undefined, 52 | playState: 'paused' 53 | }, 54 | 'should return an object with the expected property values'); 55 | 56 | const filledElement = { 57 | ...elementStub, 58 | style: { 59 | ...elementStub.style, 60 | ...filledAnimation 61 | } 62 | }; 63 | 64 | const filledProperties = getAnimationProperties(filledElement, windowStub, documentStub); 65 | 66 | t.deepEqual( 67 | filledProperties, 68 | { 69 | name: 'filled-animation', 70 | duration: '1s', 71 | delay: '.5s', 72 | iterationCount: '10', 73 | timingFunction: 'linear', 74 | fillMode: 'both', 75 | playState: 'running' 76 | }, 77 | 'should return an object with the expected property values'); 78 | 79 | t.end(); 80 | }); 81 | -------------------------------------------------------------------------------- /source/test/unit/get-css-rules.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape'; 2 | 3 | import getCSSRules from '../../library/get-css-rules.js'; 4 | 5 | import simple from './fixtures/simple-animation-declaration.js'; 6 | 7 | import keyWord from './fixtures/keyword-animation-declaration.js'; 8 | 9 | import crossDomain, { 10 | animation as crossDomainAnimationDefinition 11 | } from './fixtures/cross-domain-stylesheets.js'; 12 | 13 | import crossDomainUnsafe, { 14 | animation as crossDomainUnsafeAnimationDefinition 15 | } from './fixtures/cross-domain-unsafe-stylesheets.js'; 16 | 17 | tape('get-css-rules', t => { 18 | t.ok( 19 | Array.isArray(getCSSRules(simple[0])), 20 | 'should return an array for simple-stylesheet'); 21 | 22 | t.ok( 23 | Array.isArray(getCSSRules(keyWord[0])), 24 | 'should return an array for keyword-stylesheet'); 25 | 26 | t.ok( 27 | Array.isArray(getCSSRules(crossDomain)), 28 | 'should return an array for cross-domain-stylesheet'); 29 | 30 | t.ok( 31 | Array.isArray(getCSSRules(crossDomainUnsafe)), 32 | 'should return an array for cross-domain-unsafe-stylesheet'); 33 | 34 | t.doesNotThrow( 35 | () => getCSSRules(simple[0]), 36 | 'should not fail for simple-stylesheet' 37 | ); 38 | 39 | t.doesNotThrow( 40 | () => getCSSRules(keyWord[0]), 41 | 'should not fail for keyword-stylesheet' 42 | ); 43 | 44 | t.doesNotThrow( 45 | () => getCSSRules(crossDomain[0]), 46 | 'should not fail for cross-domain-stylesheet' 47 | ); 48 | 49 | t.doesNotThrow( 50 | () => getCSSRules(crossDomainUnsafe[0]), 51 | 'should not fail for cross-domain-unsafe-stylesheet' 52 | ); 53 | 54 | t.deepEqual( 55 | getCSSRules(simple[0]), 56 | simple[0].cssRules, 57 | 'returns correct css rules for simple-stylesheet' 58 | ); 59 | 60 | t.deepEqual( 61 | getCSSRules(keyWord[0]), 62 | keyWord[0].cssRules, 63 | 'returns correct css rules for keyword-stylesheet' 64 | ); 65 | 66 | t.deepEqual( 67 | getCSSRules(crossDomain[0]), 68 | crossDomainAnimationDefinition, 69 | 'returns correct css rules for cross-domain-stylesheet' 70 | ); 71 | 72 | t.deepEqual( 73 | getCSSRules(crossDomainUnsafe[0]), 74 | crossDomainUnsafeAnimationDefinition, 75 | 'returns correct css rules for cross-domain-unsafe-stylesheet' 76 | ); 77 | 78 | t.end(); 79 | }); 80 | -------------------------------------------------------------------------------- /source/test/unit/get-defined-styles.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape'; 2 | 3 | import simpleAnimation from './fixtures/simple-animation-declaration.js'; 4 | const simpleAnimationKeyFrameRules = simpleAnimation[0].cssRules[0].cssRules; 5 | 6 | import getDefinedStyles from '../../library/get-defined-styles.js'; 7 | 8 | tape('get-defined-styles', t => { 9 | t.ok( 10 | typeof getDefinedStyles(simpleAnimationKeyFrameRules[0].style) === 'object', 11 | 'should return an object' 12 | ); 13 | 14 | t.deepEqual( 15 | getDefinedStyles(simpleAnimationKeyFrameRules[0].style), 16 | { 17 | height: '0', 18 | width: '0' 19 | }, 'should return the correct style map'); 20 | 21 | t.deepEqual( 22 | getDefinedStyles(simpleAnimationKeyFrameRules[1].style), 23 | { 24 | height: '100px', 25 | width: '100px' 26 | }, 'should return the correct style map'); 27 | 28 | t.end(); 29 | }); 30 | -------------------------------------------------------------------------------- /source/test/unit/get-keyframe-declarations.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape'; 2 | import windowStub from './stubs/window.js'; 3 | import documentStub from './stubs/document.js'; 4 | 5 | import simpleAnimation from './fixtures/simple-animation-declaration.js'; 6 | const simpleAnimationKeyFramesRule = simpleAnimation[0].cssRules; 7 | const simpleAnimationKeyFrameRules = simpleAnimation[0].cssRules[0].cssRules; 8 | 9 | import getKeyframeDeclarations from '../../library/get-keyframe-declarations.js'; 10 | 11 | tape('get-keyframe-declarations', t => { 12 | t.ok( 13 | Array.isArray( 14 | getKeyframeDeclarations( 15 | 'default-animation', 16 | simpleAnimationKeyFramesRule, 17 | windowStub, 18 | documentStub 19 | ) 20 | ), 21 | 'should return an array'); 22 | t.deepEqual( 23 | getKeyframeDeclarations( 24 | 'default-animation', 25 | simpleAnimationKeyFramesRule, 26 | windowStub, 27 | documentStub 28 | ), 29 | simpleAnimationKeyFrameRules, 30 | 'should return an array of keyframes'); 31 | t.end(); 32 | }); 33 | -------------------------------------------------------------------------------- /source/test/unit/get-keyframes.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape'; 2 | import documentStub from './stubs/document.js'; 3 | import keywordDocumentStub from './stubs/keyword-document'; 4 | import crossDomainDocumentStub from './stubs/cross-domain-document'; 5 | import elementStub from './stubs/element.js'; 6 | 7 | import pausedAnimation from './fixtures/paused-animation.js'; 8 | 9 | import { 10 | animation as simpleAnimationDefinition 11 | } from './fixtures/simple-animation-declaration.js'; 12 | 13 | import { 14 | animation as keyWordAnimationDefinition 15 | } from './fixtures/keyword-animation-declaration.js'; 16 | 17 | import { 18 | animation as crossDomainAnimationDefinition 19 | } from './fixtures/cross-domain-stylesheets.js'; 20 | 21 | import getKeyframes from '../../library/get-keyframes.js'; 22 | 23 | tape('get-keyframes', t => { 24 | const element = {...elementStub, styles: { 25 | ...elementStub.styles, 26 | ...pausedAnimation 27 | }}; 28 | 29 | t.ok( 30 | Array.isArray(getKeyframes(element, documentStub.styleSheets)), 31 | 'should return an array'); 32 | 33 | t.doesNotThrow( 34 | () => getKeyframes('default-animation', crossDomainDocumentStub.styleSheets), 35 | crossDomainAnimationDefinition, 36 | 'should not fail for cross-domain-stylesheet' 37 | ); 38 | 39 | t.deepEqual( 40 | getKeyframes('default-animation', crossDomainDocumentStub.styleSheets), 41 | crossDomainAnimationDefinition, 42 | 'should return empty keyframes for cross-domain-stylesheet' 43 | ); 44 | 45 | t.deepEqual( 46 | getKeyframes('default-animation', documentStub.styleSheets), 47 | simpleAnimationDefinition, 48 | 'should return the correct keyframes for simple-animation' 49 | ); 50 | 51 | { 52 | const actual = getKeyframes('default-animation', keywordDocumentStub.styleSheets); 53 | const expected = keyWordAnimationDefinition; 54 | 55 | t.deepEqual( 56 | actual, 57 | expected, 58 | 'should return the correct keyframes for keyword-animation' 59 | ); 60 | } 61 | 62 | t.end(); 63 | }); 64 | -------------------------------------------------------------------------------- /source/test/unit/get-player.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape'; 2 | import windowStub from './stubs/window.js'; 3 | import documentStub from './stubs/document.js'; 4 | import elementStub from './stubs/element.js'; 5 | 6 | import pausedAnimation from './fixtures/paused-animation'; 7 | import runningAnimation from './fixtures/running-animation'; 8 | import slowAnimation from './fixtures/slow-animation'; 9 | 10 | import getPlayer from '../../library/get-player.js'; 11 | 12 | tape('get-player', t => { 13 | t.ok( 14 | typeof getPlayer(elementStub, {}, windowStub, documentStub) === 'object', 15 | 'should return an object'); 16 | 17 | const pausedElement = { 18 | ...elementStub, 19 | style: { 20 | ...elementStub.style, 21 | ...pausedAnimation 22 | } 23 | }; 24 | 25 | const pausedInstance = getPlayer(pausedElement, {}, windowStub, documentStub); 26 | 27 | t.equals( 28 | pausedInstance.player.playState, 29 | 'paused', 30 | 'should return a paused AnimationPlayer instance' 31 | ); 32 | 33 | const runningElement = { 34 | ...elementStub, 35 | style: { 36 | ...elementStub.style, 37 | ...runningAnimation 38 | } 39 | }; 40 | 41 | const runningInstance = getPlayer(runningElement, {}, windowStub, documentStub); 42 | 43 | t.equals( 44 | runningInstance.player.playState, 45 | 'running', 46 | 'should return a running AnimationPlayer instance' 47 | ); 48 | 49 | const fastInstance = getPlayer(runningElement, {}, windowStub, documentStub); 50 | 51 | t.equals( 52 | fastInstance.duration, 53 | 300, 54 | 'should return a running AnimationPlayer with correct duration' 55 | ); 56 | 57 | const slowElement = { 58 | ...elementStub, 59 | style: { 60 | ...elementStub.style, 61 | ...slowAnimation 62 | } 63 | }; 64 | 65 | const slowInstance = getPlayer(slowElement, {}, windowStub, documentStub); 66 | 67 | t.equals( 68 | slowInstance.duration, 69 | 1000, 70 | 'should return a running AnimationPlayer with correct duration' 71 | ); 72 | 73 | t.end(); 74 | }); 75 | -------------------------------------------------------------------------------- /source/test/unit/get-vendor-prefix.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape'; 2 | 3 | import windowStub from './stubs/window.js'; 4 | import documentStub from './stubs/document.js'; 5 | import prefixes from './fixtures/prefixes.js'; 6 | 7 | import getVendorPrefix, {prefix} from '../../library/get-vendor-prefix'; 8 | 9 | function getPrefixedDocument(prefix) { 10 | return { 11 | ...documentStub, 12 | body: { 13 | style: { 14 | ...documentStub.body.style, 15 | ...prefix.style 16 | } 17 | } 18 | }; 19 | } 20 | 21 | tape('get-vendor-prefix', t => { 22 | t.ok( 23 | typeof getVendorPrefix(windowStub, documentStub) === 'string', 24 | 'should return a string' 25 | ); 26 | 27 | t.comment('... given a document without prefixed style properties'); 28 | t.equals( 29 | getVendorPrefix(windowStub, documentStub), 30 | '', 31 | 'it should return an empty string' 32 | ); 33 | 34 | t.comment('... given a document with prefixed style properties'); 35 | 36 | prefixes.forEach(prefix => { 37 | const prefixedDocumentStub = getPrefixedDocument(prefix); 38 | 39 | t.equals( 40 | getVendorPrefix(windowStub, prefixedDocumentStub), 41 | prefix.prefix, 42 | 'should return the correct prefix' 43 | ); 44 | }); 45 | 46 | t.end(); 47 | }); 48 | 49 | tape('prefix', t => { 50 | t.ok( 51 | typeof prefix('', windowStub, documentStub) === 'string', 52 | 'should return a string' 53 | ); 54 | t.end(); 55 | 56 | t.comment('... given a document without prefixed style properties'); 57 | t.equals( 58 | prefix('animationName', windowStub, documentStub), 59 | 'animationName', 60 | 'it should return the property unaltered' 61 | ); 62 | 63 | prefixes.forEach(prefixData => { 64 | const prefixedDocumentStub = getPrefixedDocument(prefixData); 65 | 66 | t.equals( 67 | prefix(prefixData.unprefixed, windowStub, prefixedDocumentStub), 68 | prefixData.prefixed, 69 | 'should return the correct prefixed property' 70 | ); 71 | 72 | t.equals( 73 | prefix(prefixData.available, windowStub, prefixedDocumentStub), 74 | prefixData.available, 75 | 'should return the unprefixed property if available' 76 | ); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /source/test/unit/index.js: -------------------------------------------------------------------------------- 1 | // obsolete when switching to phantomjs2 / jsdom / zombie 2 | import 'phantomjs-function-bind-polyfill'; 3 | 4 | import './create-trap'; 5 | import './convert-animation-iterations'; 6 | import './get-animation-properties'; 7 | import './get-css-rules'; 8 | import './get-defined-styles'; 9 | import './get-keyframe-declarations'; 10 | import './get-keyframes'; 11 | import './get-player'; 12 | import './get-vendor-prefix'; 13 | import './init-player'; 14 | 15 | import './jogwheel-constructor'; 16 | import './jogwheel-create'; 17 | import './jogwheel-instance-pause'; 18 | import './jogwheel-instance-play'; 19 | import './jogwheel-instance-seek'; 20 | import './jogwheel-instance-unplug'; 21 | import './remove-vendor-prefix'; 22 | -------------------------------------------------------------------------------- /source/test/unit/init-player.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape'; 2 | import windowStub from './stubs/window.js'; 3 | import documentStub from './stubs/document.js'; 4 | import elementStub, {unpolyfilledElementStub} from './stubs/element.js'; 5 | import initPlayer from '../../library/init-player.js'; 6 | 7 | tape('init-player', t => { 8 | t.ok( 9 | typeof initPlayer(elementStub, 10 | [], 11 | {}, 12 | () => {}, 13 | windowStub, documentStub) === 'object', 14 | 'should return an object'); 15 | 16 | t.ok( 17 | typeof initPlayer(unpolyfilledElementStub, 18 | [], 19 | {}, 20 | () => {}, 21 | windowStub, documentStub) === 'object', 22 | 'should return an object when initializing on element without animate method'); 23 | 24 | const stub = {...elementStub, style: {}}; 25 | 26 | // overwrite element.animate 27 | stub.animate = function () { 28 | const element = this; 29 | return { 30 | play() { 31 | element.style.opacity = '0.3'; 32 | }, 33 | pause() {} 34 | }; 35 | }; 36 | 37 | let proxied = {}; 38 | 39 | initPlayer(stub, 40 | [], 41 | { 42 | render(el, style) { 43 | proxied = {el, style}; 44 | } 45 | }, 46 | undefined, 47 | windowStub, 48 | documentStub); 49 | 50 | t.notEqual(proxied.style, stub.style, 51 | 'should proxy style property assignments if called with options.render'); 52 | 53 | // abort window.requestAnimationFrame 54 | const prev = windowStub.requestAnimationFrame; 55 | windowStub.requestAnimationFrame = () => {}; 56 | windowStub.requestAnimationFrame = prev; 57 | 58 | const nativeStub = {...elementStub, style: {}}; 59 | 60 | // overwrite element.animate 61 | nativeStub.animate = function () { 62 | if (this !== nativeStub) { 63 | throw new Error('Illegal invocation'); 64 | } 65 | 66 | const element = this; 67 | 68 | return { 69 | play() { 70 | element.style.opacity = '0.3'; 71 | }, 72 | pause() {} 73 | }; 74 | }; 75 | 76 | t.doesNotThrow(() => { 77 | initPlayer(nativeStub, 78 | [], 79 | { 80 | render(el, style) { 81 | proxied = {el, style}; 82 | } 83 | }, 84 | undefined, 85 | windowStub, 86 | documentStub); 87 | }, 'should not fail when element.animate fails with "Illegal invocation"'); 88 | 89 | // overwrite element.animate 90 | nativeStub.animate = function () { 91 | throw new Error(); 92 | }; 93 | 94 | t.throws(() => { 95 | initPlayer(nativeStub, 96 | [], 97 | { 98 | render(el, style) { 99 | proxied = {el, style}; 100 | } 101 | }, 102 | undefined, 103 | windowStub, 104 | documentStub); 105 | }, 'should fail when element.animate fails with other error'); 106 | 107 | // abort window.requestAnimationFrame 108 | windowStub.requestAnimationFrame = () => {}; 109 | t.end(); 110 | }); 111 | -------------------------------------------------------------------------------- /source/test/unit/jogwheel-constructor.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape'; 2 | 3 | import windowStub from './stubs/window.js'; 4 | import documentStub from './stubs/document.js'; 5 | import elementStub from './stubs/element.js'; 6 | import nodeListStub from './stubs/node-list.js'; 7 | 8 | import JogWheel from '../../library/index.js'; 9 | 10 | tape('constructor', t => { 11 | t.throws(() => { 12 | new JogWheel(undefined, windowStub, documentStub); // eslint-disable-line no-new 13 | }, 'should throw when called without element'); 14 | 15 | t.doesNotThrow(() => { 16 | new JogWheel(elementStub, {}, windowStub, documentStub); // eslint-disable-line no-new 17 | }, 'should not throw when called with element'); 18 | 19 | t.doesNotThrow(() => { 20 | new JogWheel(nodeListStub, {}, windowStub, documentStub); // eslint-disable-line no-new 21 | }, 'should not throw when called with node-list'); 22 | 23 | t.end(); 24 | }); 25 | -------------------------------------------------------------------------------- /source/test/unit/jogwheel-create.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape'; 2 | 3 | import windowStub from './stubs/window.js'; 4 | import documentStub from './stubs/document.js'; 5 | import elementStub from './stubs/element.js'; 6 | import jogwheel from '../../library/index.js'; 7 | 8 | tape('jogwheel.create', t => { 9 | t.ok( 10 | jogwheel.create(elementStub, {}, windowStub, documentStub) instanceof jogwheel, 11 | 'should return an instance of jogwheel'); 12 | 13 | t.end(); 14 | }); 15 | -------------------------------------------------------------------------------- /source/test/unit/jogwheel-instance-pause.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape'; 2 | 3 | import windowStub from './stubs/window.js'; 4 | import documentStub from './stubs/document.js'; 5 | import elementStub from './stubs/element.js'; 6 | 7 | import runningAnimation from './fixtures/running-animation'; 8 | 9 | import jogwheel from '../../library/'; 10 | 11 | tape('instance.pause', t => { 12 | const instance = jogwheel.create(elementStub, {}, windowStub, documentStub); 13 | t.ok( 14 | instance.pause() === instance, 15 | 'should return the jogwheel instance' 16 | ); 17 | t.end(); 18 | 19 | const runningElement = { 20 | ...elementStub, 21 | style: { 22 | ...elementStub.style, 23 | ...runningAnimation 24 | } 25 | }; 26 | 27 | const runningInstance = jogwheel.create(runningElement, {}, windowStub, documentStub); 28 | runningInstance.pause(); 29 | 30 | t.equals( 31 | runningInstance.playState, 32 | 'paused', 33 | 'should pause a running jogwheel instance' 34 | ); 35 | }); 36 | -------------------------------------------------------------------------------- /source/test/unit/jogwheel-instance-play.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape'; 2 | 3 | import windowStub from './stubs/window.js'; 4 | import documentStub from './stubs/document.js'; 5 | import elementStub from './stubs/element.js'; 6 | 7 | import pausedAnimation from './fixtures/paused-animation'; 8 | 9 | import jogwheel from '../../library/'; 10 | 11 | tape('instance.play', t => { 12 | const instance = jogwheel.create(elementStub, {}, windowStub, documentStub); 13 | t.ok( 14 | instance.play() === instance, 15 | 'should return the jogwheel instance' 16 | ); 17 | 18 | const pausedElement = { 19 | ...elementStub, 20 | style: { 21 | ...elementStub.style, 22 | ...pausedAnimation 23 | } 24 | }; 25 | 26 | const pausedInstance = jogwheel.create(pausedElement, {}, windowStub, documentStub); 27 | pausedInstance.play(); 28 | 29 | t.equals( 30 | pausedInstance.playState, 31 | 'running', 32 | 'should start a paused jogwheel instance' 33 | ); 34 | 35 | t.end(); 36 | }); 37 | -------------------------------------------------------------------------------- /source/test/unit/jogwheel-instance-seek.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape'; 2 | 3 | import windowStub from './stubs/window.js'; 4 | import documentStub from './stubs/document.js'; 5 | import elementStub from './stubs/element.js'; 6 | 7 | import runningAnimation from './fixtures/running-animation'; 8 | import pausedAnimation from './fixtures/paused-animation'; 9 | import slowAnimation from './fixtures/slow-animation'; 10 | 11 | import jogwheel from '../../library/'; 12 | 13 | tape('instance.seek', t => { 14 | const element = { 15 | ...elementStub, 16 | style: { 17 | ...elementStub.style, 18 | ...pausedAnimation 19 | } 20 | }; 21 | 22 | const instance = jogwheel.create(element, {}, windowStub, documentStub); 23 | t.ok( 24 | instance.seek() === instance, 25 | 'should return the jogwheel instance' 26 | ); 27 | t.end(); 28 | 29 | instance.seek(0.5); 30 | 31 | t.equals( 32 | instance.progress, 33 | 0.5, 34 | 'should jump to correct progress for paused-animation' 35 | ); 36 | 37 | instance.players.forEach(player => { 38 | t.equals( 39 | player.currentTime, 40 | 150, 41 | 'should jump each player instance to correct currentTime for paused-animation' 42 | ); 43 | }); 44 | 45 | instance.seek(0); 46 | 47 | t.equals( 48 | instance.progress, 49 | 0, 50 | 'should jump to correct progress for paused-animation' 51 | ); 52 | 53 | instance.players.forEach(player => { 54 | t.equals( 55 | player.currentTime, 56 | 0, 57 | 'should jump each player instance to correct currentTime for paused-animation' 58 | ); 59 | }); 60 | 61 | const runningElement = { 62 | ...elementStub, 63 | style: { 64 | ...elementStub.style, 65 | ...runningAnimation 66 | } 67 | }; 68 | 69 | const runningInstance = jogwheel.create(runningElement, {}, windowStub, documentStub); 70 | runningInstance.seek(1); 71 | 72 | t.equals( 73 | runningInstance.progress, 74 | 1, 75 | 'should jump to correct progress for running-animation' 76 | ); 77 | 78 | runningInstance.seek(0.2); 79 | 80 | t.equals( 81 | runningInstance.progress, 82 | 0.2, 83 | 'should jump to correct progress for running-animation' 84 | ); 85 | 86 | const slowElement = { 87 | ...elementStub, 88 | style: { 89 | ...elementStub.style, 90 | ...slowAnimation 91 | } 92 | }; 93 | 94 | const slowInstance = jogwheel.create(slowElement, {}, windowStub, documentStub); 95 | slowInstance.seek(0.5); 96 | 97 | t.equals( 98 | slowInstance.progress, 99 | 0.5, 100 | 'should jump to correct progress for slow-animation' 101 | ); 102 | 103 | slowInstance.seek(0.75); 104 | 105 | t.equals( 106 | slowInstance.progress, 107 | 0.75, 108 | 'should jump to correct progress for slow-animation' 109 | ); 110 | }); 111 | -------------------------------------------------------------------------------- /source/test/unit/jogwheel-instance-unplug.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape'; 2 | 3 | import windowStub from './stubs/window.js'; 4 | import documentStub from './stubs/document.js'; 5 | import elementStub from './stubs/element.js'; 6 | 7 | import jogwheel from '../../library/'; 8 | 9 | tape('instance.unplug', t => { 10 | const instance = jogwheel.create(elementStub, {}, windowStub, documentStub); 11 | t.ok( 12 | instance.unplug(windowStub, documentStub) === instance, 13 | 'should return the jogwheel instance' 14 | ); 15 | t.end(); 16 | }); 17 | -------------------------------------------------------------------------------- /source/test/unit/remove-vendor-prefix.js: -------------------------------------------------------------------------------- 1 | import tape from 'tape'; 2 | 3 | import removeVendorPrefix from '../../library/remove-vendor-prefix'; 4 | 5 | tape('remove-vendor-prefix', t => { 6 | t.equals( 7 | typeof removeVendorPrefix(), 8 | 'string', 9 | 'should return a string' 10 | ); 11 | 12 | t.equals( 13 | removeVendorPrefix('border-radius'), 14 | 'border-radius', 15 | 'should return an unprefixed property unaltered' 16 | ); 17 | 18 | t.equals( 19 | removeVendorPrefix('-webkit-border-radius'), 20 | 'border-radius', 21 | 'should return the unprefixed property' 22 | ); 23 | 24 | t.equals( 25 | removeVendorPrefix('-moz-border-radius'), 26 | 'border-radius', 27 | 'should return the unprefixed property' 28 | ); 29 | 30 | t.equals( 31 | removeVendorPrefix('-ms-border-radius'), 32 | 'border-radius', 33 | 'should return the unprefixed property' 34 | ); 35 | 36 | t.end(); 37 | }); 38 | -------------------------------------------------------------------------------- /source/test/unit/stubs/cross-domain-document.js: -------------------------------------------------------------------------------- 1 | import {default as styleSheets} from '../fixtures/cross-domain-stylesheets'; 2 | export default {styleSheets}; 3 | -------------------------------------------------------------------------------- /source/test/unit/stubs/css-style-rules.js: -------------------------------------------------------------------------------- 1 | export default function (rules) { 2 | return { 3 | ...rules, 4 | item(index) { 5 | return Object.keys(this)[index]; 6 | }, 7 | getPropertyValue(key) { 8 | return this[key]; 9 | } 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /source/test/unit/stubs/document.js: -------------------------------------------------------------------------------- 1 | import simpleAnimation from '../fixtures/simple-animation-declaration'; 2 | 3 | export default { 4 | styleSheets: simpleAnimation, 5 | body: { 6 | style: { 7 | animationName: null, 8 | animationDuration: null, 9 | animationIterations: null, 10 | animationEasing: null, 11 | animationFill: null, 12 | animationPlayState: null 13 | } 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /source/test/unit/stubs/element.js: -------------------------------------------------------------------------------- 1 | import playerStub from './player.js'; 2 | 3 | const elementStub = { 4 | style: { 5 | }, 6 | animate(_, options) { 7 | return playerStub(options); 8 | }, 9 | getAnimations() { 10 | return []; 11 | } 12 | }; 13 | 14 | export const unpolyfilledElementStub = { 15 | style: {} 16 | }; 17 | 18 | export default elementStub; 19 | -------------------------------------------------------------------------------- /source/test/unit/stubs/keyword-document.js: -------------------------------------------------------------------------------- 1 | import keywordAnimation from '../fixtures/keyword-animation-declaration'; 2 | 3 | export default { 4 | styleSheets: keywordAnimation 5 | }; 6 | -------------------------------------------------------------------------------- /source/test/unit/stubs/node-list.js: -------------------------------------------------------------------------------- 1 | import windowStub from './window.js'; 2 | import elementStub from './element.js'; 3 | 4 | const nodeListStub = new windowStub.NodeList([ 5 | {...elementStub}, 6 | {...elementStub} 7 | ]); 8 | 9 | export default nodeListStub; 10 | -------------------------------------------------------------------------------- /source/test/unit/stubs/player.js: -------------------------------------------------------------------------------- 1 | const playerStub = options => { 2 | const _state = { 3 | currentTime: 0, 4 | activeDuration: 0, 5 | playState: 'running' 6 | }; 7 | 8 | _state.activeDuration = options.duration; 9 | 10 | return { 11 | play() { 12 | _state.playState = 'running'; 13 | }, 14 | pause() { 15 | _state.playState = 'paused'; 16 | }, 17 | cancel() { 18 | _state.playState = 'canceled'; 19 | }, 20 | effect: { 21 | get currentTime() { 22 | return _state.currentTime; 23 | }, 24 | get activeDuration() { 25 | return _state.activeDuration; 26 | } 27 | }, 28 | get playState() { 29 | return _state.playState; 30 | } 31 | }; 32 | }; 33 | 34 | export default playerStub; 35 | -------------------------------------------------------------------------------- /source/test/unit/stubs/window.js: -------------------------------------------------------------------------------- 1 | const _media = { 2 | 3 | }; 4 | 5 | const windowStub = { 6 | matchMedia(mediaRule) { 7 | return _media[mediaRule] === true; 8 | }, 9 | getComputedStyle(element) { 10 | return element.style || {}; 11 | }, 12 | requestAnimationFrame(fn) { 13 | setTimeout(fn, 15); 14 | }, 15 | NodeList(elements) { 16 | elements.forEach((element, index) => this[index] = element); 17 | this.length = elements.length; 18 | } 19 | }; 20 | 21 | export default windowStub; 22 | -------------------------------------------------------------------------------- /tasks/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["xo"], 3 | "rules": { 4 | "babel/object-shorthand": 0 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tasks/build.js: -------------------------------------------------------------------------------- 1 | var sequence = require('gulp-sequence'); 2 | 3 | module.exports = function (gulp, paths, options) { 4 | options = Object.assign({}, options, {fails: true}); 5 | 6 | var args = [].slice.call(arguments); 7 | 8 | var task = require('./helpers/task')(gulp); 9 | var clean = require('./clean').apply(null, args); 10 | var lint = require('./lint').apply(null, args); 11 | var documentation = require('./documentation').apply(null, args); 12 | var transpile = require('./transpile').apply(null, args); 13 | var copy = require('./copy').apply(null, args); 14 | var copyExample = require('./copy-example').apply(null, args); 15 | var copyStatic = require('./static').apply(null, args); 16 | var html = require('./html').apply(null, args); 17 | var css = require('./css').apply(null, args); 18 | var testCSS = require('./test-css').apply(null, args); 19 | var pack = require('./pack').apply(null, args); 20 | var test = require('./test').apply(null, args); 21 | 22 | return function build(done) { 23 | return sequence( 24 | task(clean), 25 | [ 26 | task(copy), 27 | task(copyStatic, 'copy-static'), 28 | task(copyExample, 'copy-example'), 29 | task(lint), 30 | task(css), 31 | task(testCSS, 'test-css'), 32 | task( 33 | sequence( 34 | task(documentation), 35 | task(html) 36 | ), 37 | 'docs-html'), 38 | task(sequence( 39 | task(transpile), 40 | task(sequence( 41 | [ 42 | task(pack), 43 | task(test) 44 | ] 45 | ), 'test-pack')), 46 | 'transpile-test-pack') 47 | ] 48 | )(done); 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /tasks/clean.js: -------------------------------------------------------------------------------- 1 | var del = require('del'); 2 | 3 | module.exports = function (gulp, paths) { 4 | return function clean() { 5 | /* @desc clean all build results from project */ 6 | var cleanPaths = [paths.clean.documentation, paths.clean.distribution]; 7 | return del(cleanPaths); 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /tasks/copy-example.js: -------------------------------------------------------------------------------- 1 | module.exports = function (gulp, paths) { 2 | return function copyExamples() { 3 | /* @desc copy static files to distribution */ 4 | return gulp.src(paths.source.example) 5 | .pipe(gulp.dest(paths.target.example)); 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /tasks/copy.js: -------------------------------------------------------------------------------- 1 | module.exports = function (gulp, paths) { 2 | return function copy() { 3 | /* @desc copy static files to distribution */ 4 | return gulp.src(paths.source.static) 5 | .pipe(gulp.dest(paths.target.distribution)); 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /tasks/css.js: -------------------------------------------------------------------------------- 1 | var next = require('gulp-cssnext'); 2 | 3 | module.exports = function (gulp, paths) { 4 | return function postcss() { 5 | /* @desc postprocess css sources */ 6 | return gulp.src(paths.source['public-css']) 7 | .pipe(next()) 8 | .pipe(gulp.dest(paths.target.public)); 9 | }; 10 | }; 11 | -------------------------------------------------------------------------------- /tasks/documentation.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const merge = require('lodash.merge'); 5 | const async = require('async'); 6 | const documentationjs = require('documentation'); 7 | const shell = require('shelljs'); 8 | 9 | const cached = require('gulp-cached'); 10 | const remember = require('gulp-remember'); 11 | 12 | const data = require('gulp-data'); 13 | const template = require('gulp-template'); 14 | const extension = require('gulp-ext-replace'); 15 | const rename = require('gulp-rename'); 16 | const globby = require('globby'); 17 | const request = require('sync-request'); 18 | 19 | const pkg = require('../package'); 20 | const header = require('./partials/header'); 21 | const footer = require('./partials/footer'); 22 | const badges = require('./partials/badges'); 23 | 24 | module.exports = function (gulp, paths) { 25 | const props = { 26 | paths: paths, 27 | gulp: gulp, 28 | pkg: merge({}, pkg, pkg.config.documentation), 29 | helpers: { 30 | 31 | } 32 | }; 33 | 34 | props.partials = { 35 | header: header(props), 36 | footer: footer(props), 37 | badges: badges(props) 38 | }; 39 | 40 | function getApiDocumentation(entry, formats, callback) { 41 | async.waterfall([ 42 | function (cb) { 43 | documentationjs(entry, { 44 | private: false, 45 | github: false 46 | }, cb); 47 | }, 48 | function (comments, cb) { 49 | async.reduce(formats, {}, (result, format, callback) => { 50 | documentationjs.formats[format](comments, {}, (err, formatted) => { 51 | if (err) { 52 | return callback(err); 53 | } 54 | result[format] = formatted; 55 | callback(err, result); 56 | }); 57 | }, cb); 58 | } 59 | ], callback); 60 | } 61 | 62 | return function documentation(done) { 63 | /* @desc build markdown from sources */ 64 | getApiDocumentation(paths.source.entry, ['md', 'json', 'html'], (err, docs) => { 65 | if (err) { 66 | return done(err); 67 | } 68 | 69 | props.pkg.tag = props.pkg.version ? 70 | `v${props.pkg.version}` : 71 | shell 72 | .exec('git describe --abbrev=0 --tags', {silent: true}) 73 | .output.split('\n')[0]; 74 | 75 | const exampleFiles = globby.sync(paths.source.example); 76 | 77 | const examples = exampleFiles.reduce((registry, exampleFile) => { 78 | const name = path.basename(exampleFile, path.extname(exampleFile)); 79 | const parsed = path.parse(path.relative(paths.target.root, exampleFile)); 80 | parsed.ext = '.html'; 81 | 82 | const uri = path.format(parsed) 83 | .split(path.sep).slice(1).join('/'); 84 | 85 | const host = props.pkg.config.documentation.host; 86 | let url = `https://${host}/${props.pkg.name}/${uri}`; 87 | 88 | const response = request('POST', 'https://git.io', { 89 | body: `url=${url}` 90 | }); 91 | 92 | url = response.headers.location || url; 93 | 94 | const amendment = {}; 95 | amendment[name] = url; 96 | 97 | return Object.assign(registry, amendment); 98 | }, {}); 99 | 100 | gulp.src(paths.source.documentation) 101 | .pipe(cached('documentation')) 102 | .pipe(data({ 103 | props: props, 104 | docs: docs, 105 | examples: examples 106 | })) 107 | .pipe(template()) 108 | .pipe(remember('documentation')) 109 | .pipe(extension('.md')) 110 | .pipe(rename(pathInfo => { 111 | if (pathInfo.basename[0] === '_') { 112 | pathInfo.basename = pathInfo.basename.slice(1); 113 | } 114 | })) 115 | .pipe(gulp.dest(paths.target.root)) 116 | .on('end', done); 117 | }); 118 | }; 119 | }; 120 | -------------------------------------------------------------------------------- /tasks/helpers/on-error.js: -------------------------------------------------------------------------------- 1 | var util = require('gulp-util'); 2 | var notifier = require('node-notifier'); 3 | 4 | var defaults = { 5 | fails: true, 6 | notifies: false 7 | }; 8 | 9 | module.exports = function (options) { 10 | options = Object.assign({}, defaults, options); 11 | 12 | return function (err) { 13 | if (!err) { 14 | return; 15 | } 16 | 17 | if (options.notifies) { 18 | notifier.notify({ 19 | title: err.plugin, 20 | message: err.message, 21 | sound: true 22 | }); 23 | } 24 | 25 | if (!err.logged) { 26 | var plugin = util.colors.red('[' + err.plugin + ']'); 27 | var meta = util.colors.grey(' [' + err.fileName + ':' + err.lineNumber + ']'); 28 | util.log([plugin, err.message, meta].join(' ')); 29 | } 30 | 31 | if (err.stack) { 32 | util.log(util.colors.grey(err.stack)); 33 | } 34 | 35 | if (options.fails) { 36 | throw err; 37 | } 38 | 39 | if (this.end) { 40 | this.end(); 41 | } 42 | }; 43 | }; 44 | -------------------------------------------------------------------------------- /tasks/helpers/task.js: -------------------------------------------------------------------------------- 1 | var functionName = require('fn-name'); 2 | 3 | module.exports = function () { 4 | var environment = [].slice.call(arguments); 5 | var gulp = environment[0]; 6 | /** 7 | * Helper to create gulp tasks from named functions on the fly 8 | * Allows for keeping tasks around as functions and keeping them private 9 | * @param {Function} fn named function that should be used as gulp task 10 | * @param {string} forced optional forced name for the task to create 11 | * @return {string} name of the created task 12 | */ 13 | return function task(fn, forced) { 14 | var name = forced || functionName(fn); 15 | var names = Array.isArray(name) ? name : [name]; 16 | 17 | names.forEach(function (name) { 18 | gulp.task(name, fn); 19 | }); 20 | 21 | return name; 22 | }; 23 | }; 24 | -------------------------------------------------------------------------------- /tasks/html.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | 4 | const remark = require('remark'); 5 | const html = require('remark-html'); 6 | const inline = require('remark-inline-links'); 7 | const slug = require('remark-slug'); 8 | const highlight = require('remark-highlight.js'); 9 | const gemoji = require('remark-gemoji'); 10 | const emojiParser = require('emoji-parser'); 11 | const visit = require('unist-util-visit'); 12 | const VFile = require('vfile'); 13 | const through = require('through2'); 14 | const rename = require('gulp-rename'); 15 | const globby = require('globby'); 16 | const humanize = require('string-humanize'); 17 | const pkg = require('../package.json'); 18 | const layout = require('./partials/page-layout'); 19 | 20 | const emojiBase = './.tmp/emoji/'; 21 | const emoji = emojiParser.init(emojiBase).update(); 22 | 23 | module.exports = function (gulp, paths) { 24 | 'use strict'; 25 | function toHtml() { 26 | const rewrite = function () { 27 | return function (ast, file) { 28 | const base = path.relative(file.filePath(), path.resolve('./')); 29 | pkg.staticBase = base === '..' ? '.' : path.dirname(base); 30 | 31 | visit(ast, 'link', node => { 32 | // Rewrite local md links to html files in md 33 | if (node.url[0] === '.') { 34 | node.url = node.url 35 | .replace('.md', '.html') 36 | .replace('readme', 'index'); 37 | } 38 | }); 39 | 40 | // Rewrite local md links to html files in inline html 41 | visit(ast, 'html', node => { 42 | node.value = node.value.replace(/href=\"\.\/(.*?)\.md\"/g, (match, basename) => { 43 | const name = basename.replace('readme', 'index'); 44 | return `href="./${name}.html"`; 45 | }); 46 | }); 47 | }; 48 | }; 49 | 50 | const processor = remark() 51 | .use(inline) 52 | .use(slug) 53 | .use(highlight) 54 | .use(rewrite) 55 | .use(gemoji) 56 | .use(html); 57 | 58 | const markdownFiles = globby.sync(paths.source.markdown); 59 | 60 | return through.obj((file, enc, cb) => { 61 | const isPlain = file.contents.indexOf('') === 0; 62 | 63 | if (isPlain) { 64 | return cb(null, file); 65 | } 66 | 67 | const vfile = new VFile({ 68 | contents: emoji.parse(file.contents.toString('utf-8'), '', (match, url, className, options) => { 69 | const name = match[1]; 70 | if (options.parser.list.indexOf(name) > -1) { 71 | const image = fs.readFileSync(path.resolve(emojiBase, `${name}.png`)); 72 | return ``; 73 | } 74 | return match; 75 | }), 76 | directory: path.dirname(file.path), 77 | filename: path.basename(file.path, path.extname(file.path)), 78 | extension: path.extname(file.path).slice(1) 79 | }); 80 | 81 | const result = processor.process(vfile); 82 | 83 | const navigationFiles = markdownFiles 84 | .map(markdownFile => { 85 | const absolutePath = path.resolve(markdownFile); 86 | const base = `${path.basename(markdownFile, path.extname(markdownFile))}.html`; 87 | const target = base === 'readme.html' ? path.join(markdownFile, '..', 'index.html') : path.join(path.dirname(markdownFile), base); 88 | const href = `${path.relative(file.path, target).slice(1)}`; 89 | const name = base === 'readme.html' ? humanize(path.dirname(target)) : humanize(path.basename(target)); 90 | const active = absolutePath === file.path; 91 | return href && name ? {href, name, active, path: absolutePath} : null; 92 | }) 93 | .filter(Boolean); 94 | 95 | const navigation = [ 96 | { 97 | name: 'Home', 98 | href: path.relative(file.path, './').slice(1) 99 | } 100 | ].concat(navigationFiles); 101 | 102 | const renderedResult = layout({ 103 | pkg: pkg, 104 | navigation: navigation, 105 | body: result, 106 | static: pkg.staticBase 107 | }); 108 | 109 | file.contents = new Buffer(renderedResult); 110 | cb(null, file); 111 | }); 112 | } 113 | 114 | return function html() { 115 | /* @desc compile markdown to html */ 116 | return gulp.src(paths.source.markdown) 117 | .pipe(toHtml()) 118 | .pipe(rename(info => { 119 | info.basename = info.basename === 'readme' ? 'index' : info.basename; 120 | info.extname = '.html'; 121 | })) 122 | .pipe(gulp.dest(paths.target.public)); 123 | }; 124 | }; 125 | -------------------------------------------------------------------------------- /tasks/lint.js: -------------------------------------------------------------------------------- 1 | var eslint = require('gulp-eslint'); 2 | var onError = require('./helpers/on-error'); 3 | 4 | module.exports = function (gulp, paths, options) { 5 | return function lint() { 6 | var fail = options.fails ? 'failOnError' : 'failAfterError'; 7 | 8 | /* @desc lint sources */ 9 | return gulp.src([paths.source.library, paths.source.test]) 10 | .pipe(eslint()) 11 | .pipe(eslint.format()) 12 | .pipe(eslint[fail]().on('error', onError(options))) 13 | .on('error', onError(options)); 14 | }; 15 | }; 16 | -------------------------------------------------------------------------------- /tasks/list.js: -------------------------------------------------------------------------------- 1 | var util = require('gulp-util'); 2 | 3 | module.exports = function (gulp) { 4 | return function list() { 5 | /* @desc list all public tasks */ 6 | var tasks = Object.keys(gulp.tasks || {}) 7 | .reduce(function (results, taskName) { 8 | var task = gulp.tasks[taskName]; 9 | var match = String(task.fn).match(/\/\*\s@desc\s(.*)?\s\*\//); 10 | 11 | return results.concat({ 12 | name: task.name, 13 | description: match ? match[1] : '', 14 | fn: task.fn 15 | }); 16 | }, []); 17 | 18 | var length = tasks.map(function (task) { 19 | return task.name.length; 20 | }).sort(function (a, b) { 21 | return b - a; 22 | })[0]; 23 | 24 | var format = util.colors.cyan.bold; 25 | 26 | util.log(''); 27 | util.log(util.colors.bold('Tasks')); 28 | 29 | tasks.forEach(function (task) { 30 | var aliases = tasks 31 | .filter(function (alias) { 32 | return alias !== task && alias.fn === task.fn; 33 | }) 34 | .map(function (alias) { 35 | return alias.name; 36 | }); 37 | 38 | var aliasList = aliases.length ? 'also: [' + aliases.join(', ') + ']' : ''; 39 | var spacing = new Array(length + 4 - task.name.length).join(' '); 40 | util.log(` ${format(task.name)}${spacing}– ${task.description} ${util.colors.bold.grey(aliasList)}`); 41 | }); 42 | util.log(''); 43 | }; 44 | }; 45 | -------------------------------------------------------------------------------- /tasks/pack.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var browserify = require('browserify'); 3 | var watchify = require('watchify'); 4 | var source = require('vinyl-source-stream'); 5 | 6 | module.exports = function (gulp, paths, options) { 7 | return function pack() { 8 | /* @desc pack js sources for browser consumption */ 9 | var relPath = path.relative('source/documentation', paths.source['public-js']); 10 | 11 | var bundler = browserify({ 12 | entries: paths.source['public-js'], 13 | transform: ['babelify'] 14 | }); 15 | 16 | if (options.watch) { 17 | bundler = watchify(bundler); 18 | } 19 | 20 | return bundler 21 | .bundle() 22 | .pipe(source(relPath)) 23 | .pipe(gulp.dest(paths.target.public)); 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /tasks/partials/badges.js: -------------------------------------------------------------------------------- 1 | module.exports = function (props) { 2 | return function (badges) { 3 | return badges.map(badge => { 4 | var b; 5 | 6 | if (typeof badge === 'string') { 7 | b = { 8 | name: badge, 9 | image: `${badge}-image`, 10 | url: `${badge}-url` 11 | }; 12 | } else { 13 | b = { 14 | name: badge.name, 15 | image: badge.image, 16 | url: badge.url 17 | }; 18 | } 19 | 20 | return `[![${b.name}][${b.image}]][${b.url}]`; 21 | }).join(' '); 22 | }; 23 | }; 24 | -------------------------------------------------------------------------------- /tasks/partials/footer.js: -------------------------------------------------------------------------------- 1 | module.exports = function (props) { 2 | return function () { 3 | return ( 4 | ` 5 | --- 6 | ${props.pkg.name} \`${props.pkg.tag}\` is built by ${props.pkg.author.name} and [contributors](./documentation/contributors.md) with :heart: 7 | and released under the [${props.pkg.license} License](./license.md). 8 | 9 | [npm-url]: https://www.npmjs.org/package/${props.pkg.name} 10 | [npm-image]: https://img.shields.io/npm/v/${props.pkg.name}.svg?style=flat-square 11 | [npm-dl-url]: https://www.npmjs.org/package/${props.pkg.name} 12 | [npm-dl-image]: http://img.shields.io/npm/dm/${props.pkg.name}.svg?style=flat-square 13 | 14 | [cdn-url]: https://wzrd.in/standalone/${props.pkg.name}@latest 15 | [cdn-image]: https://img.shields.io/badge/cdn-${props.pkg.tag}-${props.pkg.color}.svg?style=flat-square 16 | 17 | [ci-url]: https://travis-ci.org/${props.pkg.slug} 18 | [ci-image]: https://img.shields.io/travis/${props.pkg.slug}/master.svg?style=flat-square 19 | 20 | [coverage-url]: https://coveralls.io/r/${props.pkg.slug} 21 | [coverage-image]: https://img.shields.io/coveralls/${props.pkg.slug}.svg?style=flat-square 22 | [climate-url]: https://codeclimate.com/github/${props.pkg.slug} 23 | [climate-image]: https://img.shields.io/codeclimate/github/${props.pkg.slug}.svg?style=flat-square 24 | 25 | [pr-url]: http://issuestats.com/github/${props.pkg.slug} 26 | [pr-image]: http://issuestats.com/github/${props.pkg.slug}/badge/pr?style=flat-square 27 | [issue-url]: ${props.pkg.bugs.url} 28 | [issue-image]: http://issuestats.com/github/${props.pkg.slug}/badge/issue?style=flat-square 29 | 30 | [dependency-manager-image]: https://img.shields.io/badge/tracks%20with-greenkeeper-${props.pkg.color}.svg?style=flat-square 31 | [dependency-manager-url]: https://github.com/greenkeeperio/greenkeeper 32 | [release-manager-image]: https://img.shields.io/badge/releases%20with-semantic--release-${props.pkg.color}.svg?style=flat-square 33 | [release-manager-url]: https://github.com/semantic-release/semantic-release 34 | [ecma-image]: https://img.shields.io/badge/babel%20stage-0-${props.pkg.color}.svg?style=flat-square 35 | [ecma-url]: https://github.com/babel/babel 36 | [codestyle-url]: https://github.com/sindresorhus/xo 37 | [codestyle-image]: https://img.shields.io/badge/code%20style-xo-${props.pkg.color}.svg?style=flat-square 38 | [license-url]: ./license.md 39 | [license-image]: https://img.shields.io/badge/license-MIT-${props.pkg.color}.svg?style=flat-square 40 | [commitizen-url]: http://commitizen.github.io/cz-cli/ 41 | [commitizen-image]: https://img.shields.io/badge/commitizen-friendly-${props.pkg.color}.svg?style=flat-square 42 | 43 | [gitter-image]: https://img.shields.io/badge/gitter-join%20chat-${props.pkg.color}.svg?style=flat-square 44 | [gitter-url]: https://gitter.im/sinnerschrader/patternplate 45 | ` 46 | ); 47 | }; 48 | }; 49 | -------------------------------------------------------------------------------- /tasks/partials/header.js: -------------------------------------------------------------------------------- 1 | module.exports = function (props) { 2 | return function (image, headline, navigation) { 3 | image = image || { 4 | href: props.pkg.homepage, 5 | src: props.pkg.logo 6 | }; 7 | navigation = navigation || []; 8 | 9 | return ( 10 | ` 11 |
12 |
${props.pkg.description}
13 | 16 |

${props.pkg.icon} ${headline || props.pkg.name}

17 | 24 |
25 |
26 | ` 27 | ); 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /tasks/partials/page-layout.js: -------------------------------------------------------------------------------- 1 | module.exports = function (props) { 2 | return ` 3 | 4 | 5 | ${props.pkg.name} - ${props.pkg.description} 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | Fork me on Github 18 | 19 | 27 |
28 | ${props.body} 29 | 32 | 33 | 34 | 35 | `; 36 | }; 37 | -------------------------------------------------------------------------------- /tasks/static.js: -------------------------------------------------------------------------------- 1 | module.exports = function (gulp, paths) { 2 | return function copyStatic() { 3 | /* @desc copy public static assets */ 4 | return gulp.src(paths.source['public-static']) 5 | .pipe(gulp.dest(paths.target.public)); 6 | }; 7 | }; 8 | -------------------------------------------------------------------------------- /tasks/test-css.js: -------------------------------------------------------------------------------- 1 | var next = require('gulp-cssnext'); 2 | 3 | module.exports = function (gulp, paths) { 4 | return function postcss() { 5 | /* @desc postprocess test css sources */ 6 | return gulp.src(paths.source['test-css']) 7 | .pipe(next()) 8 | .pipe(gulp.dest(paths.target.test)); 9 | }; 10 | }; 11 | -------------------------------------------------------------------------------- /tasks/test.js: -------------------------------------------------------------------------------- 1 | var tape = require('gulp-tape'); 2 | var spec = require('tap-spec'); 3 | var onError = require('./helpers/on-error'); 4 | 5 | module.exports = function (gulp, paths, options) { 6 | return function test() { 7 | /* @desc execute the test suite */ 8 | return gulp.src(paths.executable.unit) 9 | .pipe(tape({reporter: spec()})) 10 | .on('error', onError(options)); 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /tasks/transpile.js: -------------------------------------------------------------------------------- 1 | var babel = require('gulp-babel'); 2 | var cached = require('gulp-cached'); 3 | var sourcemaps = require('gulp-sourcemaps'); 4 | var remember = require('gulp-remember'); 5 | var sequence = require('gulp-sequence'); 6 | var onError = require('./helpers/on-error'); 7 | 8 | function babelTask(gulp, source, target, options) { 9 | var name = 'transpile:' + [source, target].join(' → '); 10 | gulp.task(name, function () { 11 | return gulp.src(source) 12 | .pipe(cached(name)) 13 | // .pipe(sourcemaps.init()) 14 | .pipe(babel().on('error', onError(options))) 15 | // .pipe(sourcemaps.write('.')) 16 | .pipe(remember(name)) 17 | .pipe(gulp.dest(target)); 18 | }); 19 | return name; 20 | } 21 | 22 | module.exports = function (gulp, paths, options) { 23 | return function transpile(cb) { 24 | /* @desc transpile sources */ 25 | return sequence([ 26 | babelTask(gulp, paths.source.library, paths.target.library, options), 27 | babelTask(gulp, paths.source.scripts, paths.target.scripts, options), 28 | babelTask(gulp, paths.source.test, paths.target.test, options) 29 | ])(cb); 30 | }; 31 | }; 32 | -------------------------------------------------------------------------------- /tasks/watch.js: -------------------------------------------------------------------------------- 1 | var sequence = require('gulp-sequence'); 2 | var util = require('gulp-util'); 3 | var flatten = require('lodash.flatten'); 4 | var onError = require('./helpers/on-error'); 5 | 6 | function values(object) { 7 | return Object.keys(object) 8 | .map(function (key) { 9 | return object[key]; 10 | }); 11 | } 12 | 13 | module.exports = function (gulp, paths, options, cli) { 14 | var task = require('./helpers/task')(gulp); 15 | var build = require('./build')(gulp, paths, {fails: true, notifies: true}, cli); 16 | 17 | return function watch(cb) { 18 | /* @desc execute sequence after changes */ 19 | var watchOptions = {fails: false, notifies: true, watch: true}; 20 | var transpile = require('./transpile')(gulp, paths, watchOptions, cli); 21 | var documentation = require('./documentation')(gulp, paths, watchOptions, cli); 22 | var lint = require('./lint')(gulp, paths, watchOptions, cli); 23 | var test = require('./test')(gulp, paths, watchOptions, cli); 24 | var copy = require('./copy')(gulp, paths, watchOptions, cli); 25 | var copyExample = require('./copy-example')(gulp, paths, watchOptions, cli); 26 | var copyStatic = require('./static')(gulp, paths, watchOptions, cli); 27 | var html = require('./html')(gulp, paths, watchOptions, cli); 28 | var css = require('./css')(gulp, paths, watchOptions, cli); 29 | var testCss = require('./test-css')(gulp, paths, watchOptions, cli); 30 | var pack = require('./pack')(gulp, paths, watchOptions, cli); 31 | 32 | var excludeGlobs = flatten(values(paths.exclude)).map(function (glob) { 33 | return '!' + glob; 34 | }); 35 | 36 | var watchGlobs = flatten(values(paths.source)); 37 | 38 | return sequence( 39 | task(build, 'first-run'), 40 | task(function () { 41 | gulp.watch( 42 | watchGlobs.concat(excludeGlobs), 43 | function () { 44 | sequence( 45 | [ 46 | task(copy), 47 | task(copyStatic, 'copy-static'), 48 | task(copyExample, 'copy-example'), 49 | task(lint), 50 | task(css), 51 | task(testCss, 'test-css'), 52 | task(sequence( 53 | task(documentation), 54 | task(html)), 'docs-html'), 55 | task(sequence( 56 | task(transpile), 57 | task(sequence( 58 | [ 59 | task(pack), 60 | task(test) 61 | ] 62 | ), 'test-pack')), 63 | 'transpile-test-pack') 64 | ] 65 | )(onError(watchOptions)); 66 | } 67 | ); 68 | }, 'watch-setup') 69 | )(function (err) { 70 | if (err) { 71 | return cb(err); 72 | } 73 | 74 | util.log('Watching sources for changes, happy hacking ✊'); 75 | cb(null); 76 | }); 77 | }; 78 | }; 79 | --------------------------------------------------------------------------------