├── app
├── .gitkeep
├── components
│ └── stagger-set.js
└── styles
│ └── ember-stagger-swagger.css
├── addon
├── .gitkeep
├── templates
│ └── components
│ │ └── stagger-set.hbs
├── components
│ └── stagger-set.js
├── utils
│ └── get-animation-prefix.js
├── constants
│ └── constants.js
└── mixins
│ └── stagger-set.js
├── vendor
└── .gitkeep
├── tests
├── unit
│ ├── .gitkeep
│ └── mixins
│ │ └── stagger-set-test.js
├── integration
│ ├── .gitkeep
│ └── components
│ │ └── stagger-set-test.js
├── dummy
│ ├── app
│ │ ├── helpers
│ │ │ └── .gitkeep
│ │ ├── models
│ │ │ └── .gitkeep
│ │ ├── routes
│ │ │ ├── .gitkeep
│ │ │ ├── index.js
│ │ │ └── homepage.js
│ │ ├── styles
│ │ │ └── app.css
│ │ ├── components
│ │ │ ├── .gitkeep
│ │ │ ├── space-craft-card.js
│ │ │ └── homepage-demo.js
│ │ ├── templates
│ │ │ ├── components
│ │ │ │ ├── .gitkeep
│ │ │ │ ├── space-craft-card.hbs
│ │ │ │ └── homepage-demo.hbs
│ │ │ ├── application.hbs
│ │ │ └── homepage.hbs
│ │ ├── resolver.js
│ │ ├── router.js
│ │ ├── app.js
│ │ └── index.html
│ ├── public
│ │ ├── robots.txt
│ │ ├── crossdomain.xml
│ │ └── savvy.min.css
│ └── config
│ │ └── environment.js
├── test-helper.js
├── helpers
│ ├── destroy-app.js
│ ├── resolver.js
│ ├── integration
│ │ └── get-node.js
│ ├── start-app.js
│ ├── unit
│ │ └── test-defaults-after-invalid-instantiation.js
│ └── module-for-acceptance.js
├── .jshintrc
└── index.html
├── .watchmanconfig
├── .bowerrc
├── blueprints
├── .jshintrc
└── ember-stagger-swagger
│ └── index.js
├── config
├── environment.js
├── release.js
├── ember-try.js
└── constants
│ └── constants.js
├── bower.json
├── .npmignore
├── testem.js
├── .ember-cli
├── .gitignore
├── .jshintrc
├── .editorconfig
├── ember-cli-build.js
├── LICENSE.md
├── .travis.yml
├── index.js
├── package.json
├── CHANGELOG.md
└── README.md
/app/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/addon/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vendor/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/unit/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/integration/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/helpers/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/models/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/routes/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/styles/app.css:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/components/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/addon/templates/components/stagger-set.hbs:
--------------------------------------------------------------------------------
1 | {{yield}}
2 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/application.hbs:
--------------------------------------------------------------------------------
1 | {{outlet}}
2 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {
2 | "ignore_dirs": ["tmp", "dist"]
3 | }
4 |
--------------------------------------------------------------------------------
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "bower_components",
3 | "analytics": false
4 | }
5 |
--------------------------------------------------------------------------------
/tests/dummy/public/robots.txt:
--------------------------------------------------------------------------------
1 | # http://www.robotstxt.org
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/blueprints/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "predef": [
3 | "console"
4 | ],
5 | "strict": false
6 | }
7 |
--------------------------------------------------------------------------------
/app/components/stagger-set.js:
--------------------------------------------------------------------------------
1 | export { default } from 'ember-stagger-swagger/components/stagger-set';
2 |
--------------------------------------------------------------------------------
/tests/dummy/app/resolver.js:
--------------------------------------------------------------------------------
1 | import Resolver from 'ember-resolver';
2 |
3 | export default Resolver;
4 |
--------------------------------------------------------------------------------
/config/environment.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 | 'use strict';
3 |
4 | module.exports = function(/* environment, appConfig */) {
5 | return { };
6 | };
7 |
--------------------------------------------------------------------------------
/tests/test-helper.js:
--------------------------------------------------------------------------------
1 | import resolver from './helpers/resolver';
2 | import {
3 | setResolver
4 | } from 'ember-qunit';
5 |
6 | setResolver(resolver);
7 |
--------------------------------------------------------------------------------
/tests/helpers/destroy-app.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 |
3 | export default function destroyApp(application) {
4 | Ember.run(application, 'destroy');
5 | }
6 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/homepage.hbs:
--------------------------------------------------------------------------------
1 | {{homepage-demo
2 | staggerDirectionOptions=model.staggerDirectionOptions
3 | staggerListItems=model.staggerListItems
4 | }}
5 |
--------------------------------------------------------------------------------
/tests/dummy/app/routes/index.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 |
3 | const { Route } = Ember;
4 |
5 | export default Route.extend({
6 |
7 | beforeModel() {
8 | this.transitionTo('homepage');
9 | },
10 |
11 | });
12 |
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-stagger-swagger",
3 | "dependencies": {
4 | "ember": "~2.5.0",
5 | "ember-cli-shims": "0.1.1",
6 | "ember-cli-test-loader": "0.2.2",
7 | "ember-qunit-notifications": "0.1.0"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /bower_components
2 | /config/ember-try.js
3 | /dist
4 | /tests
5 | /tmp
6 | **/.gitkeep
7 | .bowerrc
8 | .editorconfig
9 | .ember-cli
10 | .gitignore
11 | .jshintrc
12 | .watchmanconfig
13 | .travis.yml
14 | bower.json
15 | ember-cli-build.js
16 | testem.js
17 |
--------------------------------------------------------------------------------
/addon/components/stagger-set.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import layout from '../templates/components/stagger-set';
3 | import StaggerSetMixin from '../mixins/stagger-set';
4 |
5 | const { Component } = Ember;
6 |
7 | export default Component.extend(StaggerSetMixin, {
8 | layout
9 | });
10 |
--------------------------------------------------------------------------------
/testem.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 | module.exports = {
3 | "framework": "qunit",
4 | "test_page": "tests/index.html?hidepassed",
5 | "disable_watching": true,
6 | "launch_in_ci": [
7 | "Chrome"
8 | ],
9 | "launch_in_dev": [
10 | "PhantomJS",
11 | "Chrome"
12 | ]
13 | };
14 |
--------------------------------------------------------------------------------
/tests/dummy/app/router.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import config from './config/environment';
3 |
4 | const Router = Ember.Router.extend({
5 | location: config.locationType
6 | });
7 |
8 | Router.map(function() {
9 |
10 | this.route('homepage');
11 | });
12 |
13 | export default Router;
14 |
--------------------------------------------------------------------------------
/.ember-cli:
--------------------------------------------------------------------------------
1 | {
2 | /**
3 | Ember CLI sends analytics information by default. The data is completely
4 | anonymous, but there are times when you might want to disable this behavior.
5 |
6 | Setting `disableAnalytics` to true will prevent any data from being sent.
7 | */
8 | "disableAnalytics": false
9 | }
10 |
--------------------------------------------------------------------------------
/tests/helpers/resolver.js:
--------------------------------------------------------------------------------
1 | import Resolver from '../../resolver';
2 | import config from '../../config/environment';
3 |
4 | const resolver = Resolver.create();
5 |
6 | resolver.namespace = {
7 | modulePrefix: config.modulePrefix,
8 | podModulePrefix: config.podModulePrefix
9 | };
10 |
11 | export default resolver;
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /tmp
6 |
7 | # dependencies
8 | /node_modules
9 | /bower_components
10 |
11 | # misc
12 | .DS_Store
13 | /.sass-cache
14 | /connect.lock
15 | /coverage/*
16 | /libpeerconnection.log
17 | npm-debug.log
18 | testem.log
19 |
--------------------------------------------------------------------------------
/blueprints/ember-stagger-swagger/index.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 |
3 | module.exports = {
4 | description: 'ember-stagger-swagger',
5 |
6 | // locals: function(options) {
7 | // // Return custom template variables here.
8 | // return {
9 | // foo: options.entity.options.foo
10 | // };
11 | // }
12 |
13 | normalizeEntityName: function () {}
14 |
15 | };
16 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/components/space-craft-card.hbs:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{itemNumber}}
4 |
5 |
6 |
7 |
8 |
9 | {{item.title}}
10 |
11 |
12 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/space-craft-card.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 |
3 | const {
4 | Component,
5 | computed,
6 | } = Ember;
7 |
8 | export default Component.extend({
9 |
10 | classNames: [
11 | 'space-craft-card',
12 | 'o-flex-grid',
13 | 'o-flex-grid--auto',
14 | 'o-flex-grid--noWrap',
15 | ],
16 |
17 |
18 | model: null,
19 |
20 | itemNumber: computed('index', function computeItemNumber() {
21 | return this.get('index') + 1;
22 | }),
23 | });
24 |
--------------------------------------------------------------------------------
/tests/dummy/app/app.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Resolver from './resolver';
3 | import loadInitializers from 'ember-load-initializers';
4 | import config from './config/environment';
5 |
6 | let App;
7 |
8 | Ember.MODEL_FACTORY_INJECTIONS = true;
9 |
10 | App = Ember.Application.extend({
11 | modulePrefix: config.modulePrefix,
12 | podModulePrefix: config.podModulePrefix,
13 | Resolver
14 | });
15 |
16 | loadInitializers(App, config.modulePrefix);
17 |
18 | export default App;
19 |
--------------------------------------------------------------------------------
/tests/helpers/integration/get-node.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Get the actual DOM node of of the component rendered during an integration
3 | * test, which, initially, will have a parent div wrapping it.
4 | *
5 | * This gives us direct access to the element, and, inherently, the DOM API
6 | */
7 | export default function getNode (context) {
8 | // allow for context to be passed as a param, otherwise, use the bound "this"
9 | context = context || this;
10 | return context.$()[0].firstElementChild;
11 | }
12 |
--------------------------------------------------------------------------------
/tests/helpers/start-app.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import Application from '../../app';
3 | import config from '../../config/environment';
4 |
5 | export default function startApp(attrs) {
6 | let application;
7 |
8 | let attributes = Ember.merge({}, config.APP);
9 | attributes = Ember.merge(attributes, attrs); // use defaults, but you can override;
10 |
11 | Ember.run(() => {
12 | application = Application.create(attributes);
13 | application.setupForTesting();
14 | application.injectTestHelpers();
15 | });
16 |
17 | return application;
18 | }
19 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "predef": [
3 | "document",
4 | "window",
5 | "-Promise"
6 | ],
7 | "browser": true,
8 | "boss": true,
9 | "curly": true,
10 | "debug": false,
11 | "devel": true,
12 | "eqeqeq": true,
13 | "evil": true,
14 | "forin": false,
15 | "immed": false,
16 | "laxbreak": false,
17 | "newcap": true,
18 | "noarg": true,
19 | "noempty": false,
20 | "nonew": false,
21 | "nomen": false,
22 | "onevar": false,
23 | "plusplus": false,
24 | "regexp": false,
25 | "undef": true,
26 | "sub": true,
27 | "strict": false,
28 | "white": false,
29 | "eqnull": true,
30 | "esnext": true,
31 | "unused": true
32 | }
33 |
--------------------------------------------------------------------------------
/tests/dummy/public/crossdomain.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
15 |
16 |
--------------------------------------------------------------------------------
/tests/dummy/app/components/homepage-demo.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 |
3 | const {
4 | Component,
5 | } = Ember;
6 |
7 | export default Component.extend({
8 |
9 | classNames: ['homepage-demo'],
10 |
11 | staggerDirectionOptions: null,
12 | staggerListItems: null,
13 | currentStaggerDirection: null,
14 |
15 | showItems: false,
16 |
17 | init () {
18 | this._super(...arguments);
19 |
20 | this.currentStaggerDirection = this.currentStaggerDirection || 'left';
21 | },
22 |
23 |
24 | actions: {
25 |
26 | onStaggerDirectionSelected (direction) {
27 | this.set('currentStaggerDirection', direction);
28 | },
29 |
30 | }
31 | });
32 |
--------------------------------------------------------------------------------
/config/release.js:
--------------------------------------------------------------------------------
1 | /* jshint node:true */
2 | // var RSVP = require('rsvp');
3 |
4 | // For details on each option run `ember help release`
5 | module.exports = {
6 | // local: true,
7 | // remote: 'some_remote',
8 | // annotation: "Release %@",
9 | // message: "Bumped version to %@",
10 | // manifest: [ 'package.json', 'bower.json', 'someconfig.json' ],
11 | // publish: true,
12 | // strategy: 'date',
13 | // format: 'YYYY-MM-DD',
14 | // timezone: 'America/Los_Angeles',
15 | //
16 | // beforeCommit: function(project, versions) {
17 | // return new RSVP.Promise(function(resolve, reject) {
18 | // // Do custom things here...
19 | // });
20 | // }
21 | };
22 |
--------------------------------------------------------------------------------
/addon/utils/get-animation-prefix.js:
--------------------------------------------------------------------------------
1 | export default function getAnimationPrefix(element) {
2 |
3 | if (element instanceof HTMLElement && element.style) {
4 |
5 | if (typeof element.style.animation !== 'undefined') {
6 | return '';
7 | }
8 |
9 | if (typeof element.style.webkitAnimation !== 'undefined') {
10 | return 'webkit';
11 | }
12 |
13 | if (typeof element.style.mozAnimation !== 'undefined') {
14 | return 'moz';
15 | }
16 |
17 | if (typeof element.style.msAnimation !== 'undefined') {
18 | return 'ms';
19 | }
20 |
21 | if (typeof element.style.oAnimation !== 'undefined') {
22 | return 'o';
23 | }
24 |
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # editorconfig.org
4 |
5 | root = true
6 |
7 |
8 | [*]
9 | end_of_line = lf
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 | insert_final_newline = true
13 | indent_style = space
14 | indent_size = 2
15 |
16 | [*.js]
17 | indent_style = space
18 | indent_size = 2
19 |
20 | [*.hbs]
21 | insert_final_newline = false
22 | indent_style = space
23 | indent_size = 2
24 |
25 | [*.css]
26 | indent_style = space
27 | indent_size = 2
28 |
29 | [*.html]
30 | indent_style = space
31 | indent_size = 2
32 |
33 | [*.{diff,md}]
34 | trim_trailing_whitespace = false
35 |
--------------------------------------------------------------------------------
/tests/helpers/unit/test-defaults-after-invalid-instantiation.js:
--------------------------------------------------------------------------------
1 | /* jshint ignore:start */
2 |
3 | /**
4 | * Helper function that instantiates an object with invalid property values,
5 | * and tests that the value was set to an expected default.
6 | *
7 | */
8 | export default function testDefaultsAfterInvalidInstantiation (
9 | assert,
10 | constructorObj,
11 | propName,
12 | expectedValue,
13 | invalidInputs,
14 | standardObjConfig = {}
15 | ) {
16 |
17 | invalidInputs.forEach((input) => {
18 | subject = constructorObj.create({ ...standardObjConfig, [propName]: input });
19 | expected = expectedValue;
20 | actual = subject.get(`${propName}`);
21 | assert.equal(actual, expected);
22 | });
23 |
24 | };
25 |
--------------------------------------------------------------------------------
/tests/dummy/app/routes/homepage.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 |
3 | const { Route } = Ember;
4 |
5 | const spaceCrafts = [
6 | { name: 'Falcon Heavy', weight: 33, size: 99, imgURL: '' },
7 | { name: 'Falcon Heavy', weight: 33, size: 99, imgURL: '' },
8 | { name: 'Falcon Heavy', weight: 33, size: 99, imgURL: '' },
9 | { name: 'Falcon Heavy', weight: 33, size: 99, imgURL: '' },
10 | { name: 'Falcon Heavy', weight: 33, size: 99, imgURL: '' },
11 | { name: 'Falcon Heavy', weight: 33, size: 99, imgURL: '' },
12 | ];
13 |
14 | export default Route.extend({
15 |
16 | model () {
17 | return {
18 | staggerDirectionOptions: ['left', 'up', 'right', 'down'],
19 | staggerListItems: spaceCrafts,
20 | };
21 | },
22 |
23 | });
24 |
--------------------------------------------------------------------------------
/tests/helpers/module-for-acceptance.js:
--------------------------------------------------------------------------------
1 | import { module } from 'qunit';
2 | import Ember from 'ember';
3 | import startApp from '../helpers/start-app';
4 | import destroyApp from '../helpers/destroy-app';
5 |
6 | const { RSVP: { Promise } } = Ember;
7 |
8 |
9 | export default function(name, options = {}) {
10 | module(name, {
11 | beforeEach() {
12 | this.application = startApp();
13 |
14 | if (options.beforeEach) {
15 | return options.beforeEach.apply(this, arguments);
16 | }
17 | },
18 |
19 | afterEach() {
20 | if (options.afterEach) {
21 | let afterEach = options.afterEach && options.afterEach.apply(this, arguments);
22 | return Promise.resolve(afterEach).then(() => destroyApp(this.application));
23 | }
24 | }
25 | });
26 | }
27 |
--------------------------------------------------------------------------------
/tests/dummy/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dummy
7 |
8 |
9 |
10 | {{content-for "head"}}
11 |
12 |
13 |
14 |
15 |
16 | {{content-for "head-footer"}}
17 |
18 |
19 |
20 | {{content-for "body"}}
21 |
22 |
23 |
24 |
25 | {{content-for "body-footer"}}
26 |
27 |
28 |
--------------------------------------------------------------------------------
/ember-cli-build.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 | /* global require, module */
3 | 'use strict';
4 |
5 | const EmberAddon = require('ember-cli/lib/broccoli/ember-addon');
6 | const Funnel = require('broccoli-funnel');
7 | const path = require('path');
8 |
9 | const MATCH_CSS = new RegExp('.*\\.css$');
10 |
11 | module.exports = function(defaults) {
12 |
13 | const app = new EmberAddon(defaults, {
14 | // Add options here
15 | });
16 |
17 | const stylesDir = path.join(__dirname, 'app', 'styles');
18 | const stylesTree = new Funnel(stylesDir, {
19 | include: [MATCH_CSS],
20 | destDir: 'vendor'
21 | });
22 |
23 |
24 | /*
25 | This build file specifies the options for the dummy test app of this
26 | addon, located in `/tests/dummy`
27 | This build file does *not* influence how the addon or the app using it
28 | behave. You most likely want to be modifying `./index.js` or app's build file
29 | */
30 |
31 | return app.toTree([stylesTree]);
32 | };
33 |
--------------------------------------------------------------------------------
/tests/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "predef": [
3 | "document",
4 | "window",
5 | "location",
6 | "setTimeout",
7 | "$",
8 | "-Promise",
9 | "define",
10 | "console",
11 | "visit",
12 | "exists",
13 | "fillIn",
14 | "click",
15 | "keyEvent",
16 | "triggerEvent",
17 | "find",
18 | "findWithAssert",
19 | "wait",
20 | "DS",
21 | "andThen",
22 | "currentURL",
23 | "currentPath",
24 | "currentRouteName"
25 | ],
26 | "node": false,
27 | "browser": false,
28 | "boss": true,
29 | "curly": true,
30 | "debug": false,
31 | "devel": false,
32 | "eqeqeq": true,
33 | "evil": true,
34 | "forin": false,
35 | "immed": false,
36 | "laxbreak": false,
37 | "newcap": true,
38 | "noarg": true,
39 | "noempty": false,
40 | "nonew": false,
41 | "nomen": false,
42 | "onevar": false,
43 | "plusplus": false,
44 | "regexp": false,
45 | "undef": true,
46 | "sub": true,
47 | "strict": false,
48 | "white": false,
49 | "eqnull": true,
50 | "esnext": true,
51 | "unused": true
52 | }
53 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/tests/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Dummy Tests
7 |
8 |
9 |
10 | {{content-for "head"}}
11 | {{content-for "test-head"}}
12 |
13 |
14 |
15 |
16 |
17 | {{content-for "head-footer"}}
18 | {{content-for "test-head-footer"}}
19 |
20 |
21 | {{content-for "body"}}
22 | {{content-for "test-body"}}
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | {{content-for "body-footer"}}
32 | {{content-for "test-body-footer"}}
33 |
34 |
35 |
--------------------------------------------------------------------------------
/tests/dummy/config/environment.js:
--------------------------------------------------------------------------------
1 | /* jshint node: true */
2 |
3 | module.exports = function(environment) {
4 | var ENV = {
5 | modulePrefix: 'dummy',
6 | environment: environment,
7 | baseURL: '/',
8 | locationType: 'auto',
9 | EmberENV: {
10 | FEATURES: {
11 | // Here you can enable experimental features on an ember canary build
12 | // e.g. 'with-controller': true
13 | }
14 | },
15 |
16 | APP: {
17 | // Here you can pass flags/options to your application instance
18 | // when it is created
19 | }
20 | };
21 |
22 | if (environment === 'development') {
23 | // ENV.APP.LOG_RESOLVER = true;
24 | // ENV.APP.LOG_ACTIVE_GENERATION = true;
25 | // ENV.APP.LOG_TRANSITIONS = true;
26 | // ENV.APP.LOG_TRANSITIONS_INTERNAL = true;
27 | // ENV.APP.LOG_VIEW_LOOKUPS = true;
28 | }
29 |
30 | if (environment === 'test') {
31 | // Testem prefers this...
32 | ENV.baseURL = '/';
33 | ENV.locationType = 'none';
34 |
35 | // keep test console output quieter
36 | ENV.APP.LOG_ACTIVE_GENERATION = false;
37 | ENV.APP.LOG_VIEW_LOOKUPS = false;
38 |
39 | ENV.APP.rootElement = '#ember-testing';
40 | }
41 |
42 | if (environment === 'production') {
43 | ENV.locationType = 'hash';
44 | ENV.baseURL = '/ember-stagger-swagger/';
45 |
46 | }
47 |
48 | return ENV;
49 | };
50 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | ---
2 | language: node_js
3 | node_js:
4 | - "4"
5 | - "5"
6 | - "6"
7 |
8 | sudo: required
9 | dist: trusty
10 |
11 | addons:
12 | apt:
13 | sources:
14 | - google-chrome
15 | packages:
16 | - google-chrome-stable
17 |
18 | cache:
19 | directories:
20 | - node_modules
21 |
22 |
23 | env:
24 | - EMBER_TRY_SCENARIO=default
25 | - EMBER_TRY_SCENARIO=ember-1.13
26 | - EMBER_TRY_SCENARIO=ember-2.0
27 | - EMBER_TRY_SCENARIO=ember-2.1
28 | - EMBER_TRY_SCENARIO=ember-2.2
29 | - EMBER_TRY_SCENARIO=ember-2.3
30 | - EMBER_TRY_SCENARIO=ember-2.4
31 | - EMBER_TRY_SCENARIO=ember-2.5
32 | - EMBER_TRY_SCENARIO=ember-2.6
33 | - EMBER_TRY_SCENARIO=ember-release
34 | - ALLOW_DEPRECATIONS=true EMBER_TRY_SCENARIO=ember-beta
35 | - ALLOW_DEPRECATIONS=true EMBER_TRY_SCENARIO=ember-canary
36 |
37 | matrix:
38 | fast_finish: true
39 | allow_failures:
40 | - env: ALLOW_DEPRECATIONS=true EMBER_TRY_SCENARIO=ember-canary
41 |
42 | before_install:
43 | - "export DISPLAY=:99.0"
44 | - "sh -e /etc/init.d/xvfb start"
45 | - npm config set progress false
46 | - npm install -g bower
47 | - npm install -g npm@^3
48 |
49 |
50 | install:
51 | - npm install
52 | - bower install
53 |
54 | script:
55 | # Usually, it's ok to finish the test scenario without reverting
56 | # to the addon's original dependency state, skipping "cleanup".
57 | - ember try $EMBER_TRY_SCENARIO test --skip-cleanup
58 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /* jshint node: true */
2 | 'use strict';
3 |
4 | const path = require('path');
5 |
6 | const MATCH_CSS = new RegExp('.*\\.css$');
7 |
8 | const cssNextOptions = {
9 | browsers: ['last 2 version'],
10 | sourcemap: true,
11 | };
12 |
13 | module.exports = {
14 | name: 'ember-stagger-swagger',
15 |
16 | isDevelopingAddon () {
17 | return false;
18 | },
19 |
20 | _getCSSFileName: function () {
21 | return this.name + '.css';
22 | },
23 |
24 | _isAddon: function () {
25 | const keywords = this.project.pkg.keywords;
26 | return !!(keywords && ~keywords.indexOf('ember-addon'));
27 | },
28 |
29 | _isDummyApp () {
30 | return !!( (this._isAddon()) && this.name === 'ember-stagger-swagger');
31 | },
32 |
33 | included: function (app) {
34 | this._super.included(app);
35 |
36 | if (this._isDummyApp()) {
37 | app.import(path.join('app/styles', this._getCSSFileName()));
38 | }
39 |
40 | if (!this._isAddon()) {
41 | app.import(path.join('vendor', this._getCSSFileName()));
42 | }
43 |
44 | },
45 |
46 |
47 | treeForVendor: function (node) {
48 | if (this._isAddon()) { return node; }
49 |
50 | const Funnel = require('broccoli-funnel');
51 | const mergeTrees = require('broccoli-merge-trees');
52 | const stylesPath = path.join(this.project.nodeModulesPath, this.name, 'app', 'styles');
53 |
54 | const cssTree = new Funnel(stylesPath, { include: [MATCH_CSS] });
55 |
56 | node = node ? mergeTrees([node, cssTree]) : cssTree;
57 |
58 | return node;
59 | }
60 |
61 | };
62 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ember-stagger-swagger",
3 | "version": "1.0.12",
4 | "description": "GPU stagger animation for Ember list items",
5 | "directories": {
6 | "doc": "doc",
7 | "test": "tests"
8 | },
9 | "scripts": {
10 | "build": "ember build",
11 | "start": "ember server",
12 | "test": "ember try:each"
13 | },
14 | "repository": "https://github.com/BrianSipple/ember-stagger-swagger",
15 | "engines": {
16 | "node": ">= 4.2.x"
17 | },
18 | "author": "Brian Sipple <@Brian_Sipple>",
19 | "license": "MIT",
20 | "devDependencies": {
21 | "broccoli-asset-rev": "^2.4.2",
22 | "ember-ajax": "^2.0.1",
23 | "ember-cli": "2.6.0-beta.1",
24 | "ember-cli-app-version": "^1.0.0",
25 | "ember-cli-dependency-checker": "^1.2.0",
26 | "ember-cli-github-pages": "0.0.8",
27 | "ember-cli-htmlbars": "^1.0.3",
28 | "ember-cli-htmlbars-inline-precompile": "^0.3.1",
29 | "ember-cli-inject-live-reload": "^1.4.0",
30 | "ember-cli-qunit": "^1.4.0",
31 | "ember-cli-release": "1.0.0-beta.1",
32 | "ember-cli-sri": "^2.1.0",
33 | "ember-cli-uglify": "^1.2.0",
34 | "ember-disable-prototype-extensions": "^1.1.0",
35 | "ember-disable-proxy-controllers": "^1.0.1",
36 | "ember-export-application-global": "^1.0.5",
37 | "ember-load-initializers": "^0.5.1",
38 | "ember-resolver": "^2.0.3",
39 | "ember-welcome-page": "^1.0.1",
40 | "ember-sinon": "0.5.0",
41 | "ember-sinon-qunit": "1.3.0",
42 | "loader.js": "^4.0.1"
43 | },
44 | "keywords": [
45 | "ember-addon",
46 | "ember animation",
47 | "ember animation addon",
48 | "ember animation mixin",
49 | "ember animation component",
50 | "ember animation lists",
51 | "stagger animation",
52 | "list animation"
53 | ],
54 | "dependencies": {
55 | "broccoli-funnel": "1.0.1",
56 | "broccoli-merge-trees": "1.1.1",
57 | "ember-cli-babel": "^5.1.6",
58 | "ember-cli-htmlbars": "^1.0.1"
59 | },
60 | "ember-addon": {
61 | "configPath": "tests/dummy/config",
62 | "demoURL": "http://www.sipple.io/ember-stagger-swagger-demo/"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | 1.0.12 / 02-06-2016 (dd-mm-yyyy)
2 | ================================
3 | * Fixed boolean logic when checking for an initial `outEffect` argument in `_resolveInitialEffectAttrs`. (`!!` was
4 | being used instead of `!`, causing an unnecessary warning message to be logged to the console.)
5 |
6 | 1.0.11 / 02-06-2016
7 | ================================
8 | * Fixed an error in `_setAnimationValuesForItems` where `inDelay` and `outDelay` weren't being accounted for (thanks [@techsoldaten](https://github.com/techsoldaten))
9 |
10 | 1.0.10 / 15-05-2016
11 | ================================
12 | * `visibility` is no longer set to `hidden` on items that have yet to be animated in (The only rule now applied is `opacity: 0`). This prevents an issue in Safari where even when toggling `visibility` back to `visible`, items would retain their status of `hidden`.
13 |
14 | 1.0.9 / 20-04-2016
15 | ================================
16 | * Upgrade to `ember@2.5` and `ember-cli@2.6.0-beta.1`
17 |
18 | 1.0.8 / 20-04-2016
19 | ================================
20 | * Fix typo in README describing when the `onAnimationStart` action was called.
21 |
22 | 1.0.7 / 09-04-2016
23 | ================================
24 | * perform short-circuited default setting before warning for invalid arguments.
25 | * use `Ember.Logger.warn` instead of `Ember.warn`
26 |
27 | 1.0.6 / 06-04-2016
28 | ================================
29 | * hotfix: set `isDevelopingAddon` to false 😳
30 |
31 | 1.0.5 / 04-04-2016
32 | ================================
33 | * Fix build by using Chrome in TravisCI.
34 |
35 | 1.0.4 / 03-04-2016
36 | ================================
37 | * Remove dependency adding in blueprint (see: https://github.com/BrianSipple/ember-stagger-swagger/issues/1)
38 |
39 | 1.0.3 / 03-04-2016
40 | ================================
41 | * Bring keyframes into main css file
42 | * Fixes broken import
43 | * Removes need for postCSS build step.
44 |
45 | 1.0.2 / 03-04-2016
46 | ================================
47 | * Implement addPackagesToProject inside of the main blueprint `afterInstall` hook so that CSS is properly processed.
48 |
49 | 1.0.1 / 03-04-2016
50 | ================================
51 | * Minor README fixes.
52 |
53 | 1.0.0 / 03-04-2016
54 | ================================
55 | * Initial release version.
56 |
--------------------------------------------------------------------------------
/config/ember-try.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true*/
2 | module.exports = {
3 | scenarios: [
4 | {
5 | name: 'default',
6 | bower: {
7 | dependencies: { }
8 | }
9 | },
10 | {
11 | name: 'ember-1.13',
12 | bower: {
13 | dependencies: {
14 | 'ember': '~1.13.0'
15 | },
16 | resolutions: {
17 | 'ember': '~1.13.0'
18 | }
19 | }
20 | },
21 | {
22 | name: 'ember-2.0',
23 | bower: {
24 | dependencies: {
25 | 'ember': '~2.0.0'
26 | },
27 | resolutions: {
28 | 'ember': '~2.0.0'
29 | }
30 | }
31 | },
32 | {
33 | name: 'ember-2.1',
34 | bower: {
35 | dependencies: {
36 | 'ember': '~2.1.0'
37 | },
38 | resolutions: {
39 | 'ember': '~2.1.0'
40 | }
41 | }
42 | },
43 | {
44 | name: 'ember-2.2',
45 | bower: {
46 | dependencies: {
47 | 'ember': '~2.2.0'
48 | },
49 | resolutions: {
50 | 'ember': '~2.2.0'
51 | }
52 | }
53 | },
54 | {
55 | name: 'ember-2.3',
56 | bower: {
57 | dependencies: {
58 | 'ember': '~2.3.0'
59 | },
60 | resolutions: {
61 | 'ember': '~2.3.0'
62 | }
63 | }
64 | },
65 | {
66 | name: 'ember-2.4',
67 | bower: {
68 | dependencies: {
69 | 'ember': '~2.4.0'
70 | },
71 | resolutions: {
72 | 'ember': '~2.4.0'
73 | }
74 | }
75 | },
76 | {
77 | name: 'ember-2.5',
78 | bower: {
79 | dependencies: {
80 | 'ember': '~2.5.0'
81 | },
82 | resolutions: {
83 | 'ember': '~2.5.0'
84 | }
85 | }
86 | },
87 | {
88 | name: 'ember-2.6',
89 | bower: {
90 | dependencies: {
91 | 'ember': '~2.6.0'
92 | },
93 | resolutions: {
94 | 'ember': '~2.6.0'
95 | }
96 | }
97 | },
98 | {
99 | name: 'ember-release',
100 | bower: {
101 | dependencies: {
102 | 'ember': 'components/ember#release'
103 | },
104 | resolutions: {
105 | 'ember': 'release'
106 | }
107 | }
108 | },
109 | {
110 | name: 'ember-beta',
111 | bower: {
112 | dependencies: {
113 | 'ember': 'components/ember#beta'
114 | },
115 | resolutions: {
116 | 'ember': 'beta'
117 | }
118 | }
119 | },
120 | {
121 | name: 'ember-canary',
122 | bower: {
123 | dependencies: {
124 | 'ember': 'components/ember#canary'
125 | },
126 | resolutions: {
127 | 'ember': 'canary'
128 | }
129 | }
130 | }
131 | ]
132 | };
133 |
--------------------------------------------------------------------------------
/config/constants/constants.js:
--------------------------------------------------------------------------------
1 | const DEFAULTS = {
2 |
3 | /**
4 | * 2 frames per item (1 frame @ 60fps ~= 16ms) creates a noticeably staggered
5 | * but still-perceptively fluid motion.
6 | * (see: https://en.wikipedia.org/wiki/Traditional_animation#.22Shooting_on_twos.22)
7 | */
8 | STAGGER_INTERVAL_MS: 32,
9 |
10 | ANIMATION_DELAY_IN: 0,
11 | ANIMATION_DELAY_OUT: 0,
12 |
13 | ANIMATION_DURATION_IN: 500,
14 | ANIMATION_DURATION_OUT: 500,
15 |
16 | TIMING_FUNCTION_IN: 'cubic-bezier(0.215, 0.610, 0.355, 1.000)', // ease-out-cubic
17 | TIMING_FUNCTION_OUT: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)', // ease-in-cubic
18 | };
19 |
20 | const foo = {
21 |
22 | };
23 |
24 | const ANIMATION_DIRECTIONS = {
25 | LEFT: 'left',
26 | DOWN: 'down',
27 | RIGHT: 'right',
28 | UP: 'up',
29 | };
30 |
31 | const ANIMATION_NAMES = {
32 | SLIDE_AND_FADE: 'slideAndFade',
33 | SLIDE: 'slide',
34 | FADE: 'fade',
35 | SCALE: 'scale',
36 | };
37 |
38 | const KEYFRAMES_MAP = {
39 | [ANIMATION_DIRECTIONS.LEFT]: {
40 | in: {
41 | [ANIMATION_NAMES.SLIDE_AND_FADE]: '__EmberStaggerSwagger__SlideAndFadeInFromRight',
42 | [ANIMATION_NAMES.SLIDE]: '__EmberStaggerSwagger__SlideInFromRight',
43 | [ANIMATION_NAMES.FADE]: '__EmberStaggerSwagger__FadeIn',
44 | [ANIMATION_NAMES.SCALE]: '__EmberStaggerSwagger__ScaleIn',
45 | },
46 | out: {
47 | [ANIMATION_NAMES.SLIDE_AND_FADE]: '__EmberStaggerSwagger__SlideAndFadeOutLeft',
48 | [ANIMATION_NAMES.SLIDE]: '__EmberStaggerSwagger__SlideOutLeft',
49 | [ANIMATION_NAMES.FADE]: '__EmberStaggerSwagger__FadeOut',
50 | [ANIMATION_NAMES.SCALE]: '__EmberStaggerSwagger__ScaleOut',
51 | },
52 | },
53 | [ANIMATION_DIRECTIONS.DOWN]: {
54 | in: {
55 | [ANIMATION_NAMES.SLIDE_AND_FADE]: '__EmberStaggerSwagger__SlideAndFadeInFromTop',
56 | [ANIMATION_NAMES.SLIDE]: '__EmberStaggerSwagger__SlideInFromTop',
57 | [ANIMATION_NAMES.FADE]: '__EmberStaggerSwagger__FadeIn',
58 | [ANIMATION_NAMES.SCALE]: '__EmberStaggerSwagger__ScaleIn',
59 | },
60 | out: {
61 | [ANIMATION_NAMES.SLIDE_AND_FADE]: '__EmberStaggerSwagger__SlideAndFadeOutDown',
62 | [ANIMATION_NAMES.SLIDE]: '__EmberStaggerSwagger__SlideOutDown',
63 | [ANIMATION_NAMES.FADE]: '__EmberStaggerSwagger__FadeOut',
64 | [ANIMATION_NAMES.SCALE]: '__EmberStaggerSwagger__ScaleOut',
65 | },
66 | },
67 | [ANIMATION_DIRECTIONS.RIGHT]: {
68 | in: {
69 | [ANIMATION_NAMES.SLIDE_AND_FADE]: '__EmberStaggerSwagger__SlideAndFadeInFromLeft',
70 | [ANIMATION_NAMES.SLIDE]: '__EmberStaggerSwagger__SlideInFromLeft',
71 | [ANIMATION_NAMES.FADE]: '__EmberStaggerSwagger__FadeIn',
72 | [ANIMATION_NAMES.SCALE]: '__EmberStaggerSwagger__ScaleIn',
73 | },
74 | out: {
75 | [ANIMATION_NAMES.SLIDE_AND_FADE]: '__EmberStaggerSwagger__SlideAndFadeOutRight',
76 | [ANIMATION_NAMES.SLIDE]: '__EmberStaggerSwagger__SlideOutRight',
77 | [ANIMATION_NAMES.FADE]: '__EmberStaggerSwagger__FadeOut',
78 | [ANIMATION_NAMES.SCALE]: '__EmberStaggerSwagger__ScaleOut',
79 | },
80 | },
81 | [ANIMATION_DIRECTIONS.UP]: {
82 | in: {
83 | [ANIMATION_NAMES.SLIDE_AND_FADE]: '__EmberStaggerSwagger__SlideAndFadeInFromBottom',
84 | [ANIMATION_NAMES.SLIDE]: '__EmberStaggerSwagger__SlideInFromBottom',
85 | [ANIMATION_NAMES.FADE]: '__EmberStaggerSwagger__FadeIn',
86 | [ANIMATION_NAMES.SCALE]: '__EmberStaggerSwagger__ScaleIn',
87 | },
88 | out: {
89 | [ANIMATION_NAMES.SLIDE_AND_FADE]: '__EmberStaggerSwagger__SlideAndFadeOutUp',
90 | [ANIMATION_NAMES.SLIDE]: '__EmberStaggerSwagger__SlideOutUp',
91 | [ANIMATION_NAMES.FADE]: '__EmberStaggerSwagger__FadeOut',
92 | [ANIMATION_NAMES.SCALE]: '__EmberStaggerSwagger__ScaleOut',
93 | },
94 | },
95 | };
96 |
97 |
98 | export default {
99 | foo,
100 | b,
101 | DEFAULTS: DEFAULTS,
102 | ANIMATION_DIRECTIONS,
103 | ANIMATION_NAMES,
104 | KEYFRAMES_MAP,
105 | };
106 |
--------------------------------------------------------------------------------
/addon/constants/constants.js:
--------------------------------------------------------------------------------
1 | const CLASS_NAMES = {
2 | untoggled: 'hasnt-entered',
3 | listItemCompletedInitialToggle: 'completed-initial-enter',
4 | };
5 |
6 | const DEFAULTS = {
7 |
8 | /**
9 | * 2 frames per item (1 frame @ 60fps ~= 16ms) creates a noticeably staggered
10 | * but still-perceptively fluid motion.
11 | * (see: https://en.wikipedia.org/wiki/Traditional_animation#.22Shooting_on_twos.22)
12 | */
13 | STAGGER_INTERVAL_MS: 32,
14 |
15 | ANIMATION_DELAY_IN: 0,
16 | ANIMATION_DELAY_OUT: 0,
17 |
18 | ANIMATION_DURATION_IN: 500,
19 | ANIMATION_DURATION_OUT: 500,
20 |
21 | TIMING_FUNCTION_IN: 'cubic-bezier(0.215, 0.610, 0.355, 1.000)', // ease-out-cubic
22 | TIMING_FUNCTION_OUT: 'cubic-bezier(0.55, 0.055, 0.675, 0.19)', // ease-in-cubic
23 | };
24 |
25 | const ANIMATION_DIRECTIONS = {
26 | LEFT: 'left',
27 | DOWN: 'down',
28 | RIGHT: 'right',
29 | UP: 'up',
30 | };
31 |
32 | const ANIMATION_NAMES = {
33 | SLIDE_AND_FADE: 'slideAndFade',
34 | SLIDE: 'slide',
35 | FADE: 'fade',
36 | SCALE: 'scale',
37 | };
38 |
39 | const KEYFRAMES_MAP = {
40 | [ANIMATION_DIRECTIONS.LEFT]: {
41 | in: {
42 | [ANIMATION_NAMES.SLIDE_AND_FADE]: '__EmberStaggerSwagger__SlideAndFadeInFromRight',
43 | [ANIMATION_NAMES.SLIDE]: '__EmberStaggerSwagger__SlideInFromRight',
44 | [ANIMATION_NAMES.FADE]: '__EmberStaggerSwagger__FadeIn',
45 | [ANIMATION_NAMES.SCALE]: '__EmberStaggerSwagger__ScaleIn',
46 | },
47 | out: {
48 | [ANIMATION_NAMES.SLIDE_AND_FADE]: '__EmberStaggerSwagger__SlideAndFadeOutLeft',
49 | [ANIMATION_NAMES.SLIDE]: '__EmberStaggerSwagger__SlideOutLeft',
50 | [ANIMATION_NAMES.FADE]: '__EmberStaggerSwagger__FadeOut',
51 | [ANIMATION_NAMES.SCALE]: '__EmberStaggerSwagger__ScaleOut',
52 | },
53 | },
54 | [ANIMATION_DIRECTIONS.DOWN]: {
55 | in: {
56 | [ANIMATION_NAMES.SLIDE_AND_FADE]: '__EmberStaggerSwagger__SlideAndFadeInFromTop',
57 | [ANIMATION_NAMES.SLIDE]: '__EmberStaggerSwagger__SlideInFromTop',
58 | [ANIMATION_NAMES.FADE]: '__EmberStaggerSwagger__FadeIn',
59 | [ANIMATION_NAMES.SCALE]: '__EmberStaggerSwagger__ScaleIn',
60 | },
61 | out: {
62 | [ANIMATION_NAMES.SLIDE_AND_FADE]: '__EmberStaggerSwagger__SlideAndFadeOutDown',
63 | [ANIMATION_NAMES.SLIDE]: '__EmberStaggerSwagger__SlideOutDown',
64 | [ANIMATION_NAMES.FADE]: '__EmberStaggerSwagger__FadeOut',
65 | [ANIMATION_NAMES.SCALE]: '__EmberStaggerSwagger__ScaleOut',
66 | },
67 | },
68 | [ANIMATION_DIRECTIONS.RIGHT]: {
69 | in: {
70 | [ANIMATION_NAMES.SLIDE_AND_FADE]: '__EmberStaggerSwagger__SlideAndFadeInFromLeft',
71 | [ANIMATION_NAMES.SLIDE]: '__EmberStaggerSwagger__SlideInFromLeft',
72 | [ANIMATION_NAMES.FADE]: '__EmberStaggerSwagger__FadeIn',
73 | [ANIMATION_NAMES.SCALE]: '__EmberStaggerSwagger__ScaleIn',
74 | },
75 | out: {
76 | [ANIMATION_NAMES.SLIDE_AND_FADE]: '__EmberStaggerSwagger__SlideAndFadeOutRight',
77 | [ANIMATION_NAMES.SLIDE]: '__EmberStaggerSwagger__SlideOutRight',
78 | [ANIMATION_NAMES.FADE]: '__EmberStaggerSwagger__FadeOut',
79 | [ANIMATION_NAMES.SCALE]: '__EmberStaggerSwagger__ScaleOut',
80 | },
81 | },
82 | [ANIMATION_DIRECTIONS.UP]: {
83 | in: {
84 | [ANIMATION_NAMES.SLIDE_AND_FADE]: '__EmberStaggerSwagger__SlideAndFadeInFromBottom',
85 | [ANIMATION_NAMES.SLIDE]: '__EmberStaggerSwagger__SlideInFromBottom',
86 | [ANIMATION_NAMES.FADE]: '__EmberStaggerSwagger__FadeIn',
87 | [ANIMATION_NAMES.SCALE]: '__EmberStaggerSwagger__ScaleIn',
88 | },
89 | out: {
90 | [ANIMATION_NAMES.SLIDE_AND_FADE]: '__EmberStaggerSwagger__SlideAndFadeOutUp',
91 | [ANIMATION_NAMES.SLIDE]: '__EmberStaggerSwagger__SlideOutUp',
92 | [ANIMATION_NAMES.FADE]: '__EmberStaggerSwagger__FadeOut',
93 | [ANIMATION_NAMES.SCALE]: '__EmberStaggerSwagger__ScaleOut',
94 | },
95 | },
96 | };
97 |
98 |
99 | export default {
100 | CLASS_NAMES,
101 | DEFAULTS,
102 | ANIMATION_DIRECTIONS,
103 | ANIMATION_NAMES,
104 | KEYFRAMES_MAP,
105 | };
106 |
--------------------------------------------------------------------------------
/tests/integration/components/stagger-set-test.js:
--------------------------------------------------------------------------------
1 | import Ember from 'ember';
2 | import { moduleForComponent, test } from 'ember-qunit';
3 | import { skip } from 'qunit';
4 | import hbs from 'htmlbars-inline-precompile';
5 | import getNode from '../../helpers/integration/get-node';
6 | import Constants from 'ember-stagger-swagger/constants/constants';
7 |
8 | const {
9 | run,
10 | set,
11 | } = Ember;
12 |
13 | const {
14 | ANIMATION_DIRECTIONS,
15 | ANIMATION_NAMES,
16 | KEYFRAMES_MAP,
17 | } = Constants;
18 |
19 | const DataDown = Ember.Object.extend({
20 | showItems: false,
21 | inDirection: ANIMATION_DIRECTIONS.RIGHT,
22 | inEffect: ANIMATION_NAMES.SLIDE_AND_FADE,
23 | });
24 |
25 | function renderMinimalContent () {
26 | this.render(hbs`
27 | {{#stagger-set inDirection="left" inEffect="slideAndFade"}}
28 | Seattle
29 | {{/stagger-set}}
30 | `);
31 | }
32 |
33 |
34 | let dataDown;
35 | let actual, expected;
36 |
37 | moduleForComponent('stagger-set', 'Integration | Component | stagger set', {
38 | integration: true
39 | });
40 |
41 | test('rendering block content', function(assert) {
42 | // Set any properties with this.set('myProperty', 'value');
43 | // Handle any actions with this.on('myAction', function(val) { ... });
44 | renderMinimalContent.call(this);
45 |
46 | expected = 'Seattle';
47 | actual = getNode(this).textContent.trim();
48 | assert.equal(actual, expected);
49 | });
50 |
51 |
52 | skip(`broadcasting animation start and animation completion`, function (assert) {
53 |
54 | function onAnimationStart (event) {
55 | expected = true;
56 | actual = event instanceof window.AnimationEvent;
57 | assert.equal(actual, expected);
58 |
59 | expected = 'animationstart';
60 | actual = event.type;
61 | assert.equal(actual, expected);
62 | }
63 |
64 | function onAnimationComplete (event) {
65 | expected = true;
66 | actual = event instanceof window.AnimationEvent;
67 | assert.equal(actual, expected);
68 |
69 | expected = 'animationend';
70 | actual = event.type;
71 | assert.equal(actual, expected);
72 | }
73 |
74 | dataDown = DataDown.create({ showItems: false });
75 |
76 | this.set('onAnimationComplete', onAnimationComplete);
77 | this.set('onAnimationStart', onAnimationStart);
78 | this.set('dataDown', dataDown);
79 |
80 | run(() => {
81 | this.render(hbs`
82 | {{#stagger-set
83 | showItems=dataDown.showItems
84 | inEffect=dataDown.inEffect
85 | inDirection=dataDown.inDirection
86 | onAnimationStart=onAnimationStart
87 | onAnimationComplete=onAnimationComplete
88 | }}
89 | Seattle
90 | New York City
91 | Boston
92 | {{/stagger-set}}
93 | `);
94 | });
95 | });
96 |
97 |
98 | test(`toggling the element's animation name when the
99 | value of the \`showItems\` attribute changes after
100 | the initial render`, function (assert) {
101 |
102 | const inEffect = ANIMATION_NAMES.FADE;
103 | const inDirection = ANIMATION_DIRECTIONS.DOWN;
104 | const dataDown = DataDown.create({ showItems: false, inEffect, inDirection });
105 |
106 | this.set('dataDown', dataDown);
107 |
108 | this.render(hbs`
109 | {{#stagger-set
110 | inEffect=dataDown.inEffect
111 | inDirection=dataDown.inDirection
112 | showItems=dataDown.showItems
113 | }}
114 | Seattle
115 | New York City
116 | Boston
117 | {{/stagger-set}}
118 | `);
119 |
120 | expected = '';
121 | actual = getNode(this).children[0].style.animationName;
122 | assert.equal(actual, expected);
123 |
124 | run(() => {
125 | set(dataDown, 'showItems', true);
126 | });
127 |
128 | expected = KEYFRAMES_MAP[inDirection].in[inEffect];
129 | actual = getNode(this).children[0].style.animationName;
130 | assert.equal(actual, expected);
131 | });
132 |
133 |
134 | test(`mapping keyframes according to the current direction and effect name`, function (assert) {
135 |
136 | const dataDown = DataDown.create({ outEffect: null, showItems: false });
137 | this.set('dataDown', dataDown);
138 |
139 |
140 | Object.keys(ANIMATION_DIRECTIONS).forEach((animationDirection) => {
141 |
142 | Object.keys(ANIMATION_NAMES).forEach((animationName) => {
143 |
144 | run(() => {
145 | set(dataDown, 'inEffect', ANIMATION_NAMES[animationName]);
146 | set(dataDown, 'inDirection', ANIMATION_DIRECTIONS[animationDirection]);
147 | set(dataDown, 'showItems', true);
148 | });
149 |
150 | this.render(hbs`
151 | {{#stagger-set
152 | inDirection=dataDown.inDirection
153 | inEffect=dataDown.inEffect
154 | outEffect=dataDown.outEffect
155 | showItems=dataDown.showItems
156 | }}
157 | Seattle
158 | New York City
159 | Boston
160 | {{/stagger-set}}
161 | `);
162 |
163 |
164 | expected = KEYFRAMES_MAP[ANIMATION_DIRECTIONS[animationDirection]].in[ANIMATION_NAMES[animationName]];
165 | actual = getNode(this).children[0].style.animationName;
166 | assert.equal(actual, expected);
167 |
168 | });
169 | });
170 | });
171 |
--------------------------------------------------------------------------------
/tests/dummy/app/templates/components/homepage-demo.hbs:
--------------------------------------------------------------------------------
1 |
141 |
--------------------------------------------------------------------------------
/tests/unit/mixins/stagger-set-test.js:
--------------------------------------------------------------------------------
1 | /* jshint ignore:start */
2 |
3 | import Ember from 'ember';
4 | import StaggerSetMixin from 'ember-stagger-swagger/mixins/stagger-set';
5 | import { module, test } from 'qunit';
6 | import Constants from 'ember-stagger-swagger/constants/constants';
7 | import testDefaultsAfterInvalidInstantiation from '../../helpers/unit/test-defaults-after-invalid-instantiation';
8 |
9 | const {
10 | ANIMATION_DIRECTIONS,
11 | ANIMATION_NAMES,
12 | KEYFRAMES_MAP,
13 | DEFAULTS,
14 | } = Constants;
15 |
16 | const minimalConfig = {
17 | inDirection: ANIMATION_DIRECTIONS.LEFT,
18 | inEffect: ANIMATION_NAMES.SLIDE_AND_FADE,
19 | };
20 |
21 |
22 | let StaggerSetObject, subject;
23 | let actual, expected;
24 |
25 | module('Unit | Mixin | stagger list');
26 |
27 | test('requiring a valid `inDirection`', function (assert) {
28 | assert.expect(3);
29 | StaggerSetObject = Ember.Object.extend(StaggerSetMixin);
30 |
31 | assert.throws(
32 | () => { subject = StaggerSetObject.create({ inDirection: null }); },
33 | /inDirection/,
34 | 'failing to specify a `inDirection` throws an error'
35 | );
36 |
37 | assert.throws(
38 | () => { subject = StaggerSetObject.create({ inDirection: 'spiral' }); },
39 | /inDirection/,
40 | 'specifying an invalid `inDirection` throws an error'
41 | );
42 |
43 | assert.ok(subject = StaggerSetObject.create(minimalConfig));
44 | });
45 |
46 | test('requiring a valid `inEffect`', function (assert) {
47 | assert.expect(3);
48 | StaggerSetObject = Ember.Object.extend(StaggerSetMixin);
49 |
50 | assert.throws(
51 | () => {
52 | subject = StaggerSetObject.create({
53 | inDirection: ANIMATION_DIRECTIONS.LEFT,
54 | inEffect: null,
55 | });
56 | },
57 | /inEffect/,
58 | 'failing to specify a `inEffect` throws an error'
59 | );
60 |
61 | assert.throws(
62 | () => {
63 | subject = StaggerSetObject.create({
64 | inDirection: ANIMATION_DIRECTIONS.LEFT,
65 | inEffect: 'supernova',
66 | });
67 | },
68 | 'specifying an invalid `inEffect` throws an error'
69 | );
70 |
71 | assert.ok(subject = StaggerSetObject.create(minimalConfig));
72 | });
73 |
74 |
75 | test(`\`staggerInterval\` defaults to ${DEFAULTS.STAGGER_INTERVAL_MS} when it's
76 | not set to a value greater than or equal to 32`, function (assert) {
77 | assert.expect(4);
78 |
79 | StaggerSetObject = Ember.Object.extend(StaggerSetMixin);
80 |
81 | const valid = 90;
82 | const invalidNaN = 'superfast';
83 | const invalidNumber = -900;
84 | const invalidNumber2 = 31;
85 |
86 | subject = StaggerSetObject.create({ ...minimalConfig, staggerInterval: valid });
87 | expected = valid;
88 | actual = subject.get('staggerInterval');
89 | assert.equal(actual, expected);
90 |
91 | expected = DEFAULTS.STAGGER_INTERVAL_MS;
92 |
93 | testDefaultsAfterInvalidInstantiation(
94 | assert,
95 | StaggerSetObject,
96 | 'staggerInterval',
97 | expected,
98 | [invalidNaN, invalidNumber, invalidNumber2],
99 | minimalConfig
100 | );
101 | });
102 |
103 | test(`\`inDelay\` defaults to ${DEFAULTS.ANIMATION_DELAY_IN} if not set
104 | to a number greater than 0`, function (assert) {
105 | assert.expect(3);
106 |
107 | StaggerSetObject = Ember.Object.extend(StaggerSetMixin);
108 |
109 | const valid = 90;
110 | const invalidNaN = 'superfast';
111 | const invalidNumber = -900;
112 |
113 | subject = StaggerSetObject.create({...minimalConfig, inDelay: valid });
114 | expected = valid;
115 | actual = subject.get('inDelay');
116 | assert.equal(actual, expected);
117 |
118 | expected = DEFAULTS.ANIMATION_DELAY_IN;
119 |
120 | testDefaultsAfterInvalidInstantiation(
121 | assert,
122 | StaggerSetObject,
123 | 'inDelay',
124 | expected,
125 | [invalidNaN, invalidNumber],
126 | minimalConfig
127 | );
128 | });
129 |
130 | test(`\`outDelay\` defaults to matching \`inDelay\` if not set
131 | to a number greater than 0`, function (assert) {
132 |
133 | assert.expect(3);
134 |
135 | StaggerSetObject = Ember.Object.extend(StaggerSetMixin);
136 |
137 | const valid = 90;
138 | const invalidNaN = 'superfast';
139 | const invalidNumber = -900;
140 |
141 | subject = StaggerSetObject.create({...minimalConfig, outDelay: valid });
142 | expected = valid;
143 | actual = subject.get('outDelay');
144 | assert.equal(actual, expected);
145 |
146 | expected = DEFAULTS.ANIMATION_DELAY_IN;
147 |
148 | testDefaultsAfterInvalidInstantiation(
149 | assert,
150 | StaggerSetObject,
151 | 'outDelay',
152 | expected,
153 | [invalidNaN, invalidNumber],
154 | minimalConfig
155 | );
156 | });
157 |
158 | test(`\`inDuration\` defaults to ${DEFAULTS.ANIMATION_DURATION_IN} if not set
159 | to a number greater than 0`, function (assert) {
160 |
161 | assert.expect(4);
162 |
163 | StaggerSetObject = Ember.Object.extend(StaggerSetMixin);
164 |
165 | const valid = 90;
166 | const invalidNaN = 'superfast';
167 | const invalidNumber = 0;
168 | const invalidNumber2 = -900;
169 |
170 | subject = StaggerSetObject.create({...minimalConfig, inDuration: valid });
171 | expected = valid;
172 | actual = subject.get('inDuration');
173 | assert.equal(actual, expected);
174 |
175 | expected = DEFAULTS.ANIMATION_DURATION_IN;
176 |
177 | testDefaultsAfterInvalidInstantiation(
178 | assert,
179 | StaggerSetObject,
180 | 'inDuration',
181 | expected,
182 | [invalidNaN, invalidNumber, invalidNumber2],
183 | minimalConfig
184 | );
185 | });
186 |
187 | test(`\`outDuration\` defaults to matching \`inDuration\` if not set
188 | to a number greater than 0`, function (assert) {
189 |
190 | assert.expect(4);
191 |
192 | StaggerSetObject = Ember.Object.extend(StaggerSetMixin);
193 |
194 | const valid = 90;
195 | const invalidNaN = 'superfast';
196 | const invalidNumber = 0;
197 | const invalidNumber2 = -900;
198 |
199 | subject = StaggerSetObject.create({...minimalConfig, outDuration: valid });
200 | expected = valid;
201 | actual = subject.get('outDuration');
202 | assert.equal(actual, expected);
203 |
204 | expected = DEFAULTS.ANIMATION_DURATION_IN;
205 |
206 | testDefaultsAfterInvalidInstantiation(
207 | assert,
208 | StaggerSetObject,
209 | 'outDuration',
210 | expected,
211 | [invalidNaN, invalidNumber, invalidNumber2],
212 | minimalConfig
213 | );
214 | });
215 |
216 | test(`\`inTimingFunc\` defaults to ${DEFAULTS.TIMING_FUNCTION_IN} if not set`, function (assert) {
217 | assert.expect(2);
218 | StaggerSetObject = Ember.Object.extend(StaggerSetMixin);
219 | expected = DEFAULTS.TIMING_FUNCTION_IN;
220 |
221 | subject = StaggerSetObject.create({...minimalConfig, inTimingFunc: DEFAULTS.TIMING_FUNCTION_IN });
222 | actual = subject.get('inTimingFunc');
223 | assert.equal(actual, expected);
224 |
225 | subject = StaggerSetObject.create({...minimalConfig, inTimingFunc: null });
226 | actual = subject.get('inTimingFunc');
227 | assert.equal(actual, expected);
228 | });
229 |
230 | test(`\`outTimingFunc\` defaults to ${DEFAULTS.TIMING_FUNCTION_OUT} if not set`, function (assert) {
231 | assert.expect(2);
232 | StaggerSetObject = Ember.Object.extend(StaggerSetMixin);
233 | expected = DEFAULTS.TIMING_FUNCTION_OUT;
234 |
235 | subject = StaggerSetObject.create({...minimalConfig, outTimingFunc: DEFAULTS.TIMING_FUNCTION_OUT });
236 | actual = subject.get('outTimingFunc');
237 | assert.equal(actual, expected);
238 |
239 | subject = StaggerSetObject.create({...minimalConfig, outTimingFunc: null });
240 | actual = subject.get('outTimingFunc');
241 | assert.equal(actual, expected);
242 | });
243 |
244 | test(`\`outDirection\` defaults to matching \`inDirection\` if not set`, function (assert) {
245 | assert.expect(1);
246 | StaggerSetObject = Ember.Object.extend(StaggerSetMixin);
247 |
248 | inDirection = ANIMATION_DIRECTIONS.LEFT;
249 | subject = StaggerSetObject.create({...minimalConfig, inDirection, outDirection: null });
250 |
251 | expected = inDirection;
252 | actual = subject.get('outDirection');
253 | assert.equal(actual, expected);
254 | });
255 |
256 | test(`\`outEffect\` defaults to matching \`inEffect\` if not set`, function (assert) {
257 | assert.expect(1);
258 | StaggerSetObject = Ember.Object.extend(StaggerSetMixin);
259 |
260 | inEffect = ANIMATION_NAMES.SLIDE_AND_FADE;
261 | subject = StaggerSetObject.create({...minimalConfig, inEffect, outEffect: null });
262 |
263 | expected = inEffect;
264 | actual = subject.get('inEffect');
265 | assert.equal(actual, expected);
266 | });
267 |
--------------------------------------------------------------------------------
/app/styles/ember-stagger-swagger.css:
--------------------------------------------------------------------------------
1 | /* TODO: JS all the things? */
2 | .__ember-stagger-swagger__stagger-set.hasnt-entered > :not(.completed-initial-enter) {
3 | opacity: 0;
4 | }
5 |
6 |
7 |
8 | /* --------------------- KEYFRAMES --------------------- */
9 |
10 |
11 | /**
12 | * ---------------------------------------------------------------
13 | * INs
14 | * ---------------------------------------------------------------
15 | */
16 |
17 | /**
18 | * SlideAndFade
19 | */
20 | @keyframes __EmberStaggerSwagger__SlideAndFadeInFromLeft {
21 | 0% {
22 | opacity: 0;
23 | visibility: visible;
24 | transform: translate3d(-100%, 0, 0);
25 | }
26 | 100% {
27 | opacity: 1;
28 | transform: translate3d(0, 0, 0);
29 | }
30 | }
31 | @-webkit-keyframes __EmberStaggerSwagger__SlideAndFadeInFromLeft {
32 | 0% {
33 | opacity: 0;
34 | visibility: visible;
35 | -webkit-transform: translate3d(-100%, 0, 0);
36 | }
37 | 100% {
38 | opacity: 1;
39 | -webkit-transform: translate3d(0, 0, 0);
40 | }
41 | }
42 |
43 |
44 | @keyframes __EmberStaggerSwagger__SlideAndFadeInFromRight {
45 | 0% {
46 | opacity: 0;
47 | visibility: visible;
48 | transform: translate3d(100%, 0, 0);
49 | }
50 | 100% {
51 | opacity: 1;
52 | transform: translate3d(0, 0, 0);
53 | }
54 | }
55 | @-webkit-keyframes __EmberStaggerSwagger__SlideAndFadeInFromRight {
56 | 0% {
57 | opacity: 0;
58 | visibility: visible;
59 | transform: translate3d(100%, 0, 0);
60 | }
61 | 100% {
62 | opacity: 1;
63 | transform: translate3d(0, 0, 0);
64 | }
65 | }
66 |
67 |
68 | @keyframes __EmberStaggerSwagger__SlideAndFadeInFromTop {
69 | 0% {
70 | opacity: 0;
71 | visibility: visible;
72 | transform: translate3d(0, -100%, 0);
73 | }
74 | 100% {
75 | opacity: 1;
76 | transform: translate3d(0, 0, 0);
77 | }
78 | }
79 | @-webkit-keyframes __EmberStaggerSwagger__SlideAndFadeInFromTop {
80 | 0% {
81 | opacity: 0;
82 | visibility: visible;
83 | -webkit-transform: translate3d(0, -100%, 0);
84 | }
85 | 100% {
86 | opacity: 1;
87 | -webkit-transform: translate3d(0, 0, 0);
88 | }
89 | }
90 |
91 |
92 | @keyframes __EmberStaggerSwagger__SlideAndFadeInFromBottom {
93 | 0% {
94 | opacity: 0;
95 | visibility: visible;
96 | transform: translate3d(0, 100%, 0);
97 | }
98 | 100% {
99 | opacity: 1;
100 | transform: translate3d(0, 0, 0);
101 | }
102 | }
103 | @-webkit-keyframes __EmberStaggerSwagger__SlideAndFadeInFromBottom {
104 | 0% {
105 | opacity: 0;
106 | visibility: visible;
107 | -webkit-transform: translate3d(0, 100%, 0);
108 | }
109 | 100% {
110 | opacity: 1;
111 | -webkit-transform: translate3d(0, 0, 0);
112 | }
113 | }
114 |
115 |
116 |
117 | /**
118 | * Slide
119 | */
120 | @keyframes __EmberStaggerSwagger__SlideInFromLeft {
121 | 0% {
122 | opacity: 1;
123 | visibility: visible;
124 | transform: translate3d(-100%, 0, 0);
125 | }
126 | 100% {
127 | transform: translate3d(0, 0, 0);
128 | }
129 | }
130 | @-webkit-keyframes __EmberStaggerSwagger__SlideInFromLeft {
131 | 0% {
132 | opacity: 1;
133 | visibility: visible;
134 | -webkit-transform: translate3d(-100%, 0, 0);
135 | }
136 | 100% {
137 | -webkit-transform: translate3d(0, 0, 0);
138 | }
139 | }
140 |
141 |
142 | @keyframes __EmberStaggerSwagger__SlideInFromRight {
143 | 0% {
144 | opacity: 1;
145 | visibility: visible;
146 | transform: translate3d(100%, 0, 0);
147 | }
148 | 100% {
149 | transform: translate3d(0, 0, 0);
150 | }
151 | }
152 | @-webkit-keyframes __EmberStaggerSwagger__SlideInFromRight {
153 | 0% {
154 | opacity: 1;
155 | visibility: visible;
156 | -webkit-transform: translate3d(100%, 0, 0);
157 | }
158 | 100% {
159 | -webkit-transform: translate3d(0, 0, 0);
160 | }
161 | }
162 |
163 |
164 | @keyframes __EmberStaggerSwagger__SlideInFromTop {
165 | 0% {
166 | opacity: 1;
167 | visibility: visible;
168 | transform: translate3d(0, -100%, 0);
169 | }
170 | 100% {
171 | transform: translate3d(0, 0, 0);
172 | }
173 | }
174 | @-webkit-keyframes __EmberStaggerSwagger__SlideInFromTop {
175 | 0% {
176 | opacity: 1;
177 | visibility: visible;
178 | -webkit-transform: translate3d(0, -100%, 0);
179 | }
180 | 100% {
181 | -webkit-transform: translate3d(0, 0, 0);
182 | }
183 | }
184 |
185 |
186 | @keyframes __EmberStaggerSwagger__SlideInFromBottom {
187 | 0% {
188 | opacity: 1;
189 | visibility: visible;
190 | transform: translate3d(0, 100%, 0);
191 | }
192 | 100% {
193 | transform: translate3d(0, 0, 0);
194 | }
195 | }
196 | @-webkit-keyframes __EmberStaggerSwagger__SlideInFromBottom {
197 | 0% {
198 | opacity: 1;
199 | visibility: visible;
200 | -webkit-transform: translate3d(0, 100%, 0);
201 | }
202 | 100% {
203 | -webkit-transform: translate3d(0, 0, 0);
204 | }
205 | }
206 |
207 |
208 |
209 | /**
210 | * Fade
211 | */
212 | @keyframes __EmberStaggerSwagger__FadeIn {
213 | 0% {
214 | visibility: visible;
215 | opacity: 0;
216 | }
217 | 100% {
218 | opacity: 1;
219 | }
220 | }
221 | @-webkit-keyframes __EmberStaggerSwagger__FadeIn {
222 | 0% {
223 | visibility: visible;
224 | opacity: 0;
225 | }
226 | 100% {
227 | opacity: 1;
228 | }
229 | }
230 |
231 |
232 |
233 | /**
234 | * Scale
235 | */
236 | @keyframes __EmberStaggerSwagger__ScaleIn {
237 | 0% {
238 | visibility: visible;
239 | opacity: 1;
240 | transform: scale(0);
241 | }
242 | 100% {
243 | opacity: 1;
244 | transform: scale(1);
245 | }
246 | }
247 | @-webkit-keyframes __EmberStaggerSwagger__ScaleIn {
248 | 0% {
249 | visibility: visible;
250 | opacity: 1;
251 | -webkit-transform: scale(0);
252 | }
253 | 100% {
254 | opacity: 1;
255 | -webkit-transform: scale(1);
256 | }
257 | }
258 |
259 |
260 |
261 |
262 |
263 |
264 | /**
265 | * ---------------------------------------------------------------
266 | * OUTs
267 | * ---------------------------------------------------------------
268 | */
269 |
270 | /**
271 | * SlideAndFade
272 | */
273 | @keyframes __EmberStaggerSwagger__SlideAndFadeOutRight {
274 | 0% {
275 | opacity: 1;
276 | transform: translate3d(0, 0, 0);
277 | }
278 | 100% {
279 | opacity: 0;
280 | visibility: hidden;
281 | transform: translate3d(100%, 0, 0);
282 | }
283 | }
284 | @-webkit-keyframes __EmberStaggerSwagger__SlideAndFadeOutRight {
285 | 0% {
286 | opacity: 1;
287 | -webkit-transform: translate3d(0, 0, 0);
288 | }
289 | 100% {
290 | opacity: 0;
291 | visibility: hidden;
292 | -webkit-transform: translate3d(100%, 0, 0);
293 | }
294 | }
295 |
296 |
297 | @keyframes __EmberStaggerSwagger__SlideAndFadeOutLeft {
298 | 0% {
299 | opacity: 1;
300 | visibility: visible;
301 | transform: translate3d(0, 0, 0);
302 | }
303 | 100% {
304 | opacity: 0;
305 | visibility: hidden;
306 | transform: translate3d(-100%, 0, 0);
307 | }
308 | }
309 | @-webkit-keyframes __EmberStaggerSwagger__SlideAndFadeOutLeft {
310 | 0% {
311 | opacity: 1;
312 | visibility: visible;
313 | -webkit-transform: translate3d(0, 0, 0);
314 | }
315 | 100% {
316 | opacity: 0;
317 | visibility: hidden;
318 | -webkit-transform: translate3d(-100%, 0, 0);
319 | }
320 | }
321 |
322 |
323 | @keyframes __EmberStaggerSwagger__SlideAndFadeOutDown {
324 | 0% {
325 | opacity: 1;
326 | transform: translate3d(0, 0, 0);
327 | }
328 | 100% {
329 | opacity: 0;
330 | visibility: hidden;
331 | transform: translate3d(0, 100%, 0);
332 | }
333 | }
334 | @-webkit-keyframes __EmberStaggerSwagger__SlideAndFadeOutDown {
335 | 0% {
336 | opacity: 1;
337 | -webkit-transform: translate3d(0, 0, 0);
338 | }
339 | 100% {
340 | opacity: 0;
341 | visibility: hidden;
342 | -webkit-transform: translate3d(0, 100%, 0);
343 | }
344 | }
345 |
346 |
347 | @keyframes __EmberStaggerSwagger__SlideAndFadeOutUp {
348 | 0% {
349 | opacity: 1;
350 | transform: translate3d(0, 0, 0);
351 | }
352 | 100% {
353 | opacity: 0;
354 | visibility: hidden;
355 | transform: translate3d(0, -100%, 0);
356 | }
357 | }
358 | @-webkit-keyframes __EmberStaggerSwagger__SlideAndFadeOutUp {
359 | 0% {
360 | opacity: 1;
361 | -webkit-transform: translate3d(0, 0, 0);
362 | }
363 | 100% {
364 | opacity: 0;
365 | visibility: hidden;
366 | -webkit-transform: translate3d(0, -100%, 0);
367 | }
368 | }
369 |
370 |
371 |
372 | /**
373 | * Slide
374 | */
375 | @keyframes __EmberStaggerSwagger__SlideOutRight {
376 | 0% {
377 | transform: translate3d(0, 0, 0);
378 | }
379 | 100% {
380 | transform: translate3d(100%, 0, 0);
381 | visibility: hidden;
382 | }
383 | }
384 | @-webkit-keyframes __EmberStaggerSwagger__SlideOutRight {
385 | 0% {
386 | -webkit-transform: translate3d(0, 0, 0);
387 | }
388 | 100% {
389 | -webkit-transform: translate3d(100%, 0, 0);
390 | visibility: hidden;
391 | }
392 | }
393 |
394 |
395 | @keyframes __EmberStaggerSwagger__SlideOutLeft {
396 | 0% {
397 | transform: translate3d(0, 0, 0);
398 | }
399 | 100% {
400 | transform: translate3d(-100%, 0, 0);
401 | visibility: hidden;
402 | }
403 | }
404 | @-webkit-keyframes __EmberStaggerSwagger__SlideOutLeft {
405 | 0% {
406 | -webkit-transform: translate3d(0, 0, 0);
407 | }
408 | 100% {
409 | -webkit-transform: translate3d(-100%, 0, 0);
410 | visibility: hidden;
411 | }
412 | }
413 |
414 |
415 | @keyframes __EmberStaggerSwagger__SlideOutDown {
416 | 0% {
417 | }
418 | 100% {
419 | visibility: hidden;
420 | transform: translate3d(0, 100%, 0);
421 | }
422 | }
423 | @-webkit-keyframes __EmberStaggerSwagger__SlideOutDown {
424 | 0% {
425 | }
426 | 100% {
427 | visibility: hidden;
428 | -webkit-transform: translate3d(0, 100%, 0);
429 | }
430 | }
431 |
432 |
433 | @keyframes __EmberStaggerSwagger__SlideOutUp {
434 | 0% {
435 | transform: translate3d(0, 0, 0);
436 | }
437 | 100% {
438 | visibility: hidden;
439 | transform: translate3d(0, -100%, 0);
440 | }
441 | }
442 | @-webkit-keyframes __EmberStaggerSwagger__SlideOutUp {
443 | 0% {
444 | -webkit-transform: translate3d(0, 0, 0);
445 | }
446 | 100% {
447 | visibility: hidden;
448 | -webkit-transform: translate3d(0, -100%, 0);
449 | }
450 | }
451 |
452 |
453 |
454 | /**
455 | * Fade
456 | */
457 | @keyframes __EmberStaggerSwagger__FadeOut {
458 | 0% {
459 | opacity: 1;
460 | }
461 | 100% {
462 | visibility: hidden;
463 | opacity: 0;
464 | }
465 | }
466 | @-webkit-keyframes __EmberStaggerSwagger__FadeOut {
467 | 0% {
468 | opacity: 1;
469 | }
470 | 100% {
471 | visibility: hidden;
472 | opacity: 0;
473 | }
474 | }
475 |
476 |
477 |
478 | /**
479 | * Scale
480 | */
481 | @keyframes __EmberStaggerSwagger__ScaleOut {
482 | 0% {
483 | transform: scale(1);
484 | }
485 | 100% {
486 | visibility: hidden;
487 | transform: scale(0);
488 | }
489 | }
490 | @-webkit-keyframes __EmberStaggerSwagger__ScaleOut {
491 | 0% {
492 | -webkit-transform: scale(1);
493 | }
494 | 100% {
495 | visibility: hidden;
496 | -webkit-transform: scale(0);
497 | }
498 | }
499 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ember-stagger-swagger
2 |
3 | [](https://badge.fury.io/js/ember-stagger-swagger) [](https://travis-ci.org/BrianSipple/ember-stagger-swagger) [](http://emberobserver.com/addons/ember-stagger-swagger)
4 |
5 | *GPU stagger animation for Ember list items*
6 |
7 | 
8 |
9 | See the demo [here][demo].
10 |
11 |
12 | ## Installation
13 | ```
14 | ember install ember-stagger-swagger
15 | ```
16 |
17 | ## Usage
18 |
19 | `ember-stagger-swagger` ships with a `stagger-set` component that can be used directly in a template to wrap the items that you wish to animate.
20 |
21 | Conceptually, the component treats all direct child elements as its _set items_ or _list items_:
22 |
23 | ```htmlbars
24 |
25 | Spell Ingredients
26 |
27 | {{#stagger-set inDirection="right" inEffect="slideAndFade"}}
28 |
29 | {{#each potions as |potion|}}
30 | {{potion.name}}
31 | {{/each}}
32 |
33 | {{/stagger-set}}
34 |
35 | ```
36 |
37 | Additionally, `ember-stagger-swagger` exposes a mixin that can be imported directly from the addon and extended however you see fit:
38 | ```js.es6
39 | import StaggerSetMixin from 'ember-stagger-swagger/mixins/stagger-set';
40 | ```
41 |
42 | ## API
43 |
44 | ##### `inDirection`
45 | * _description_: The direction of animation when the items stagger into view.
46 | * ***_required_: yes***
47 | * _constraints_: A string keyword matching either `'left'`, `'down'`, `'right'`, or `'up'`.
48 |
49 | ##### `outDirection`
50 | * _description_: The direction of animation when the items stagger out of view.
51 | * _required_: no
52 | * _default_: The provided `inDirection`.
53 | * _constraints_: A string keyword matching either `'left'`, `'down'`, `'right'`, or `'up'`.
54 |
55 | ##### `inEffect`
56 | * _description_: A recognized animation effect applied to each list item when it's animating in.
57 | * ***_required_: yes***
58 | * _constraints_: A string keyword matching either `'slideAndFade'`, `'slide'`, `'fade'`, or `'scale'` (see the [demo][demo] for a preview of each).
59 |
60 | ##### `outEffect`
61 | * _description_: A recognized animation effect applied to each list item when it's animating in.
62 | * _required_: no
63 | * _default_: the provided `inEffect`
64 | * _constraints_: A string keyword matching either `'slideAndFade'`, `'slide'`, `'fade'`, or `'scale'` (see the [demo][demo] for a preview of each).
65 |
66 | ##### `customInEffect`
67 | * _description_: An [animation name](https://developer.mozilla.org/en-US/docs/Web/CSS/animation-name) matching a [CSS animation keyframe](https://developer.mozilla.org/en-US/docs/Web/CSS/@keyframes) that you have defined in your project. If specified alongside an `inEffect`, this name will take precedence. This can be used to create your own effects and integrate them with `stagger-swagger`'s built-in functionality.
68 | * _required_: no
69 | * _default_: `null`
70 | * _constraints_: None. Just make sure the name matches a CSS animation keyframe that you have defined in your project.
71 |
72 | ##### `customOutEffect`
73 | * _description_: An [animation name](https://developer.mozilla.org/en-US/docs/Web/CSS/animation-name) matching a [CSS animation keyframe](https://developer.mozilla.org/en-US/docs/Web/CSS/@keyframes) that you have defined in your project. If specified alongside an `outEffect`, this name will take precedence. This can be used to create your own effects and integrate them with `stagger-swagger`'s built-in functionality.
74 | * _required_: no
75 | * _default_: `null`
76 | * _constraints_: None. Just make sure the name matches a CSS animation keyframe that you have defined in your project.
77 |
78 |
79 | ##### `showItems`
80 | * _description_: Manual hook for toggling the set between its entrance and exit animations.
81 | * _required_: no
82 | * _default_: `true` to correspond with [`enterOnRender`](#api-enterOnRender)'s default.
83 | * _constraints_: `true` or `false`
84 |
85 |
86 | ##### `enterOnRender`
87 | * _description_: Whether or not the elements in the stagger set should animate into view when the component is rendered.
88 | *Note:* `enterOnRender` allows for a
89 | more fine-grained level of control than just using `showItems`. Without `enterOnRender`, initializing the component with `showItems` set to `true` will cause the items to render in their normal visible state, from which the animation can be toggled further. Setting `enterOnRender` to true -- in conjunction with setting `showItems` to true (both of which are the default) -- creates a stagger-in animation on render and then hinges on the state of `showItems` going forward.
90 | * _required_: no
91 | * _default_: `true`
92 | * _constraints_: `true` or `false`
93 |
94 |
95 | ##### `staggerInterval`
96 | * _description_: the number of milliseconds between the animation of successive items in the set.
97 | * _required_: no
98 | * _default_: 32ms. 2 frames per item (1 frame @ 60fps ~= 16ms) creates a noticeably staggered but still-perceptively [smooth and fluid motion](https://en.wikipedia.org/wiki/Traditional_animation#.22Shooting_on_twos.22).
99 | * _constraints_: a number value greater than or equal to 32.
100 |
101 |
102 | ##### `inDelay`
103 | * _description_: Duration of delay for the items' entrance animation (when the animation is activated).
104 | * _required_: no
105 | * _default_: 0
106 | * _constraints_: a number value greater than or equal to 0.
107 |
108 | ##### `outDelay`
109 | * _description_: Duration of delay for the items' exit animation (when the animation is activated).
110 | * _required_: no
111 | * _default_: 0
112 | * _constraints_: a number value greater than or equal to 0.
113 |
114 |
115 | ##### `inTimingFunc`
116 | * _description_: The [animation-timing-function](https://developer.mozilla.org/en-US/docs/Web/CSS/animation-timing-function) applied to each item in the set when it's animating in.
117 | * _required_: no
118 | * _default_: `cubic-bezier(0.215, 0.610, 0.355, 1.000)` (AKA ["ease-out-cubic"](http://easings.net/#easeOutCubic))
119 | * _constraints_: a string matching any [valid CSS `timing-function` value](https://developer.mozilla.org/en-US/docs/Web/CSS/timing-function). If this value is invalid, the browser will default to using [`ease`](http://cubic-bezier.com/#.25,.1,.25,1).
120 |
121 |
122 | ##### `outTimingFunc`
123 | * _description_: The [animation-timing-function](https://developer.mozilla.org/en-US/docs/Web/CSS/animation-timing-function) applied to each item in the set when it's animating out.
124 | * _required_: no
125 | * _default_: `cubic-bezier(0.55, 0.055, 0.675, 0.19)` (AKA ["ease-in-cubic"](http://easings.net/#easeInCubic))
126 | * _constraints_: a string matching any [valid CSS `timing-function` value](https://developer.mozilla.org/en-US/docs/Web/CSS/timing-function). If this value is invalid, the browser will default to using [`ease`](http://cubic-bezier.com/#.25,.1,.25,1).
127 |
128 | ##### `inDuration`
129 | * _description_: The duration (in milliseconds) that *a single item* will take when animating in.
130 | * _required_: no
131 | * _default_: 500
132 | * _constraints_: a numeric value greater than 0
133 |
134 | ##### `outDuration`
135 | * _description_: The duration (in milliseconds) that *a single item* will take when animating out.
136 | * _required_: no
137 | * _default_: 500
138 | * _constraints_: a numeric value greater than 0
139 |
140 | ##### `duration`
141 | * _description_: A convenience property to set a single duration on both the entrance and exit animations. If set alongside any `inDuration` or `outDuration`, this property will take precedence
142 | * _required_: no
143 | * _default_: null (property is ignored if unset),
144 | * _constraints_: a numeric value greater than zero
145 |
146 |
147 |
148 |
149 | ### Practical Tips
150 |
151 | #### Styling
152 | Because the DOM elements of Ember components are, by default, ``s, and because it handles setting an `animationName` property on the component's direct children, you can safely design, conceptualize, and style your child elements as you normally would for the list items of a relative container.
153 |
154 | Furthermore, because the keyframes for the built-in effects of `slide` and `slideAndFade` define transforms to bring their element in or out of its container's visible bounds (e.g., `transform: translate3d(-100, 0, 0)` at the 100%-stop of a left slide), it may well be useful to restrict overflow on the top-level component's element so that the children disappear when they're outside of said bounds.
155 |
156 | The [stagger-set "list items" demo][demo] is an example of how this would appear.
157 |
158 | #### Creating Animation Effects
159 | By default, a `stagger-list` component will attempt to map the keywords provided for `inEffect` or `outEffect` to one of its [built-in keyframes](/app/styles/_keyframes.css).
160 |
161 | However, you're free to implement your own keyframes and have them called instead. Just define them in your stylesheets as you would normally, and then pass the keyframe name to a `stagger-list` as a string argument for either `customInEffect` or `customOutEffect`. When these attributes are defined, `stagger-list` will always set them on the `animation-name` property of its child elements' style definition at the appropriate time.
162 |
163 | ## Action Hooks
164 |
165 | * `onAnimationStart`: called immediately after the _first_ item in the set triggers its [`animationstart`](https://developer.mozilla.org/en-US/docs/Web/Events/animationstart) event.
166 | * called with:
167 | * `animationEvent`: the [`animationevent` object](https://developer.mozilla.org/en-US/docs/Web/Events/animationstart#Properties)
168 |
169 | * `onAnimationComplete`: called immediately after the _last_ item in the set triggers its [`animationend`](https://developer.mozilla.org/en-US/docs/Web/Events/animationend) event.
170 | * called with:
171 | * `animationEvent`: the [`animationevent` object](https://developer.mozilla.org/en-US/docs/Web/Events/animationend#Properties)
172 |
173 | Together, these hooks can provide more control over interactions with the component during its animation. For example, if you set up a button to trigger toggles of the animation, you might want to make sure that it's disabled between the start and completion events.
174 |
175 | ```htmlbars
176 |
177 |
Spell Ingredients
178 |
179 | {{#stagger-set
180 | inDirection="right"
181 | inEffect="slideAndFade"
182 | onAnimationStart=(action 'loadCannons')
183 | onAnimationComplete=(action 'fireCannons')
184 | }}
185 |
186 | {{#each potions as |potion|}}
187 | {{potion.name}}
188 | {{/each}}
189 |
190 | {{/stagger-set}}
191 |
192 | ```
193 |
194 |
195 | ## Future Goals & Ideas
196 | * _Tentative_: [Explore improved effects for the vertical `slide` animation](#link-to-issue)?
197 | * possibly break the mixins apart to deal with vertical and horizontal animation separately?
198 | * _Tentative_: Removing need for any CSS by using the [Web Animations API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Animations_API)?
199 | * Libraries like GSAP or Velocity are great for enabling pure JavaScript animation today (see: [`liquid-fire-velocity`](https://github.com/ember-animation/liquid-fire-velocity)), but they're a bit too heavy for this addon, which only seeks to provide a handful of base effects and mainly concerns itself with encapsulating functionality.
200 |
201 |
202 | ## Developing Locally
203 |
204 | ### Installation
205 |
206 | * `git clone` this repository
207 | * `npm install`
208 | * `bower install`
209 |
210 | ### Running
211 |
212 | * `ember server`
213 | * Visit your app at http://localhost:4200.
214 |
215 | ### Running Tests
216 |
217 | * `npm test` (Runs `ember try:testall` to test your addon against multiple Ember versions)
218 | * `ember test`
219 | * `ember test --server`
220 |
221 | ### Building
222 |
223 | * `ember build`
224 |
225 | For more information on using ember-cli, visit [http://www.ember-cli.com/](http://www.ember-cli.com/).
226 |
227 |
228 |
229 | [demo]: http://www.sipple.io/ember-stagger-swagger-demo/
230 |
--------------------------------------------------------------------------------
/addon/mixins/stagger-set.js:
--------------------------------------------------------------------------------
1 | /**
2 | * stagger-set mixin
3 | *
4 | * A component mixin that renders list items by performing a staggered animation of their
5 | * entrance/exit by listening to a "showItems" binding.
6 | *
7 | * A "list item" is considered anything that's supplied as a direct child
8 | * element of the component's template block.
9 | */
10 | import Ember from 'ember';
11 | import Constants from 'ember-stagger-swagger/constants/constants';
12 | import getAnimationPrefix from 'ember-stagger-swagger/utils/get-animation-prefix';
13 |
14 | const {
15 | Mixin,
16 | computed,
17 | run,
18 | Logger: { warn },
19 | assert,
20 | isNone,
21 | } = Ember;
22 |
23 | const {
24 | notEmpty,
25 | } = computed;
26 |
27 | const {
28 | CLASS_NAMES,
29 | DEFAULTS,
30 | ANIMATION_DIRECTIONS,
31 | ANIMATION_NAMES,
32 | KEYFRAMES_MAP,
33 | } = Constants;
34 |
35 | const warningPreface = 'ember-stagger-swagger:stagger-set-mixin: ';
36 |
37 | export default Mixin.create({
38 |
39 | classNames: ['__ember-stagger-swagger__stagger-set'],
40 | classNameBindings: [`hasListToggled::${CLASS_NAMES.untoggled}`],
41 |
42 | /* -------------------------------------------------------------------- *
43 | * ----------------------- API ------------------------ *
44 | * -------------------------------------------------------------------- */
45 | inDirection: null,
46 | outDirection: null,
47 | inEffect: null,
48 | outEffect: null,
49 | customInEffect: null,
50 | customOutEffect: null,
51 |
52 | /* trigger the entrance animation when this element is inserted into the DOM */
53 | enterOnRender: true,
54 |
55 | /**
56 | * Flag for manually triggering either the show or hide animation
57 | *
58 | * By default, the list items will animate in on render, but
59 | * but this allows the user to have full toggle control if they want it.
60 | */
61 | showItems: true,
62 |
63 | /* MILLESECONDS */
64 | staggerInterval: null,
65 |
66 | /* MILLESECONDS */
67 | inDelay: null,
68 | outDelay: null,
69 |
70 | /* Timing Function */
71 | inTimingFunc: null,
72 | outTimingFunc: null,
73 | timingFunc: null, // single timing function for both in and out
74 |
75 | /* Duration (milliseconds) */
76 | inDuration: null,
77 | outDuration: null,
78 | duration: null, // convinience option for a single in/out duration
79 | /* -------------------------------------------------------------------- *
80 | * ----------------------- /API ------------------------ *
81 | * -------------------------------------------------------------------- */
82 |
83 |
84 | isAnimating: false,
85 | hasListToggled: false,
86 |
87 | /**
88 | * Callback (to be initialized) for our animationstart event listener
89 | */
90 | _onStaggerStart: null,
91 |
92 | /**
93 | * Callback (to be initialized) for our animationend event listener
94 | */
95 | _onStaggerComplete: null,
96 |
97 | /**
98 | * Callback (to be initialized) for caching the DOM nodes of our child elements
99 | * when they get added to the DOM
100 | */
101 | _onChildElementsInserted: null,
102 | _childInsertionListener: null,
103 |
104 | /**
105 | * Array of cached "list item" elements to cache upon insertion
106 | */
107 | _listItemElems: null,
108 |
109 | /**
110 | * We'll be caching the animation prefix (if any) that's needed to
111 | * modify DOM element style properties.
112 | */
113 | _animationPrefix: null,
114 |
115 | hasItemsToAnimate: notEmpty('_listItemElems'),
116 |
117 | isReadyToAnimate: computed('hasItemsToAnimate', 'isAnimating', function isReadyToAnimate () {
118 | return this.get('hasItemsToAnimate') && !this.get('isAnimating');
119 | }),
120 |
121 | _inAnimationName: computed('inEffect', 'customInEffect', 'inDirection', function computeInAnimationName () {
122 | return this.get('customInEffect') || KEYFRAMES_MAP[this.get('inDirection')].in[this.get('inEffect')];
123 | }),
124 |
125 | _outAnimationName: computed('outEffect', 'customOutEffect', 'outDirection', function computeOutAnimationName () {
126 | return this.get('customOutEffect') || KEYFRAMES_MAP[this.get('outDirection')].out[this.get('outEffect')];
127 | }),
128 |
129 | needsToEnterAfterInit: computed('enterOnRender', 'hasListToggled', 'isReadyToAnimate', 'showItems', function needsInitialRender () {
130 | return (
131 | !this.get('hasListToggled') &&
132 | this.get('enterOnRender') &&
133 | this.get('showItems') &&
134 | this.get('isReadyToAnimate')
135 | );
136 | }),
137 |
138 | currentAnimationName: computed(
139 | '_inAnimationName',
140 | '_outAnimationName',
141 | 'showItems',
142 | 'needsToEnterAfterInit',
143 | function currentAnimationName() {
144 | return ( this.get('showItems') || this.get('needsToEnterAfterInit') ) ?
145 | this.get('_inAnimationName')
146 | :
147 | this.get('_outAnimationName');
148 | }
149 | ),
150 |
151 | currentAnimationDuration: computed('duration', 'inDuration', 'outDuration', 'showItems', function computeDuration () {
152 | // give priority to a valid general duration
153 | const generalDuration = this.get('duration');
154 | if (!isNone(generalDuration) && !Number.isNaN(Number(generalDuration)) && generalDuration > 0) {
155 | return generalDuration;
156 | }
157 |
158 | // otherwise, set according to the state of showItems
159 | return this.get('showItems') ? this.get('inDuration') : this.get('outDuration');
160 | }),
161 |
162 | currentAnimationTimingFunction: computed('inTimingFunc', 'outTimingFunc', 'showItems', function computeCurrentTimingFunction () {
163 | // otherwise, set according to the state of showItems
164 | return this.get('showItems') ? this.get('inTimingFunc') : this.get('outTimingFunc');
165 | }),
166 |
167 | /* -------------------- LIFECYCLE HOOKS ---------------------- */
168 | init () {
169 | this._super(...arguments);
170 |
171 | this._resolveInitialTimingAttrs();
172 | this._resolveInitialEffectAttrs();
173 | },
174 |
175 |
176 | didInsertElement () {
177 | this._super(...arguments);
178 |
179 | this._initAnimationCallbacks();
180 |
181 | if (this.element.children && this.element.children.length) {
182 | this._syncWithDOM();
183 |
184 | } else {
185 | this._initChildInsertionCallback();
186 | this._initChildInsertionListener();
187 | }
188 | },
189 |
190 |
191 | /**
192 | * Trigger the staggering animation when something on the outside updates `showItems`
193 | */
194 | didUpdateAttrs (attrData) {
195 | this._super(...arguments);
196 |
197 | const oldShowItems = (
198 | typeof attrData.oldAttrs.showItems.value !== 'undefined' &&
199 | attrData.oldAttrs.showItems.value
200 | );
201 |
202 | const newShowItems = (
203 | typeof attrData.newAttrs.showItems.value !== 'undefined' &&
204 | attrData.newAttrs.showItems.value
205 | );
206 |
207 | if (oldShowItems !== newShowItems && this.get('isReadyToAnimate')) {
208 | run.scheduleOnce('afterRender', this, '_triggerAnimation');
209 | }
210 | },
211 |
212 |
213 | willDestroyElement () {
214 | this._super(...arguments);
215 |
216 | const animationPrefix = this.get('animationPrefix');
217 | const startEvent = animationPrefix ? `${animationPrefix}AnimationStart` : 'animationstart';
218 | const endEvent = animationPrefix ? `${animationPrefix}AnimationEnd` : 'animationend';
219 |
220 | this.element.removeEventListener(`${startEvent}`, this._onStaggerStart, false);
221 | this.element.removeEventListener(`${endEvent}`, this._onStaggerComplete, false);
222 |
223 |
224 | this.set('_onStaggerStart', null);
225 | this.set('_onStaggerComplete', null);
226 | this.set('_listItemElems', null);
227 |
228 | if (this._childInsertionListener) {
229 | this._childInsertionListener.disconnect();
230 | this.set('_childInsertionListener', null);
231 | this.set('_onChildElementsInserted', null);
232 | }
233 | },
234 |
235 | /* -------------------- ACTIONS ---------------------- */
236 | actions: {
237 |
238 | broadcastAnimationStart(animationEvent) {
239 | if (typeof this.onAnimationStart === 'function') {
240 | this.onAnimationStart(animationEvent);
241 | }
242 | },
243 |
244 | broadcastAnimationComplete(animationEvent) {
245 | if (typeof this.onAnimationComplete === 'function') {
246 | this.onAnimationComplete(animationEvent);
247 | }
248 | },
249 | },
250 |
251 | /* -------------------- HELPERS ---------------------- */
252 | _cacheListItems () {
253 | this.set('_listItemElems', Array.from(this.element.children));
254 | },
255 |
256 | _cacheAnimationPrefix () {
257 | this.set('_animationPrefix', getAnimationPrefix(this.element));
258 | },
259 |
260 | _initAnimationCallbacks () {
261 |
262 | this._onStaggerStart = function onStaggerStart (event) {
263 | this.set('isAnimating', true);
264 | this.send('broadcastAnimationStart', event);
265 |
266 | }.bind(this);
267 |
268 | /**
269 | * AnimationEvent listener for the `animationend` event fired by each
270 | * child item.
271 | *
272 | * If the event corresponds to the last item, we'll trigger a run loop call
273 | * that will fire immediately after the animation and
274 | * update `hasListToggled` (if necessary), `isAnimating`, and
275 | * tell our `broadcastAnimationComplete` action to fire
276 | */
277 | this._onStaggerComplete = function onStaggerComplete (event) {
278 | // only update the DOM after we've finished animating all items
279 | const hasListToggled = this.get('hasListToggled');
280 | const lastListItemElem = this._listItemElems[this._listItemElems.length - 1];
281 | const targetElem = event.target;
282 |
283 | if (!hasListToggled) {
284 | targetElem.classList.add(CLASS_NAMES.listItemCompletedInitialToggle);
285 | }
286 |
287 | if (Object.is(targetElem, lastListItemElem)) {
288 | run.once(() => {
289 | if (!hasListToggled) {
290 | this.set('hasListToggled', true);
291 | }
292 | this.set('isAnimating', false);
293 | this.send('broadcastAnimationComplete', event);
294 | });
295 | }
296 |
297 | }.bind(this);
298 | },
299 |
300 | _syncWithDOM () {
301 | this._cacheListItems();
302 | this._cacheAnimationPrefix();
303 | this._prepareItemsInDOM();
304 | if (this.get('needsToEnterAfterInit')) {
305 | run.scheduleOnce('afterRender', this, '_triggerAnimation');
306 | }
307 | },
308 |
309 | _initChildInsertionCallback () {
310 | this._onChildElementsInserted = function onChildElementsInserted () {
311 | run.scheduleOnce('afterRender', this, () => {
312 | this._syncWithDOM();
313 | });
314 | }
315 | },
316 |
317 | /**
318 | * For components whose child elements are asynchronously rendered,
319 | * we can listen for the completion of such rendering and cache our
320 | * list items then.
321 | */
322 | _initChildInsertionListener () {
323 | this._childInsertionListener = new MutationObserver(function childInsertionListener (mutations) {
324 | this._onChildElementsInserted();
325 | }.bind(this));
326 |
327 | this._childInsertionListener.observe(this.element, { childList: true });
328 | },
329 |
330 | _prepareItemsInDOM () {
331 | this._setAnimationValuesForItems(false);
332 |
333 | const animationPrefix = this.get('_animationPrefix');
334 | const startEvent = animationPrefix ? `${animationPrefix}AnimationStart` : 'animationstart';
335 | const endEvent = animationPrefix ? `${animationPrefix}AnimationEnd` : 'animationend';
336 |
337 | this.element.addEventListener(`${startEvent}`, this._onStaggerStart, false);
338 | this.element.addEventListener(`${endEvent}`, this._onStaggerComplete, false);
339 | },
340 |
341 |
342 | /**
343 | * helper function called on init that ensures we're wired up
344 | * with acceptable timing properties, PROVIDING WARNINGS when
345 | * the user specified something invalid
346 | */
347 | _resolveInitialTimingAttrs () {
348 |
349 | this.staggerInterval = this.staggerInterval || DEFAULTS.STAGGER_INTERVAL_MS;
350 | this.inDelay = this.inDelay || DEFAULTS.ANIMATION_DELAY_IN;
351 | this.outDelay = this.outDelay || DEFAULTS.ANIMATION_DELAY_OUT;
352 | this.inDuration = this.inDuration || DEFAULTS.ANIMATION_DURATION_IN;
353 | this.outDuration = this.outDuration || DEFAULTS.ANIMATION_DURATION_OUT;
354 |
355 | /* eslint-disable max-len */
356 | if (isNone(this.staggerInterval) || Number.isNaN(Number(this.staggerInterval)) || this.staggerInterval < 32) {
357 | warn(warningPreface, `Invalid \`staggerInterval\`: ${this.staggerInterval}. Please use a numeric value greater than or equal to 32 (milliseconds). Defaulting to ${DEFAULTS.STAGGER_INTERVAL_MS} milliseconds`);
358 | this.staggerInterval = DEFAULTS.STAGGER_INTERVAL_MS;
359 | }
360 |
361 | if (isNone(this.inDelay) || Number.isNaN(Number(this.inDelay)) || this.inDelay < 0) {
362 | warn(warningPreface, `Invalid \`inDelay\`: ${this.inDelay}. Please use a numeric value greater than or equal to zero. Defaulting to ${DEFAULTS.ANIMATION_DELAY_IN}`);
363 | this.inDelay = DEFAULTS.ANIMATION_DELAY_IN;
364 | }
365 |
366 | if (isNone(this.outDelay) || Number.isNaN(Number(this.outDelay)) || this.outDelay < 0) {
367 | warn(warningPreface, `Invalid \`outDelay\`: ${this.outDelay}. Please use a numeric value greater than or equal to zero. Defaulting to ${DEFAULTS.ANIMATION_DELAY_OUT}`);
368 | this.outDelay = DEFAULTS.ANIMATION_DELAY_IN;
369 | }
370 |
371 | if (isNone(this.inDuration) || Number.isNaN(Number(this.inDuration)) || this.inDuration <= 0) {
372 | warn(warningPreface, `Invalid \`inDuration\`: ${this.inDuration}. Please use a numeric value greater than zero. Defaulting to ${DEFAULTS.ANIMATION_DURATION_IN}`);
373 | this.inDuration = DEFAULTS.ANIMATION_DURATION_IN;
374 | }
375 |
376 | if (isNone(this.outDuration) || Number.isNaN(Number(this.outDuration)) || this.outDuration <= 0) {
377 | warn(warningPreface, `Invalid \`outDuration\`: ${this.outDuration}. Please use a numeric value greater than zero. Defaulting to ${DEFAULTS.ANIMATION_DURATION_OUT}`);
378 | this.outDuration = DEFAULTS.ANIMATION_DURATION_OUT;
379 | }
380 | /* eslint-enable max-len */
381 |
382 | // Set a default timing functoin if none is provided, but don't warn
383 | if (isNone(this.inTimingFunc)) {
384 | this.inTimingFunc = DEFAULTS.TIMING_FUNCTION_IN;
385 | }
386 | if (isNone(this.outTimingFunc)) {
387 | this.outTimingFunc = DEFAULTS.TIMING_FUNCTION_OUT;
388 | }
389 | },
390 |
391 | /**
392 | * helper function called on init that ensures we're wired up
393 | * with acceptable aniamation effect properties, THROWING ERRORS when
394 | * required attributes are invalid and warning when optional attributes
395 | * are set but invalid.
396 | */
397 | _resolveInitialEffectAttrs () {
398 | assert(
399 | `stagger-set must have a valid \`inDirection\`. Received \`${this.inDirection}\``, // TODO: Link to docs
400 | !isNone(this.inDirection) && !!KEYFRAMES_MAP[this.inDirection]
401 | );
402 | assert(
403 | `stagger-set must have a valid \`inEffect\`. Received \`${this.inEffect}\``, // TODO: Link to docs
404 | !isNone(this.inEffect) && !!KEYFRAMES_MAP[this.inDirection].in[this.inEffect]
405 | );
406 |
407 | /* eslint-disable max-len */
408 | if (isNone(this.outDirection)) {
409 | this.outDirection = this.inDirection;
410 |
411 | } else if (!KEYFRAMES_MAP[this.outDirection]) {
412 | warn(warningPreface, `Invalid \`outDirection\`: ${this.outDirection}. Defaulting to ${this.inDirection}`);
413 | this.outDirection = this.inDirection;
414 | }
415 |
416 | if (isNone(this.outEffect)) {
417 | this.outEffect = this.inEffect;
418 |
419 | } else if (!KEYFRAMES_MAP[this.outDirection].out[this.outEffect]) {
420 | warn(warningPreface, `Invalid \`outEffect\`: ${this.outEffect}. Defaulting to ${this.inEffect}`);
421 | this.outEffect = this.inEffect;
422 | }
423 | /* eslint-enable max-len */
424 | },
425 |
426 | _setAnimationValuesForItems (applyName = false) {
427 |
428 | // setting an animation name is what we use to trigger the animation, so only set if asked for
429 | const currentAnimationName = applyName ? this.get('currentAnimationName'): '';
430 | const currentStaggerInterval = this.get('staggerInterval');
431 | const currentAnimationDuration = `${this.get('currentAnimationDuration')}ms`;
432 | const currentAnimationTimingFunction = this.get('currentAnimationTimingFunction');
433 |
434 | // inDelay / outDelay
435 | const currentDelay = this.get('showItems') ? this.get('inDelay') : this.get('outDelay');
436 |
437 | const animationPrefix = this.get('_animationPrefix');
438 | const propertyPrefix = animationPrefix ? `${animationPrefix}A` : 'a';
439 |
440 | this._listItemElems.forEach((listItemElem, idx) => {
441 | listItemElem.style[`${propertyPrefix}nimationDelay`] = `${(currentStaggerInterval * (idx + 1)) + currentDelay}ms`;
442 | listItemElem.style[`${propertyPrefix}nimationTimingFunction`] = currentAnimationTimingFunction;
443 | listItemElem.style[`${propertyPrefix}nimationDuration`] = currentAnimationDuration;
444 | listItemElem.style[`${propertyPrefix}nimationName`] = currentAnimationName;
445 | listItemElem.style[`${propertyPrefix}nimationFillMode`] = 'both';
446 | });
447 | },
448 |
449 | _triggerAnimation() {
450 | this._setAnimationValuesForItems(true);
451 | },
452 |
453 | });
454 |
--------------------------------------------------------------------------------
/tests/dummy/public/savvy.min.css:
--------------------------------------------------------------------------------
1 | html{-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}iframe{border:0}h1,h2,h3,h4,h5,p,blockquote,figure,button{margin:0;padding:0}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}small{font-size:80%}sub,sup{font-size:65%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}svg:not(:root){overflow:hidden}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible;font-size:inherit;background:none;-webkit-font-smoothing:inherit;letter-spacing:inherit;cursor:pointer;color:currentColor}button:focus{outline:0}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button:-moz-focusring,input:-moz-focusring{outline:1px dotted ButtonText}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}img,object,embed,video{max-width:100%;height:auto;border:0}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}html{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Avenir,Geneva,Tahoma,san-serif;color:rgba(0,0,0,.87)}input{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Avenir,Geneva,Tahoma,san-serif;color:rgba(0,0,0,.87)}select{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Avenir,Geneva,Tahoma,san-serif;color:rgba(0,0,0,.87)}textarea{font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Avenir,Geneva,Tahoma,san-serif;color:rgba(0,0,0,.87)}address{font-style:normal}html{font-size:100%;-webkit-font-kerning:normal;font-kerning:normal;-webkit-font-variant-ligatures:common-ligatures,contextual;font-variant-ligatures:common-ligatures,contextual;-ms-font-feature-settings:"kern","liga","clig","alt";-webkit-font-feature-settings:"kern","liga","clig","calt";font-feature-settings:"kern","liga","clig","calt","kern",common-ligatures,contextual}a{text-decoration:none;color:currentColor;background-color:transparent;background-image:-webkit-linear-gradient(transparent 50%,currentColor 50%);background-image:linear-gradient(transparent 50%,currentColor 50%);background-repeat:repeat-x;background-size:2px 2px;background-position:0 90%;-webkit-transition:all .36s;transition:all .36s;-webkit-transition-timing-function:ease;transition-timing-function:ease}a:visited{color:currentColor}a:focus{color:#00a7f5}a:hover{color:#00a7f5}a:focus{color:#00a7f5}a:active{color:#48c1f9}a:active,a:hover,a:focus{outline:0}blockquote{font-size:1.2em;line-height:1.2em}body{font-size:.83333em;line-height:1.44em}h1,h2,h3,h4,h5,h6{text-rendering:optimizeLegibility}h1{font-size:2.0736em;letter-spacing:-.06em}h2{font-size:1.728em;letter-spacing:-.04em}h3{font-size:1.44em;letter-spacing:-.02em}h4{font-size:1.2em}h5{font-size:1em}h6{font-size:.83333em}h1{line-height:1em}h2{line-height:1.2em}h3{line-height:1.2em}h4{line-height:1.2em}h5{line-height:1.2em}h6{line-height:1.2em}dt{font-weight:700}dd{margin-left:1.25em}ul{padding-left:1.25em}ol{padding-left:1.25em}p{margin:0}p+p{margin-top:1.5em}.o-flex-grid{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;list-style:none;margin:0;padding:0}.o-flex-grid .o-grid-cell{-webkit-box-flex:1;-webkit-flex-grow:1;-ms-flex-positive:1;flex-grow:1;-webkit-flex-shrink:1;-ms-flex-negative:1;flex-shrink:1;-webkit-flex-basis:0%;-ms-flex-preferred-size:0;flex-basis:0%;box-sizing:border-box}.o-flex-grid>.o-grid-cell--grow-none{-webkit-box-flex:0;-webkit-flex:0 0 0%;-ms-flex:0 0 0%;flex:0 0 0%}.o-flex-grid>.o-grid-cell--grow-one{-webkit-box-flex:1;-webkit-flex:1 0 0%;-ms-flex:1 0 0%;flex:1 0 0%}.o-flex-grid>.o-grid-cell--auto{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto}.o-flex-grid.o-flex-grid--noWrap{-webkit-flex-wrap:nowrap;-ms-flex-wrap:nowrap;flex-wrap:nowrap}.o-flex-grid.o-flex-grid--flex-cells>.o-grid-cell{display:-webkit-box;display:-webkit-flex;display:-ms-flexbox;display:flex}.o-flex-grid.o-flex-grid--top{-webkit-box-align:start;-webkit-align-items:flex-start;-ms-flex-align:start;align-items:flex-start}.o-flex-grid.o-flex-grid--bottom{-webkit-box-align:end;-webkit-align-items:flex-end;-ms-flex-align:end;align-items:flex-end}.o-flex-grid.o-flex-grid--center{-webkit-box-align:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center}.o-flex-grid.o-flex-grid--justifyStart{-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}.o-flex-grid.o-flex-grid--justifyCenter{-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.o-flex-grid.o-flex-grid--justifyEnd{-webkit-box-pack:end;-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.o-flex-grid.o-flex-grid--spaceAround{-webkit-justify-content:space-around;-ms-flex-pack:distribute;justify-content:space-around}.o-flex-grid.o-flex-grid--spaceBetween{-webkit-box-pack:justify;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between}.o-flex-grid .o-grid-cell--top{-webkit-align-self:flex-start;-ms-flex-item-align:start;align-self:flex-start}.o-flex-grid .o-grid-cell--bottom{-webkit-align-self:flex-end;-ms-flex-item-align:end;align-self:flex-end}.o-flex-grid .o-grid-cell--center{-webkit-align-self:center;-ms-flex-item-align:center;align-self:center}.o-flex-grid .o-grid-cell--autoSize{-webkit-box-flex:0;-webkit-flex:none;-ms-flex:none;flex:none}.o-flex-grid.o-flex-grid--fit>.o-grid-cell{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.o-flex-grid.o-flex-grid--full>.o-grid-cell{-webkit-box-flex:0;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.o-flex-grid.o-flex-grid--auto>.o-grid-cell{-webkit-box-flex:0;-webkit-flex:0 0 auto;-ms-flex:0 0 auto;flex:0 0 auto}.o-flex-grid.o-flex-grid--oneHalf>.o-grid-cell{-webkit-box-flex:0;-webkit-flex:0 0 50%;-ms-flex:0 0 50%;flex:0 0 50%}.o-flex-grid.o-flex-grid--oneThird>.o-grid-cell{-webkit-box-flex:0;-webkit-flex:0 0 33.33333%;-ms-flex:0 0 33.33333%;flex:0 0 33.33333%}.o-flex-grid.o-flex-grid--oneQuarter>.o-grid-cell{-webkit-box-flex:0;-webkit-flex:0 0 25%;-ms-flex:0 0 25%;flex:0 0 25%}.o-flex-grid.o-flex-grid--gutters{margin:-1em 0 0 -1em}.o-flex-grid.o-flex-grid--gutters>.o-grid-cell{padding:1em 0 0 1em}.o-flex-grid.o-flex-grid--guttersLg{margin:-1.5em 0 0 -1.5em}.o-flex-grid.o-flex-grid--guttersLg>.o-grid-cell{padding:1.5em 0 0 1.5em}.o-flex-grid.o-flex-grid--guttersXl{margin:-2em 0 0 -2em}.o-flex-grid.o-flex-grid--guttersXl>.o-grid-cell{padding:2em 0 0 2em}.o-divider-1--horizontal{height:1px}.o-divider-2--horizontal{height:2px}.o-divider-1--vertical{width:1px}.o-divider-2--vertical{width:2px}.o-link--no-under{text-decoration:none;background:none}.o-scroll-container--vertical{overflow-y:scroll}.o-scroll-container--horizontal{overflow-x:scroll}.o-clip-container{overflow:hidden}.o-list--flat,.o-list--reset{list-style-type:none;margin-left:0;padding-left:0}.a-anim-ease-default{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1.000);animation-timing-function:cubic-bezier(.215,.61,.355,1.000)}.a-trans-ease-default{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1.000);transition-timing-function:cubic-bezier(.215,.61,.355,1.000)}.a-anim-ease-scaleUp{-webkit-animation-timing-function:cubic-bezier(.23,1.000,.32,1.000);animation-timing-function:cubic-bezier(.23,1.000,.32,1.000)}.a-trans-ease-scaleUp{-webkit-transition-timing-function:cubic-bezier(.23,1.000,.32,1.000);transition-timing-function:cubic-bezier(.23,1.000,.32,1.000)}.a-anim-ease-scaleUp--bounce{-webkit-animation-timing-function:cubic-bezier(0.00,.3,0.00,1.48);animation-timing-function:cubic-bezier(0.00,.3,0.00,1.48)}.a-trans-ease-scaleUp--bounce{-webkit-transition-timing-function:cubic-bezier(0.00,.3,0.00,1.48);transition-timing-function:cubic-bezier(0.00,.3,0.00,1.48)}.a-anim-ease-scaleDown{-webkit-animation-timing-function:cubic-bezier(.47,0.000,.745,.715);animation-timing-function:cubic-bezier(.47,0.000,.745,.715)}.a-trans-ease-scaleDown{-webkit-transition-timing-function:cubic-bezier(.47,0.000,.745,.715);transition-timing-function:cubic-bezier(.47,0.000,.745,.715)}.a-anim-ease-fadeIn{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1.000);animation-timing-function:cubic-bezier(.215,.61,.355,1.000)}.a-trans-ease-fadeIn{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1.000);transition-timing-function:cubic-bezier(.215,.61,.355,1.000)}.a-anim-ease-fadeIn--bounce{-webkit-animation-timing-function:cubic-bezier(0.00,.3,0.00,1.48);animation-timing-function:cubic-bezier(0.00,.3,0.00,1.48)}.a-trans-ease-fadeIn--bounce{-webkit-transition-timing-function:cubic-bezier(0.00,.3,0.00,1.48);transition-timing-function:cubic-bezier(0.00,.3,0.00,1.48)}.a-anim-ease-fadeOut{-webkit-animation-timing-function:cubic-bezier(.47,0.000,.745,.715);animation-timing-function:cubic-bezier(.47,0.000,.745,.715)}.a-trans-ease-fadeOut{-webkit-transition-timing-function:cubic-bezier(.47,0.000,.745,.715);transition-timing-function:cubic-bezier(.47,0.000,.745,.715)}.a-anim-ease-slideIn--light{-webkit-animation-timing-function:cubic-bezier(.165,.84,.44,1.000);animation-timing-function:cubic-bezier(.165,.84,.44,1.000)}.a-trans-ease-slideIn--light{-webkit-transition-timing-function:cubic-bezier(.165,.84,.44,1.000);transition-timing-function:cubic-bezier(.165,.84,.44,1.000)}.a-anim-ease-slideIn--heavy{-webkit-animation-timing-function:cubic-bezier(.135,.535,.45,.94);animation-timing-function:cubic-bezier(.135,.535,.45,.94)}.a-trans-ease-slideIn--heavy{-webkit-transition-timing-function:cubic-bezier(.135,.535,.45,.94);transition-timing-function:cubic-bezier(.135,.535,.45,.94)}.a-anim-ease-slideOut--light{-webkit-animation-timing-function:cubic-bezier(.55,.055,.675,.19);animation-timing-function:cubic-bezier(.55,.055,.675,.19)}.a-trans-ease-slideOut--light{-webkit-transition-timing-function:cubic-bezier(.55,.055,.675,.19);transition-timing-function:cubic-bezier(.55,.055,.675,.19)}.a-anim-ease-slideOut--heavy{-webkit-animation-timing-function:cubic-bezier(.47,.02,1.000,.57);animation-timing-function:cubic-bezier(.47,.02,1.000,.57)}.a-trans-ease-slideOut--heavy{-webkit-transition-timing-function:cubic-bezier(.47,.02,1.000,.57);transition-timing-function:cubic-bezier(.47,.02,1.000,.57)}.a-anim-ease-reposition--light{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1.000);animation-timing-function:cubic-bezier(.215,.61,.355,1.000)}.a-trans-ease-reposition--light{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1.000);transition-timing-function:cubic-bezier(.215,.61,.355,1.000)}.a-anim-ease-reposition--heavy{-webkit-animation-timing-function:cubic-bezier(.25,.46,.45,.94);animation-timing-function:cubic-bezier(.25,.46,.45,.94)}.a-trans-ease-reposition--heavy{-webkit-transition-timing-function:cubic-bezier(.25,.46,.45,.94);transition-timing-function:cubic-bezier(.25,.46,.45,.94)}.a-anim-ease-zoom{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1.000);animation-timing-function:cubic-bezier(.215,.61,.355,1.000)}.a-trans-ease-zoom{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1.000);transition-timing-function:cubic-bezier(.215,.61,.355,1.000)}.a-anim-ease-zoom--bounce{-webkit-animation-timing-function:cubic-bezier(.27,.78,.75,2.15);animation-timing-function:cubic-bezier(.27,.78,.75,2.15)}.a-trans-ease-zoom--bounce{-webkit-transition-timing-function:cubic-bezier(.27,.78,.75,2.15);transition-timing-function:cubic-bezier(.27,.78,.75,2.15)}.a-anim-ease-colorShift{-webkit-animation-timing-function:cubic-bezier(.215,.61,.355,1.000);animation-timing-function:cubic-bezier(.215,.61,.355,1.000)}.a-trans-ease-colorShift{-webkit-transition-timing-function:cubic-bezier(.215,.61,.355,1.000);transition-timing-function:cubic-bezier(.215,.61,.355,1.000)}.a-anim-ease-gtfo{-webkit-animation-timing-function:cubic-bezier(.68,-.63,0.00,.67);animation-timing-function:cubic-bezier(.68,-.63,0.00,.67)}.a-trans-ease-gtfo{-webkit-transition-timing-function:cubic-bezier(.68,-.63,0.00,.67);transition-timing-function:cubic-bezier(.68,-.63,0.00,.67)}.g-red--100{color:#ffccd1!important}.g-red--300{color:#e7716f!important}.g-red--500{color:#f7402b!important}.g-red--700{color:#d62c29!important}.g-red--900{color:#ba1b12!important}.g-bg-red--100{background-color:#ffccd1!important}.g-bg-red--300{background-color:#e7716f!important}.g-bg-red--500{background-color:#f7402b!important}.g-bg-red--700{background-color:#d62c29!important}.g-bg-red--900{background-color:#ba1b12!important}.g-yellow--100{color:#fffac2!important}.g-yellow--300{color:#fff36b!important}.g-yellow--500{color:#ffec1a!important}.g-yellow--700{color:#fcc203!important}.g-yellow--900{color:#f57e00!important}.g-bg-yellow--100{background-color:#fffac2!important}.g-bg-yellow--300{background-color:#fff36b!important}.g-bg-yellow--500{background-color:#ffec1a!important}.g-bg-yellow--700{background-color:#fcc203!important}.g-bg-yellow--900{background-color:#f57e00!important}.g-green--100{color:#b6f7c8!important}.g-green--300{color:#51f66c!important}.g-green--500{color:#00fa00!important}.g-green--700{color:#5abf0d!important}.g-green--900{color:#175e1c!important}.g-bg-green--100{background-color:#b6f7c8!important}.g-bg-green--300{background-color:#51f66c!important}.g-bg-green--500{background-color:#00fa00!important}.g-bg-green--700{background-color:#5abf0d!important}.g-bg-green--900{background-color:#175e1c!important}.g-aqua--100{color:#a3ffeb!important}.g-aqua--300{color:#5cffd9!important}.g-aqua--500{color:#00ebb4!important}.g-aqua--700{color:#00c2a8!important}.g-aqua--900{color:#008a7c!important}.g-bg-aqua--100{background-color:#a3ffeb!important}.g-bg-aqua--300{background-color:#5cffd9!important}.g-bg-aqua--500{background-color:#00ebb4!important}.g-bg-aqua--700{background-color:#00c2a8!important}.g-bg-aqua--900{background-color:#008a7c!important}.g-cyan--100{color:#afebf3!important}.g-cyan--300{color:#32fbf7!important}.g-cyan--500{color:#00bdd6!important}.g-cyan--700{color:#0097a8!important}.g-cyan--900{color:#006166!important}.g-bg-cyan--100{background-color:#afebf3!important}.g-bg-cyan--300{background-color:#32fbf7!important}.g-bg-cyan--500{background-color:#00bdd6!important}.g-bg-cyan--700{background-color:#0097a8!important}.g-bg-cyan--900{background-color:#006166!important}.g-blue--100{color:#afe4fd!important}.g-blue--300{color:#48c1f9!important}.g-blue--500{color:#00a7f5!important}.g-blue--700{color:#0088d6!important}.g-blue--900{color:#00549e!important}.g-bg-blue--100{background-color:#afe4fd!important}.g-bg-blue--300{background-color:#48c1f9!important}.g-bg-blue--500{background-color:#00a7f5!important}.g-bg-blue--700{background-color:#0088d6!important}.g-bg-blue--900{background-color:#00549e!important}.g-purple--100{color:#cac0f7!important}.g-purple--300{color:#8470db!important}.g-purple--500{color:#6835bb!important}.g-purple--700{color:#5026ab!important}.g-purple--900{color:#321197!important}.g-bg-purple--100{background-color:#cac0f7!important}.g-bg-purple--300{background-color:#8470db!important}.g-bg-purple--500{background-color:#6835bb!important}.g-bg-purple--700{background-color:#5026ab!important}.g-bg-purple--900{background-color:#321197!important}.g-violet--100{color:#f0d7f4!important}.g-violet--300{color:#f3c8f3!important}.g-violet--500{color:#f07ff0!important}.g-violet--700{color:#9d1bb1!important}.g-violet--900{color:#7b13a4!important}.g-bg-violet--100{background-color:#f0d7f4!important}.g-bg-violet--300{background-color:#f3c8f3!important}.g-bg-violet--500{background-color:#f07ff0!important}.g-bg-violet--700{background-color:#9d1bb1!important}.g-bg-violet--900{background-color:#7b13a4!important}.g-magenta--100{color:#ff42ff!important}.g-magenta--300{color:#f0f!important}.g-magenta--500{color:#d100d1!important}.g-magenta--700{color:purple!important}.g-magenta--900{color:#802182!important}.g-bg-magenta--100{background-color:#ff42ff!important}.g-bg-magenta--300{background-color:#f0f!important}.g-bg-magenta--500{background-color:#d100d1!important}.g-bg-magenta--700{background-color:purple!important}.g-bg-magenta--900{background-color:#802182!important}.g-gray--100{color:#f5f5f5!important}.g-gray--300{color:#e0e0e0!important}.g-gray--500{color:#9e9e9e!important}.g-gray--700{color:#616161!important}.g-gray--900{color:#212121!important}.g-bg-gray--100{background-color:#f5f5f5!important}.g-bg-gray--300{background-color:#e0e0e0!important}.g-bg-gray--500{background-color:#9e9e9e!important}.g-bg-gray--700{background-color:#616161!important}.g-bg-gray--900{background-color:#212121!important}.g-warm-gray--100{color:#ece9e4!important}.g-warm-gray--300{color:#cec4ba!important}.g-warm-gray--500{color:#c0b4a5!important}.g-warm-gray--700{color:#7e7467!important}.g-warm-gray--900{color:#36312b!important}.g-bg-warm-gray--100{background-color:#ece9e4!important}.g-bg-warm-gray--300{background-color:#cec4ba!important}.g-bg-warm-gray--500{background-color:#c0b4a5!important}.g-bg-warm-gray--700{background-color:#7e7467!important}.g-bg-warm-gray--900{background-color:#36312b!important}.g-cool-gray--100{color:#d0d9dd!important}.g-cool-gray--300{color:#8ea3af!important}.g-cool-gray--500{color:#5f7d8c!important}.g-cool-gray--700{color:#445a64!important}.g-cool-gray--900{color:#253137!important}.g-bg-cool-gray--100{background-color:#d0d9dd!important}.g-bg-cool-gray--300{background-color:#8ea3af!important}.g-bg-cool-gray--500{background-color:#5f7d8c!important}.g-bg-cool-gray--700{background-color:#445a64!important}.g-bg-cool-gray--900{background-color:#253137!important}.g-white{color:#fff!important}.g-black{color:#000!important}.g-nearWhite{color:#fafafa!important}.g-nearBlack{color:rgba(0,0,0,.87)!important}.g-bg-white{background-color:#fff!important}.g-bg-black{background-color:#000!important}.g-bg-nearWhite{background-color:#fafafa!important}.g-bg-nearBlack{background-color:rgba(0,0,0,.87)!important}.g-bg-transparent{background-color:transparent!important}.g-currentColor{color:currentColor!important}.g-bg-currentColor{background-color:currentColor!important}.g-border-none{border:0}.g-border-noRounding{border-radius:0}.g-border-circle{border-radius:50%}.g-border-s1{border-style:solid;border-width:1px;border-width:.1rem}.g-border-top-s1{border-top-style:solid;border-top-width:1px;border-top-width:.1rem}.g-border-right-s1{border-right-style:solid;border-right-width:1px;border-right-width:.1rem}.g-border-bottom-s1{border-bottom-style:solid;border-bottom-width:1px;border-bottom-width:.1rem}.g-border-left-s1{border-left-style:solid;border-left-width:1px;border-left-width:.1rem}.g-border-dash1{border-style:dashed;border-width:1px;border-width:.1rem}.g-border-top-dash1{border-top-style:dashed;border-top-width:1px;border-top-width:.1rem}.g-border-right-dash1{border-right-style:dashed;border-right-width:1px;border-right-width:.1rem}.g-border-bottom-dash1{border-bottom-style:dashed;border-bottom-width:1px;border-bottom-width:.1rem}.g-border-left-dash1{border-left-style:dashed;border-left-width:1px;border-left-width:.1rem}.g-border-s2{border-style:solid;border-width:3px;border-width:.2rem}.g-border-top-s2{border-top-style:solid;border-top-width:3px;border-top-width:.2rem}.g-border-right-s2{border-right-style:solid;border-right-width:3px;border-right-width:.2rem}.g-border-bottom-s2{border-bottom-style:solid;border-bottom-width:3px;border-bottom-width:.2rem}.g-border-left-s2{border-left-style:solid;border-left-width:3px;border-left-width:.2rem}.g-border-dash2{border-style:dashed;border-width:3px;border-width:.2rem}.g-border-top-dash2{border-top-style:dashed;border-top-width:3px;border-top-width:.2rem}.g-border-right-dash2{border-right-style:dashed;border-right-width:3px;border-right-width:.2rem}.g-border-bottom-dash2{border-bottom-style:dashed;border-bottom-width:3px;border-bottom-width:.2rem}.g-border-left-dash2{border-left-style:dashed;border-left-width:3px;border-left-width:.2rem}.g-border-s3{border-style:solid;border-width:4px;border-width:.3rem}.g-border-top-s3{border-top-style:solid;border-top-width:4px;border-top-width:.3rem}.g-border-right-s3{border-right-style:solid;border-right-width:4px;border-right-width:.3rem}.g-border-bottom-s3{border-bottom-style:solid;border-bottom-width:4px;border-bottom-width:.3rem}.g-border-left-s3{border-left-style:solid;border-left-width:4px;border-left-width:.3rem}.g-border-dash3{border-style:dashed;border-width:4px;border-width:.3rem}.g-border-top-dash3{border-top-style:dashed;border-top-width:4px;border-top-width:.3rem}.g-border-right-dash3{border-right-style:dashed;border-right-width:4px;border-right-width:.3rem}.g-border-bottom-dash3{border-bottom-style:dashed;border-bottom-width:4px;border-bottom-width:.3rem}.g-border-left-dash3{border-left-style:dashed;border-left-width:4px;border-left-width:.3rem}.g-border-s4{border-style:solid;border-width:6px;border-width:.4rem}.g-border-top-s4{border-top-style:solid;border-top-width:6px;border-top-width:.4rem}.g-border-right-s4{border-right-style:solid;border-right-width:6px;border-right-width:.4rem}.g-border-bottom-s4{border-bottom-style:solid;border-bottom-width:6px;border-bottom-width:.4rem}.g-border-left-s4{border-left-style:solid;border-left-width:6px;border-left-width:.4rem}.g-border-dash4{border-style:dashed;border-width:6px;border-width:.4rem}.g-border-top-dash4{border-top-style:dashed;border-top-width:6px;border-top-width:.4rem}.g-border-right-dash4{border-right-style:dashed;border-right-width:6px;border-right-width:.4rem}.g-border-bottom-dash4{border-bottom-style:dashed;border-bottom-width:6px;border-bottom-width:.4rem}.g-border-left-dash4{border-left-style:dashed;border-left-width:6px;border-left-width:.4rem}.g-border-s5{border-style:solid;border-width:8px;border-width:.5rem}.g-border-top-s5{border-top-style:solid;border-top-width:8px;border-top-width:.5rem}.g-border-right-s5{border-right-style:solid;border-right-width:8px;border-right-width:.5rem}.g-border-bottom-s5{border-bottom-style:solid;border-bottom-width:8px;border-bottom-width:.5rem}.g-border-left-s5{border-left-style:solid;border-left-width:8px;border-left-width:.5rem}.g-border-dash5{border-style:dashed;border-width:8px;border-width:.5rem}.g-border-top-dash5{border-top-style:dashed;border-top-width:8px;border-top-width:.5rem}.g-border-right-dash5{border-right-style:dashed;border-right-width:8px;border-right-width:.5rem}.g-border-bottom-dash5{border-bottom-style:dashed;border-bottom-width:8px;border-bottom-width:.5rem}.g-border-left-dash5{border-left-style:dashed;border-left-width:8px;border-left-width:.5rem}.g-box-shadow-1{box-shadow:0 1.5px 4px rgba(0,0,0,.24),0 1.5px 6px rgba(0,0,0,.12)}.g-box-shadow-2{box-shadow:0 3px 12px rgba(0,0,0,.23),0 3px 12px rgba(0,0,0,.16)}.g-box-shadow-3{box-shadow:0 10px 12px rgba(0,0,0,.23),0 6px 40px rgba(0,0,0,.19)}.g-box-shadow-4{box-shadow:0 14px 20px rgba(0,0,0,.22),0 10px 56px rgba(0,0,0,.25)}.g-box-shadow-5{box-shadow:0 19px 24px rgba(0,0,0,.22),0 15px 76px rgba(0,0,0,.3)}.g-font-100{font-weight:100}.g-font-200{font-weight:200}.g-font-300{font-weight:300}.g-font-400{font-weight:400}.g-font-500{font-weight:500}.g-font-600{font-weight:600}.g-font-700{font-weight:700}.g-font-800{font-weight:800}.g-font-900{font-weight:900}.g-font-smaller{font-size:.83333em}.g-font-larger{font-size:1.2em}.g-small-caps{font-size:.666em;text-transform:uppercase;letter-spacing:.125em}.g-font-size--halfRoot{font-size:8px;font-size:.5rem}.g-font-size--threeQuartersRoot{font-size:12px;font-size:.75rem}.g-font-size--root{font-size:16px;font-size:1rem}.g-font-size--rootPlusQuarter{font-size:20px;font-size:1.25rem}.g-font-size--rootPlusThird{font-size:21px;font-size:1.333333rem}.g-font-size--rootPlusHalf{font-size:24px;font-size:1.5rem}.g-font-size--twoRoot{font-size:32px;font-size:2rem}.g-truncate{max-width:100%;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.g-clip{max-width:100%;text-overflow:clip;white-space:nowrap;overflow:hidden}.g-link-reset{background-image:none;text-decoration:none}.g-link-reset:hover,.g-link-reset:focus{background-image:none;text-decoration:none;color:currentColor}.g-list-reset{padding-left:0;list-style:none}.g-list-style-none{list-style:none}.g-underline{text-decoration:underline}.g-strikethrough{text-decoration:line-through}.u-align-left{text-align:left!important}.u-align-right{text-align:right!important}.u-align-center{text-align:center!important}.u-b-box{box-sizing:border-box}.u-c-box{box-sizing:content-box}.u-pad-box{box-sizing:padding-box}.u-pointer{cursor:pointer!important}.u-block{display:block}.u-inline-block{display:inline-block}.u-inline{display:inline}.u-justify-start{-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}.u-justify-end{-webkit-box-pack:end;-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}.u-justify-center{-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}.u-overflow-auto{overflow:auto}.u-overflow-auto-x{overflow-x:auto}.u-overflow-auto-y{overflow-y:auto}.u-overflow-hide{overflow:hidden}.u-overflow-hide-x{overflow-x:hidden}.u-overflow-hide-y{overflow-y:hidden}.u-overflow-show{overflow:visible}.u-overflow-show-y{overflow-y:visible}.u-overflow-show-x{overflow-y:visible}.u-overflow-scroll-y{overflow-y:scroll}.u-overflow-scroll-x{overflow-x:scroll}.u-static{position:static}.u-relative{position:relative}.u-absolute{position:absolute}.u-transform-center{top:50%;left:50%;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}.u-offset-left{top:0;left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.u-fixed{position:fixed}.u-flow-middle{margin-left:auto;margin-right:auto}.u-sticky{position:-webkit-sticky;position:sticky}.u-edge-top{top:0}.u-edge-bottom{bottom:0}.u-edge-left{left:0}.u-edge-right{right:0}.u-full-bleed{height:100vh;margin:0}.u-full-stretch{width:100vw;margin:0}.u-fill-height{height:100%}.u-inherit-height{height:inherit}.u-fit-width{max-width:100%}.u-fill-width{width:100%}.u-inherit-width{width:inherit}.u-width-95{width:95%}.u-width-90{width:90%}.u-width-85{width:85%}.u-width-80{width:80%}.u-width-75,.u-width-threeQuarters{width:75%}.u-width-70{width:70%}.u-width-twoThirds{width:66.6667%}.u-width-60{width:60%}.u-width-55{width:55%}.u-width-50,.u-width-oneHalf{width:50%}.u-width-45{width:45%}.u-width-40{width:40%}.u-width-35{width:35%}.u-width-oneThird{width:33.3333%}.u-width-30{width:30%}.u-width-25,.u-width-oneQuarter{width:25%}.u-width-20{width:20%}.u-width-15{width:15%}.u-width-10{width:10%}.u-height-90{height:90%}.u-height-80{height:80%}.u-height-70{height:70%}.u-height-60{height:60%}.u-height-50{height:50%}.u-height-40{height:40%}.u-height-30{height:30%}.u-height-20{height:20%}.u-height-10{height:10%}.u-width-spacing-1{width:8px;width:.5rem}.u-width-spacing-2{width:16px;width:1rem}.u-width-spacing-3{width:32px;width:2rem}.u-width-spacing-4{width:64px;width:4rem}.u-width-spacing-5{width:128px;width:8rem}.u-height-spacing-1{height:8px;height:.5rem}.u-height-spacing-2{height:16px;height:1rem}.u-height-spacing-3{height:32px;height:2rem}.u-height-spacing-4{height:64px;height:4rem}.u-height-spacing-5{height:128px;height:8rem}.u-min-width-breakpoint--xs{min-width:20em}.u-min-width-breakpoint--sm{min-width:32em}.u-min-width-breakpoint--md{min-width:48em}.u-min-width-breakpoint--lg{min-width:64em}.u-min-width-breakpoint--xl{min-width:80em}.u-min-width-breakpoint--xxl{min-width:96em}.u-p0{padding:0}.u-p1{padding:8px;padding:.5rem}.u-p2{padding:16px;padding:1rem}.u-p3{padding:32px;padding:2rem}.u-p4{padding:64px;padding:4rem}.u-p5{padding:128px;padding:8rem}.u-pt0{padding-top:0}.u-pt1{padding-top:8px;padding-top:.5rem}.u-pt2{padding-top:16px;padding-top:1rem}.u-pt3{padding-top:32px;padding-top:2rem}.u-pt4{padding-top:64px;padding-top:4rem}.u-pt5{padding-top:128px;padding-top:8rem}.u-pr0{padding-right:0}.u-pr1{padding-right:8px;padding-right:.5rem}.u-pr2{padding-right:16px;padding-right:1rem}.u-pr3{padding-right:32px;padding-right:2rem}.u-pr4{padding-right:64px;padding-right:4rem}.u-pr5{padding-right:128px;padding-right:8rem}.u-pb0{padding-bottom:0}.u-pb1{padding-bottom:8px;padding-bottom:.5rem}.u-pb2{padding-bottom:16px;padding-bottom:1rem}.u-pb3{padding-bottom:32px;padding-bottom:2rem}.u-pb4{padding-bottom:64px;padding-bottom:4rem}.u-pb5{padding-bottom:128px;padding-bottom:8rem}.u-pl0{padding-left:0}.u-pl1{padding-left:8px;padding-left:.5rem}.u-pl2{padding-left:16px;padding-left:1rem}.u-pl3{padding-left:32px;padding-left:2rem}.u-pl4{padding-left:64px;padding-left:4rem}.u-pl5{padding-left:128px;padding-left:8rem}.u-m-auto{margin:auto}.u-m0{margin:0}.u-m1{margin:8px;margin:.5rem}.u-m2{margin:16px;margin:1rem}.u-m3{margin:32px;margin:2rem}.u-m4{margin:64px;margin:4rem}.u-m5{margin:128px;margin:8rem}.u-mt-auto{margin-top:auto}.u-mt0{margin-top:0}.u-mt1{margin-top:8px;margin-top:.5rem}.u-mt2{margin-top:16px;margin-top:1rem}.u-mt3{margin-top:32px;margin-top:2rem}.u-mt4{margin-top:64px;margin-top:4rem}.u-mt5{margin-top:128px;margin-top:8rem}.u-mr-auto{margin-right:auto}.u-mr0{margin-right:0}.u-mr1{margin-right:8px;margin-right:.5rem}.u-mr2{margin-right:16px;margin-right:1rem}.u-mr3{margin-right:32px;margin-right:2rem}.u-mr4{margin-right:64px;margin-right:4rem}.u-mr5{margin-right:128px;margin-right:8rem}.u-mb-auto{margin-bottom:auto}.u-mb0{margin-bottom:0}.u-mb1{margin-bottom:8px;margin-bottom:.5rem}.u-mb2{margin-bottom:16px;margin-bottom:1rem}.u-mb3{margin-bottom:32px;margin-bottom:2rem}.u-mb4{margin-bottom:64px;margin-bottom:4rem}.u-mb5{margin-bottom:128px;margin-bottom:8rem}.u-ml-auto{margin-left:auto}.u-ml0{margin-left:0}.u-ml1{margin-left:8px;margin-left:.5rem}.u-ml2{margin-left:16px;margin-left:1rem}.u-ml3{margin-left:32px;margin-left:2rem}.u-ml4{margin-left:64px;margin-left:4rem}.u-ml5{margin-left:128px;margin-left:8rem}.u-list-reset{padding-left:0;list-style:none}.u-nowrap{white-space:nowrap}.u-break-word{white-space:break-word}.u-zn6{z-index:-6}.u-zn5{z-index:-5}.u-zn4{z-index:-4}.u-zn3{z-index:-3}.u-zn2{z-index:-2}.u-zn1{z-index:-1}.u-z0{z-index:0}.u-z1{z-index:1}.u-z2{z-index:2}.u-z3{z-index:3}.u-z4{z-index:4}.u-z5{z-index:5}.u-z6{z-index:6}@media screen and (min-width:64em){blockquote{font-size:1.44em;line-height:1em}body{font-size:1.44em}h1{font-size:2.98598em}h2{font-size:2.48832em}h3{font-size:2.0736em}h4{font-size:1.728em}.o-flex-grid._lg-o-flex-grid--gutters{margin:-1em 0 0 -1em}.o-flex-grid._lg-o-flex-grid--gutters>.o-grid-cell{padding:1em 0 0 1em}.o-flex-grid._lg-o-flex-grid--guttersLg{margin:-1.5em 0 0 -1.5em}.o-flex-grid._lg-o-flex-grid--guttersLg>.o-grid-cell{padding:1.5em 0 0 1.5em}.o-flex-grid._lg-o-flex-grid--guttersXl{margin:-2em 0 0 -2em}.o-flex-grid._lg-o-flex-grid--guttersXl>.o-grid-cell{padding:2em 0 0 2em}.o-flex-grid._lg-o-flex-grid--fit>.o-grid-cell{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.o-flex-grid._lg-o-flex-grid--full>.o-grid-cell{-webkit-box-flex:0;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.o-flex-grid._lg-o-flex-grid--oneHalf>.o-grid-cell{-webkit-box-flex:0;-webkit-flex:0 0 50%;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.o-flex-grid._lg-o-flex-grid--oneThird>.o-grid-cell{-webkit-box-flex:0;-webkit-flex:0 0 33.33333%;-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.333333%}.o-flex-grid._lg-o-flex-grid--oneQuarter>.o-grid-cell{-webkit-box-flex:0;-webkit-flex:0 0 25%;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}._lg-o-list--unflattening{list-style-type:disc;padding-left:3em}._lg-o-list--unflattening>li{display:list-item;list-style-type:disc}._lg-u-align-left{text-align:left!important}._lg-u-align-right{text-align:right!important}._lg-u-align-center{text-align:center!important}._lg-u-block{display:block}._lg-u-inline-block{display:inline-block}._lg-u-inline{display:inline}._lg-u-justify-start{-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}._lg-u-justify-end{-webkit-box-pack:end;-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}._lg-u-justify-center{-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}._lg-u-static{position:static}._lg-u-relative{position:relative}._lg-u-absolute{position:absolute}._lg-u-fixed{position:fixed}._lg-u-transform-center{top:50%;left:50%;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}._lg-u-offset-left{top:0;left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}._lg-u-sticky{position:-webkit-sticky;position:sticky}._lg-u-flow-middle{margin-left:auto;margin-right:auto}._lg-u-edge-top{top:0}._lg-u-edge-bottom{bottom:0}._lg-u-edge-left{left:0}._lg-u-edge-right{right:0}._lg-u-p0{padding:0}._lg-u-p1{padding:.5rem}._lg-u-p2{padding:1rem}._lg-u-p3{padding:2rem}._lg-u-p4{padding:4rem}._lg-u-p5{padding:8rem}._lg-u-pt0{padding-top:0}._lg-u-pt1{padding-top:.5rem}._lg-u-pt2{padding-top:1rem}._lg-u-pt3{padding-top:2rem}._lg-u-pt4{padding-top:4rem}._lg-u-pt5{padding-top:8rem}._lg-u-pr0{padding-right:0}._lg-u-pr1{padding-right:.5rem}._lg-u-pr2{padding-right:1rem}._lg-u-pr3{padding-right:2rem}._lg-u-pr4{padding-right:4rem}._lg-u-pr5{padding-right:8rem}._lg-u-pb0{padding-bottom:0}._lg-u-pb1{padding-bottom:.5rem}._lg-u-pb2{padding-bottom:1rem}._lg-u-pb3{padding-bottom:2rem}._lg-u-pb4{padding-bottom:4rem}._lg-u-pb5{padding-bottom:8rem}._lg-u-pl0{padding-left:0}._lg-u-pl1{padding-left:.5rem}._lg-u-pl2{padding-left:1rem}._lg-u-pl3{padding-left:2rem}._lg-u-pl4{padding-left:4rem}._lg-u-pl5{padding-left:8rem}._lg-u-m-auto{margin:auto}._lg-u-m0{margin:0}._lg-u-m1{margin:.5rem}._lg-u-m2{margin:1rem}._lg-u-m3{margin:2rem}._lg-u-m4{margin:4rem}._lg-u-m5{margin:8rem}._lg-u-mt-auto{margin-top:auto}._lg-u-mt0{margin-top:0}._lg-u-mt1{margin-top:.5rem}._lg-u-mt2{margin-top:1rem}._lg-u-mt3{margin-top:2rem}._lg-u-mt4{margin-top:4rem}._lg-u-mt5{margin-top:8rem}._lg-u-mr-auto{margin-right:auto}._lg-u-mr0{margin-right:0}._lg-u-mr1{margin-right:.5rem}._lg-u-mr2{margin-right:1rem}._lg-u-mr3{margin-right:2rem}._lg-u-mr4{margin-right:4rem}._lg-u-mr5{margin-right:8rem}._lg-u-mb-auto{margin-bottom:auto}._lg-u-mb0{margin-bottom:0}._lg-u-mb1{margin-bottom:.5rem}._lg-u-mb2{margin-bottom:1rem}._lg-u-mb3{margin-bottom:2rem}._lg-u-mb4{margin-bottom:4rem}._lg-u-mb5{margin-bottom:8rem}._lg-u-ml-auto{margin-left:auto}._lg-u-ml0{margin-left:0}._lg-u-ml1{margin-left:.5rem}._lg-u-ml2{margin-left:1rem}._lg-u-ml3{margin-left:2rem}._lg-u-ml4{margin-left:4rem}._lg-u-ml5{margin-left:8rem}}@media screen and (min-width:20em){body{font-size:1em}}@media screen and (min-width:32em){body{font-size:1.2em}.o-flex-grid._ns-o-flex-grid--gutters{margin:-1em 0 0 -1em}.o-flex-grid._ns-o-flex-grid--gutters>.o-grid-cell{padding:1em 0 0 1em}.o-flex-grid._ns-o-flex-grid--guttersLg{margin:-1.5em 0 0 -1.5em}.o-flex-grid._ns-o-flex-grid--guttersLg>.o-grid-cell{padding:1.5em 0 0 1.5em}.o-flex-grid._ns-o-flex-grid--guttersXl{margin:-2em 0 0 -2em}.o-flex-grid._ns-o-flex-grid--guttersXl>.o-grid-cell{padding:2em 0 0 2em}.o-flex-grid._ns-o-flex-grid--fit>.o-grid-cell{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.o-flex-grid._ns-o-flex-grid--full>.o-grid-cell{-webkit-box-flex:0;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.o-flex-grid._ns-o-flex-grid--oneHalf>.o-grid-cell{-webkit-box-flex:0;-webkit-flex:0 0 50%;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.o-flex-grid._ns-o-flex-grid--oneThird>.o-grid-cell{-webkit-box-flex:0;-webkit-flex:0 0 33.33333%;-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.333333333%}.o-flex-grid._ns-o-flex-grid--oneQuarter>.o-grid-cell{-webkit-box-flex:0;-webkit-flex:0 0 25%;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}._ns-o-list--unflattening{list-style-type:disc;padding-left:1.25em}._ns-o-list--unflattening>li{display:list-item;list-style-type:disc}._ns-u-align-left{text-align:left!important}._ns-u-align-right{text-align:right!important}._ns-u-align-center{text-align:center!important}._ns-u-block{display:block}._ns-u-inline-block{display:inline-block}._ns-u-inline{display:inline}._ns-u-justify-start{-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}._ns-u-justify-end{-webkit-box-pack:end;-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}._ns-u-justify-center{-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}._ns-u-static{position:static}._ns-u-relative{position:relative}._ns-u-absolute{position:absolute}._ns-u-fixed{position:fixed}._ns-u-offset-left{top:0;left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}._ns-u-transform-center{top:50%;left:50%;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}._ns-u-sticky{position:-webkit-sticky;position:sticky}._ns-u-flow-middle{margin-left:auto;margin-right:auto}._ns-u-edge-top{top:0}._ns-u-edge-bottom{bottom:0}._ns-u-edge-left{left:0}._ns-u-edge-right{right:0}._ns-u-p0{padding:0}._ns-u-p1{padding:.5rem}._ns-u-p2{padding:1rem}._ns-u-p3{padding:2rem}._ns-u-p4{padding:4rem}._ns-u-p5{padding:8rem}._ns-u-pt0{padding-top:0}._ns-u-pt1{padding-top:.5rem}._ns-u-pt2{padding-top:1rem}._ns-u-pt3{padding-top:2rem}._ns-u-pt4{padding-top:4rem}._ns-u-pt5{padding-top:8rem}._ns-u-pr0{padding-right:0}._ns-u-pr1{padding-right:.5rem}._ns-u-pr2{padding-right:1rem}._ns-u-pr3{padding-right:2rem}._ns-u-pr4{padding-right:4rem}._ns-u-pr5{padding-right:8rem}._ns-u-pb0{padding-bottom:0}._ns-u-pb1{padding-bottom:.5rem}._ns-u-pb2{padding-bottom:1rem}._ns-u-pb3{padding-bottom:2rem}._ns-u-pb4{padding-bottom:4rem}._ns-u-pb5{padding-bottom:8rem}._ns-u-pl0{padding-left:0}._ns-u-pl1{padding-left:.5rem}._ns-u-pl2{padding-left:1rem}._ns-u-pl3{padding-left:2rem}._ns-u-pl4{padding-left:4rem}._ns-u-pl5{padding-left:8rem}._ns-u-m-auto{margin:auto}._ns-u-m0{margin:0}._ns-u-m1{margin:.5rem}._ns-u-m2{margin:1rem}._ns-u-m3{margin:2rem}._ns-u-m4{margin:4rem}._ns-u-m5{margin:8rem}._ns-u-mt-auto{margin-top:auto}._ns-u-mt0{margin-top:0}._ns-u-mt1{margin-top:.5rem}._ns-u-mt2{margin-top:1rem}._ns-u-mt3{margin-top:2rem}._ns-u-mt4{margin-top:4rem}._ns-u-mt5{margin-top:8rem}._ns-u-mr-auto{margin-right:auto}._ns-u-mr0{margin-right:0}._ns-u-mr1{margin-right:.5rem}._ns-u-mr2{margin-right:1rem}._ns-u-mr3{margin-right:2rem}._ns-u-mr4{margin-right:4rem}._ns-u-mr5{margin-right:8rem}._ns-u-mb-auto{margin-bottom:auto}._ns-u-mb0{margin-bottom:0}._ns-u-mb1{margin-bottom:.5rem}._ns-u-mb2{margin-bottom:1rem}._ns-u-mb3{margin-bottom:2rem}._ns-u-mb4{margin-bottom:4rem}._ns-u-mb5{margin-bottom:8rem}._ns-u-ml-auto{margin-left:auto}._ns-u-ml0{margin-left:0}._ns-u-ml1{margin-left:.5rem}._ns-u-ml2{margin-left:1rem}._ns-u-ml3{margin-left:2rem}._ns-u-ml4{margin-left:4rem}._ns-u-ml5{margin-left:8rem}}@media screen and (min-width:undefined){body{font-size:1.728em}h1{font-size:3.58318em}h2{font-size:2.98598em}h3{font-size:2.48832em}h4{font-size:2.0736em}}@media screen and (min-width:48em){h1{font-size:2.48832em}h2{font-size:2.0736em}h3{font-size:1.728em}h4{font-size:1.44em}dd{margin-left:2em}ul{padding-left:3em}ol{padding-left:3em}.o-flex-grid._md-o-flex-grid--gutters{margin:-1em 0 0 -1em}.o-flex-grid._md-o-flex-grid--gutters>.o-grid-cell{padding:1em 0 0 1em}.o-flex-grid._md-o-flex-grid--guttersLg{margin:-1.5em 0 0 -1.5em}.o-flex-grid._md-o-flex-grid--guttersLg>.o-grid-cell{padding:1.5em 0 0 1.5em}.o-flex-grid._md-o-flex-grid--guttersXl{margin:-2em 0 0 -2em}.o-flex-grid._md-o-flex-grid--guttersXl>.o-grid-cell{padding:2em 0 0 2em}.o-flex-grid._md-o-flex-grid--fit>.o-grid-cell{-webkit-box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}.o-flex-grid._md-o-flex-grid--full>.o-grid-cell{-webkit-box-flex:0;-webkit-flex:0 0 100%;-ms-flex:0 0 100%;flex:0 0 100%;max-width:100%}.o-flex-grid._md-o-flex-grid--oneHalf>.o-grid-cell{-webkit-box-flex:0;-webkit-flex:0 0 50%;-ms-flex:0 0 50%;flex:0 0 50%;max-width:50%}.o-flex-grid._md-o-flex-grid--oneThird>.o-grid-cell{-webkit-box-flex:0;-webkit-flex:0 0 33.33333%;-ms-flex:0 0 33.33333%;flex:0 0 33.33333%;max-width:33.33333%}.o-flex-grid._md-o-flex-grid--oneQuarter>.o-grid-cell{-webkit-box-flex:0;-webkit-flex:0 0 25%;-ms-flex:0 0 25%;flex:0 0 25%;max-width:25%}._md-o-list--unflattening{list-style-type:disc;padding-left:3em}._md-o-list--unflattening>li{display:list-item;list-style-type:disc}._md-u-align-left{text-align:left!important}._md-u-align-right{text-align:right!important}._md-u-align-center{text-align:center!important}._md-u-block{display:block}._md-u-inline-block{display:inline-block}._md-u-inline{display:inline}._md-u-justify-start{-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}._md-u-justify-end{-webkit-box-pack:end;-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}._md-u-justify-center{-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}._md-u-static{position:static}._md-u-relative{position:relative}._md-u-absolute{position:absolute}._md-u-fixed{position:fixed}._md-u-transform-center{top:50%;left:50%;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}._md-u-offset-left{top:0;left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}._md-u-sticky{position:-webkit-sticky;position:sticky}._md-u-flow-middle{margin-left:auto;margin-right:auto}._md-u-edge-top{top:0}._md-u-edge-bottom{bottom:0}._md-u-edge-left{left:0}._md-u-edge-right{right:0}._md-u-p0{padding:0}._md-u-p1{padding:.5rem}._md-u-p2{padding:1rem}._md-u-p3{padding:2rem}._md-u-p4{padding:4rem}._md-u-p5{padding:8rem}._md-u-pt0{padding-top:0}._md-u-pt1{padding-top:.5rem}._md-u-pt2{padding-top:1rem}._md-u-pt3{padding-top:2rem}._md-u-pt4{padding-top:4rem}._md-u-pt5{padding-top:8rem}._md-u-pr0{padding-right:0}._md-u-pr1{padding-right:.5rem}._md-u-pr2{padding-right:1rem}._md-u-pr3{padding-right:2rem}._md-u-pr4{padding-right:4rem}._md-u-pr5{padding-right:8rem}._md-u-pb0{padding-bottom:0}._md-u-pb1{padding-bottom:.5rem}._md-u-pb2{padding-bottom:1rem}._md-u-pb3{padding-bottom:2rem}._md-u-pb4{padding-bottom:4rem}._md-u-pb5{padding-bottom:8rem}._md-u-pl0{padding-left:0}._md-u-pl1{padding-left:.5rem}._md-u-pl2{padding-left:1rem}._md-u-pl3{padding-left:2rem}._md-u-pl4{padding-left:4rem}._md-u-pl5{padding-left:8rem}._md-u-m-auto{margin:auto}._md-u-m0{margin:0}._md-u-m1{margin:.5rem}._md-u-m2{margin:1rem}._md-u-m3{margin:2rem}._md-u-m4{margin:4rem}._md-u-m5{margin:8rem}._md-u-mt-auto{margin-top:auto}._md-u-mt0{margin-top:0}._md-u-mt1{margin-top:.5rem}._md-u-mt2{margin-top:1rem}._md-u-mt3{margin-top:2rem}._md-u-mt4{margin-top:4rem}._md-u-mt5{margin-top:8rem}._md-u-mr-auto{margin-right:auto}._md-u-mr0{margin-right:0}._md-u-mr1{margin-right:.5rem}._md-u-mr2{margin-right:1rem}._md-u-mr3{margin-right:2rem}._md-u-mr4{margin-right:4rem}._md-u-mr5{margin-right:8rem}._md-u-mb-auto{margin-bottom:auto}._md-u-mb0{margin-bottom:0}._md-u-mb1{margin-bottom:.5rem}._md-u-mb2{margin-bottom:1rem}._md-u-mb3{margin-bottom:2rem}._md-u-mb4{margin-bottom:4rem}._md-u-mb5{margin-bottom:8rem}._md-u-ml-auto{margin-left:auto}._md-u-ml0{margin-left:0}._md-u-ml1{margin-left:.5rem}._md-u-ml2{margin-left:1rem}._md-u-ml3{margin-left:2rem}._md-u-ml4{margin-left:4rem}._md-u-ml5{margin-left:8rem}}@media screen and (min-width:80em){._xl-u-align-left{text-align:left!important}._xl-u-align-right{text-align:right!important}._xl-u-align-center{text-align:center!important}._xl-u-justify-start{-webkit-box-pack:start;-webkit-justify-content:flex-start;-ms-flex-pack:start;justify-content:flex-start}._xl-u-justify-end{-webkit-box-pack:end;-webkit-justify-content:flex-end;-ms-flex-pack:end;justify-content:flex-end}._xl-u-justify-center{-webkit-box-pack:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center}._xlg-u-static{position:static}._xlg-u-relative{position:relative}._xlg-u-absolute{position:absolute}._xlg-u-fixed{position:fixed}._xlg-u-transform-center{top:50%;left:50%;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%)}._xlg-u-offset-left{top:0;left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}._xlg-u-sticky{position:-webkit-sticky;position:sticky}._xlg-u-flow-middle{margin-left:auto;margin-right:auto}._xlg-u-edge-top{top:0}._xlg-u-edge-bottom{bottom:0}._xlg-u-edge-left{left:0}._xlg-u-edge-right{right:0}}
--------------------------------------------------------------------------------