`
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 |
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 |
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 |
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 |
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 |
30 |
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 |
49 | Please follow these guidelines when reporting issues:
50 |
51 | Provide a title in the format of <Error> when <Task>
52 | Tag your issue with the tag bug
53 | Provide a short summary of what you are trying to do
54 | Provide the log of the encountered error if applicable
55 | Provide the exact version of jogwheel. Check npm ls jogwheel
when in doubt
56 | Be awesome and consider contributing a pull request
57 |
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 |
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 |
86 |
87 | Happiness enforced via eslint
88 | Use advanced language features where possible
89 | JSdoc comments for everything
90 | Favor micro library over swiss army knives (rimraf, ncp vs. fs-extra)
91 | Coverage never drops below 90%
92 | No change may lower coverage by more than 5%
93 | Be awesome
94 |
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 |
102 |
103 | conventional-changelog
104 | husky commit message hook available
105 | present tense
106 | maximum of 100 characters
107 | message format of $type($scope): $message
108 |
109 |
110 | jogwheel v1.4.5
is built by Mario Nebl and contributors with :heart:
111 | and released under the MIT License .
112 |
113 |
114 | Looking for older versions? Search our archives .
115 |
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 |
30 |
40 |
41 | constructor
42 | Creates a new jogwheel instance
43 | Parameters
44 |
45 | nodes
Node or NodeList Node or NodeList to instantiate on
46 | options
object Options object
47 | window
[Window] Global context to use (optional, default global.window
)
48 | document
[Document] Document context to use (optional, default global.window
)
49 |
50 | Examples
51 | import jogwheel from 'jogwheel' ;
52 | const element = document .querySelector('[data-animated]' );
53 |
54 |
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 |
72 | const wheel = jogwheel.create(element, {
73 | paused: false
74 | });
75 |
76 |
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 |
86 | const wheel = jogwheel.create(element, {
87 | paused: true
88 | });
89 |
90 |
91 | wheel.seek(0.5 ).play();
92 | Returns JogWheel JogWheel instance
93 | seek
94 | Seeks the timeline of the animation
95 | Parameters
96 |
97 | progress
float fraction of the animation timeline [0..1]
98 |
99 | Examples
100 | import jogwheel from 'jogwheel' ;
101 | const element = document .querySelector('[data-animated]' );
102 |
103 |
104 | const wheel = jogwheel.create(element, {
105 | paused: true
106 | });
107 |
108 |
109 | let scrollTop = document .scrollTop;
110 | document .addEventListener('scroll' , () => scrollTop = document .scrollTop);
111 |
112 |
113 | function loop ( ) {
114 | const fraction = Math .max((300 / scrollTop) - 1 , 0 );
115 | wheel.seek(fraction);
116 | window .requestAnimationFrame(loop);
117 | }
118 |
119 |
120 | loop();
121 | Returns jogwheel jogwheel instance
122 | create
123 | Creates a new jogwheel instance
124 | Parameters
125 |
126 | nodes
Node or NodeList Node or NodeList to instantiate on
127 | options
object Options object
128 | window
[Window] Global context to use (optional, default global.window
)
129 | document
[Document] Document context to use (optional, default global.window
)
130 | args
...
131 |
132 | Examples
133 | import jogwheel from 'jogwheel' ;
134 | const element = document .querySelector('[data-animated]' );
135 | :
136 |
137 | const wheel = jogwheel.create(element, {
138 | paused: true
139 | });
140 |
141 |
142 | wheel.seek(0.5 );
143 |
144 |
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 |
152 | Looking for older versions? Search our archives .
153 |
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 |
30 |
40 |
41 |
42 | Alive and kicking
43 |
44 |
45 | Basic project setup
46 | Basic working and tested implementation
47 | Speed up the Travis build process (max. 4 minutes)
48 | Test min/max browser versions per default
49 | Use semantic-release for automatic npm releases
50 | Make integration tests workable with phantomjs
51 | Add gh-pages
deployed automatically by Travis
52 | Add interactive playground with live code editing for examples
53 | Make all examples integration tests
54 | Unit test documentation examples
55 | Implement stubbed HTMLElement.prototype.style for React integration
56 | Implement (configurable) matchMedia callbacks to read responsive styles correctly
57 |
58 |
59 | Obsolute
60 |
61 |
62 | Run browser tests only for code changes vs. documentation
63 | Run full cross-browser tests for relase-builds
64 |
65 |
66 | jogwheel v1.4.5
is built by Mario Nebl and contributors with :heart:
67 | and released under the MIT License .
68 |
69 |
70 | Looking for older versions? Search our archives .
71 |
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 |
30 |
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 |
49 | Looking for older versions? Search our archives .
50 |
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 |
30 |
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 |
63 | Looking for older versions? Search our archives .
64 |
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 | [](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 |
100 |
101 |
105 |
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 |
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 |
20 |
21 | ${props.navigation.map(function(item) {
22 | var className = item.active ? 'jogwheel-item jogwheel-item--active' : 'jogwheel-item';
23 | return `${item.name} `
24 | }).join('\n')}
25 |
26 |
27 |
28 | ${props.body}
29 |
30 | Looking for older versions? Search our archives .
31 |
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 |
--------------------------------------------------------------------------------