├── .circleci └── config.yml ├── .editorconfig ├── .eslintrc.json ├── .gitignore ├── .npmignore ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE ├── README.md ├── bower.json ├── build ├── args.js ├── babel-options.js ├── paths.js ├── tasks │ ├── build.js │ ├── clean.js │ ├── dev.js │ ├── doc.js │ ├── lint.js │ ├── prepare-release.js │ └── test.js └── typescript-options.js ├── config.js ├── dist ├── amd │ ├── aurelia-animator-css.js │ └── index.js ├── aurelia-animator-css.d.ts ├── aurelia-animator-css.js ├── commonjs │ ├── aurelia-animator-css.js │ └── index.js ├── es2015 │ ├── aurelia-animator-css.js │ └── index.js ├── index.d.ts ├── native-modules │ ├── aurelia-animator-css.js │ └── index.js └── system │ ├── aurelia-animator-css.js │ └── index.js ├── doc ├── CHANGELOG.md └── api.json ├── gulpfile.js ├── karma.conf.js ├── package-lock.json ├── package.json ├── src ├── animator.js └── index.js ├── test ├── animate.spec.js ├── animator.spec.js ├── fixtures │ ├── addremove.html │ ├── animate-missing-keyframes.html │ ├── animate.html │ ├── animation.html │ └── run-sequence.html └── run-sequence.spec.js ├── tsconfig.json └── typings.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Defaults for all jobs 2 | 3 | defaults: &defaults 4 | working_directory: ~/repo 5 | docker: 6 | - image: aureliaeffect/circleci-v1:latest 7 | 8 | # Variables 9 | var_1: &cache_key aurelia-{{ .Branch }}-{{ checksum "package.json" }}{{ checksum "package-lock.json" }} 10 | 11 | version: 2 12 | jobs: 13 | install: 14 | <<: *defaults 15 | steps: 16 | - checkout 17 | - run: npm ci 18 | - run: jspm install 19 | - save_cache: 20 | key: *cache_key 21 | paths: 22 | - node_modules 23 | - jspm_packages 24 | 25 | build: 26 | <<: *defaults 27 | steps: 28 | - checkout 29 | - restore_cache: 30 | key: *cache_key 31 | - run: gulp build 32 | - store_artifacts: 33 | path: ./dist 34 | 35 | unit_tests: 36 | <<: *defaults 37 | steps: 38 | - checkout 39 | - restore_cache: 40 | key: *cache_key 41 | - run: gulp cover 42 | - run: codecov -f ./build/reports/coverage/coverage-final.json 43 | - store_test_results: 44 | path: ./build/reports/coverage 45 | - store_artifacts: 46 | path: ./build/reports/coverage 47 | 48 | lint: 49 | <<: *defaults 50 | steps: 51 | - checkout 52 | - restore_cache: 53 | key: *cache_key 54 | - run: gulp lint 55 | 56 | workflows: 57 | version: 2 58 | default_workflow: 59 | jobs: 60 | - install 61 | - build: 62 | requires: 63 | - install 64 | - unit_tests: 65 | requires: 66 | - install 67 | - lint: 68 | requires: 69 | - install 70 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | # Unix-style newlines with a newline ending every file 7 | [*] 8 | end_of_line = lf 9 | insert_final_newline = true 10 | 11 | # 2 space indentation 12 | [**.*] 13 | indent_style = space 14 | indent_size = 2 -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/aurelia-tools/.eslintrc.json", 3 | "rules": { 4 | "no-empty": 0 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | jspm_packages 3 | bower_components 4 | .idea 5 | .DS_STORE 6 | *.swp 7 | build/reports 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | jspm_packages 2 | bower_components 3 | .idea 4 | build/reports 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We'd love for you to contribute and to make this project even better than it is today! If this interests you, please begin by reading [our contributing guidelines](https://github.com/DurandalProject/about/blob/master/CONTRIBUTING.md). The contributing document will provide you with all the information you need to get started. Once you have read that, you will need to also [sign our CLA](http://goo.gl/forms/dI8QDDSyKR) before we can accept a Pull Request from you. More information on the process is included in the [contributor's guide](https://github.com/DurandalProject/about/blob/master/CONTRIBUTING.md). 4 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 19 | **I'm submitting a bug report** 20 | **I'm submitting a feature request** 21 | 22 | * **Library Version:** 23 | major.minor.patch-pre 24 | 25 | 26 | **Please tell us about your environment:** 27 | * **Operating System:** 28 | OSX 10.x|Linux (distro)|Windows [7|8|8.1|10] 29 | 30 | * **Node Version:** 31 | 6.2.0 32 | 36 | 37 | * **NPM Version:** 38 | 3.8.9 39 | 43 | 44 | * **JSPM OR Webpack AND Version** 45 | JSPM 0.16.32 | webpack 2.1.0-beta.17 46 | 52 | 53 | * **Browser:** 54 | all | Chrome XX | Firefox XX | Edge XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView 55 | 56 | * **Language:** 57 | all | TypeScript X.X | ESNext 58 | 59 | 60 | **Current behavior:** 61 | 62 | 63 | **Expected/desired behavior:** 64 | 71 | 72 | 73 | * **What is the expected behavior?** 74 | 75 | 76 | * **What is the motivation / use case for changing the behavior?** 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2010 - 2018 Blue Spire Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # aurelia-animator-css 2 | 3 | [![npm Version](https://img.shields.io/npm/v/aurelia-animator-css.svg)](https://www.npmjs.com/package/aurelia-animator-css) 4 | [![ZenHub](https://raw.githubusercontent.com/ZenHubIO/support/master/zenhub-badge.png)](https://zenhub.io) 5 | [![Join the chat at https://gitter.im/aurelia/discuss](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/aurelia/discuss?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 6 | [![CircleCI](https://circleci.com/gh/aurelia/animator-css.svg?style=shield)](https://circleci.com/gh/aurelia/animator-css) 7 | 8 | An implementation of the abstract Animator interface from templating which enables css-based animations. 9 | 10 | > To keep up to date on [Aurelia](http://www.aurelia.io/), please visit and subscribe to [the official blog](http://blog.aurelia.io/) and [our email list](http://eepurl.com/ces50j). We also invite you to [follow us on twitter](https://twitter.com/aureliaeffect). If you have questions look around our [Discourse forums](https://discourse.aurelia.io/), chat in our [community on Gitter](https://gitter.im/aurelia/discuss) or use [stack overflow](http://stackoverflow.com/search?q=aurelia). Documentation can be found [in our developer hub](http://aurelia.io/docs). If you would like to have deeper insight into our development process, please install the [ZenHub](https://zenhub.io) Chrome or Firefox Extension and visit any of our repository's boards. 11 | 12 | ## Platform Support 13 | 14 | This library can be used in the **browser**. 15 | 16 | ## Building The Code 17 | 18 | To build the code, follow these steps. 19 | 20 | 1. Ensure that [NodeJS](http://nodejs.org/) is installed. This provides the platform on which the build tooling runs. 21 | 2. From the project folder, execute the following command: 22 | 23 | ```shell 24 | npm install 25 | ``` 26 | 3. Ensure that [Gulp](http://gulpjs.com/) is installed. If you need to install it, use the following command: 27 | 28 | ```shell 29 | npm install -g gulp 30 | ``` 31 | 4. To build the code, you can now run: 32 | 33 | ```shell 34 | gulp build 35 | ``` 36 | 5. You will find the compiled code in the `dist` folder, available in three module formats: AMD, CommonJS and ES6. 37 | 38 | 6. See `gulpfile.js` for other tasks related to generating the docs and linting. 39 | 40 | ## Running The Tests 41 | 42 | To run the unit tests, first ensure that you have followed the steps above in order to install all dependencies and successfully build the library. Once you have done that, proceed with these additional steps: 43 | 44 | 1. Ensure that the [Karma](http://karma-runner.github.io/) CLI is installed. If you need to install it, use the following command: 45 | 46 | ```shell 47 | npm install -g karma-cli 48 | ``` 49 | 2. Ensure that [jspm](http://jspm.io/) is installed. If you need to install it, use the following commnand: 50 | 51 | ```shell 52 | npm install -g jspm 53 | ``` 54 | 3. Download the [SystemJS](https://github.com/systemjs/systemjs) module loader: 55 | 56 | ```shell 57 | jspm dl-loader 58 | ``` 59 | 60 | 4. You can now run the tests with this command: 61 | 62 | ```shell 63 | karma start 64 | ``` 65 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aurelia-animator-css", 3 | "version": "1.0.4", 4 | "description": "An implementation of the abstract Animator interface from templating which enables css-based animations.", 5 | "keywords": [ 6 | "aurelia", 7 | "animation", 8 | "css", 9 | "plugin" 10 | ], 11 | "homepage": "http://aurelia.io", 12 | "main": "dist/commonjs/aurelia-animator-css.js", 13 | "moduleType": "node", 14 | "license": "MIT", 15 | "authors": [ 16 | "Rob Eisenberg (http://robeisenberg.com/)" 17 | ], 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/aurelia/animator-css" 21 | }, 22 | "dependencies": { 23 | "aurelia-metadata": "^1.0.0", 24 | "aurelia-pal": "^1.0.0", 25 | "aurelia-templating": "^1.0.0" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /build/args.js: -------------------------------------------------------------------------------- 1 | var yargs = require('yargs'); 2 | 3 | var argv = yargs.argv, 4 | validBumpTypes = "major|minor|patch|prerelease".split("|"), 5 | bump = (argv.bump || 'patch').toLowerCase(); 6 | 7 | if(validBumpTypes.indexOf(bump) === -1) { 8 | throw new Error('Unrecognized bump "' + bump + '".'); 9 | } 10 | 11 | module.exports = { 12 | bump: bump 13 | }; 14 | -------------------------------------------------------------------------------- /build/babel-options.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var paths = require('./paths'); 3 | 4 | exports.base = function() { 5 | var config = { 6 | filename: '', 7 | filenameRelative: '', 8 | sourceMap: true, 9 | sourceRoot: '', 10 | moduleRoot: path.resolve('src').replace(/\\/g, '/'), 11 | moduleIds: false, 12 | comments: false, 13 | compact: false, 14 | code: true, 15 | presets: [ 'es2015-loose', 'stage-1' ], 16 | plugins: [ 17 | 'syntax-flow', 18 | 'transform-decorators-legacy', 19 | ] 20 | }; 21 | if (!paths.useTypeScriptForDTS) { 22 | config.plugins.push( 23 | ['babel-dts-generator', { 24 | packageName: paths.packageName, 25 | typings: '', 26 | suppressModulePath: true, 27 | suppressComments: false, 28 | memberOutputFilter: /^_.*/, 29 | suppressAmbientDeclaration: true 30 | }] 31 | ); 32 | }; 33 | config.plugins.push('transform-flow-strip-types'); 34 | return config; 35 | } 36 | 37 | exports.commonjs = function() { 38 | var options = exports.base(); 39 | options.plugins.push('transform-es2015-modules-commonjs'); 40 | return options; 41 | }; 42 | 43 | exports.amd = function() { 44 | var options = exports.base(); 45 | options.plugins.push('transform-es2015-modules-amd'); 46 | return options; 47 | }; 48 | 49 | exports.system = function() { 50 | var options = exports.base(); 51 | options.plugins.push('transform-es2015-modules-systemjs'); 52 | return options; 53 | }; 54 | 55 | exports.es2015 = function() { 56 | var options = exports.base(); 57 | options.presets = ['stage-1'] 58 | return options; 59 | }; 60 | 61 | exports['native-modules'] = function() { 62 | var options = exports.base(); 63 | options.presets[0] = 'es2015-loose-native-modules'; 64 | return options; 65 | } 66 | -------------------------------------------------------------------------------- /build/paths.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var fs = require('fs'); 3 | 4 | // hide warning // 5 | var emitter = require('events'); 6 | emitter.defaultMaxListeners = 20; 7 | 8 | var appRoot = 'src/'; 9 | var pkg = JSON.parse(fs.readFileSync('./package.json', 'utf-8')); 10 | 11 | var paths = { 12 | root: appRoot, 13 | source: appRoot + '**/*.js', 14 | html: appRoot + '**/*.html', 15 | style: 'styles/**/*.css', 16 | output: 'dist/', 17 | doc:'./doc', 18 | unitTests: 'test/**/*.js', 19 | e2eSpecsSrc: 'test/e2e/src/*.js', 20 | e2eSpecsDist: 'test/e2e/dist/', 21 | packageName: pkg.name, 22 | ignore: [], 23 | useTypeScriptForDTS: false, 24 | importsToAdd: [], 25 | sort: false 26 | }; 27 | 28 | paths.files = [ 29 | 'animator.js', 30 | 'index.js' 31 | ].map(function(file){ 32 | return paths.root + file; 33 | }); 34 | 35 | module.exports = paths; 36 | -------------------------------------------------------------------------------- /build/tasks/build.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var runSequence = require('run-sequence'); 3 | var to5 = require('gulp-babel'); 4 | var paths = require('../paths'); 5 | var compilerOptions = require('../babel-options'); 6 | var compilerTsOptions = require('../typescript-options'); 7 | var assign = Object.assign || require('object.assign'); 8 | var through2 = require('through2'); 9 | var concat = require('gulp-concat'); 10 | var insert = require('gulp-insert'); 11 | var rename = require('gulp-rename'); 12 | var tools = require('aurelia-tools'); 13 | var ts = require('gulp-typescript'); 14 | var gutil = require('gulp-util'); 15 | var gulpIgnore = require('gulp-ignore'); 16 | var merge = require('merge2'); 17 | var jsName = paths.packageName + '.js'; 18 | var compileToModules = ['es2015', 'commonjs', 'amd', 'system', 'native-modules']; 19 | 20 | function cleanGeneratedCode() { 21 | return through2.obj(function(file, enc, callback) { 22 | file.contents = new Buffer(tools.cleanGeneratedCode(file.contents.toString('utf8'))); 23 | this.push(file); 24 | return callback(); 25 | }); 26 | } 27 | 28 | gulp.task('build-index', function() { 29 | var importsToAdd = paths.importsToAdd.slice(); 30 | 31 | var src = gulp.src(paths.files); 32 | 33 | if (paths.sort) { 34 | src = src.pipe(tools.sortFiles()); 35 | } 36 | 37 | if (paths.ignore) { 38 | paths.ignore.forEach(function(filename){ 39 | src = src.pipe(gulpIgnore.exclude(filename)); 40 | }); 41 | } 42 | 43 | return src.pipe(through2.obj(function(file, enc, callback) { 44 | file.contents = new Buffer(tools.extractImports(file.contents.toString('utf8'), importsToAdd)); 45 | this.push(file); 46 | return callback(); 47 | })) 48 | .pipe(concat(jsName)) 49 | .pipe(insert.transform(function(contents) { 50 | return tools.createImportBlock(importsToAdd) + contents; 51 | })) 52 | .pipe(gulp.dest(paths.output)); 53 | }); 54 | 55 | function gulpFileFromString(filename, string) { 56 | var src = require('stream').Readable({ objectMode: true }); 57 | src._read = function() { 58 | this.push(new gutil.File({ cwd: paths.appRoot, base: paths.output, path: filename, contents: new Buffer(string) })) 59 | this.push(null) 60 | } 61 | return src; 62 | } 63 | 64 | function srcForBabel() { 65 | return merge( 66 | gulp.src(paths.output + jsName), 67 | gulpFileFromString(paths.output + 'index.js', "export * from './" + paths.packageName + "';") 68 | ); 69 | } 70 | 71 | function srcForTypeScript() { 72 | return gulp 73 | .src(paths.output + paths.packageName + '.js') 74 | .pipe(rename(function (path) { 75 | if (path.extname == '.js') { 76 | path.extname = '.ts'; 77 | } 78 | })); 79 | } 80 | 81 | compileToModules.forEach(function(moduleType){ 82 | gulp.task('build-babel-' + moduleType, function () { 83 | return srcForBabel() 84 | .pipe(to5(assign({}, compilerOptions[moduleType]()))) 85 | .pipe(cleanGeneratedCode()) 86 | .pipe(gulp.dest(paths.output + moduleType)); 87 | }); 88 | 89 | if (moduleType === 'native-modules') return; // typescript doesn't support the combination of: es5 + native modules 90 | 91 | gulp.task('build-ts-' + moduleType, function () { 92 | var tsProject = ts.createProject( 93 | compilerTsOptions({ module: moduleType, target: moduleType == 'es2015' ? 'es2015' : 'es5' }), ts.reporter.defaultReporter()); 94 | var tsResult = srcForTypeScript().pipe(ts(tsProject)); 95 | return tsResult.js 96 | .pipe(gulp.dest(paths.output + moduleType)); 97 | }); 98 | }); 99 | 100 | gulp.task('build-dts', function() { 101 | var tsProject = ts.createProject( 102 | compilerTsOptions({ removeComments: false, target: "es2015", module: "es2015" }), ts.reporter.defaultReporter()); 103 | var tsResult = srcForTypeScript().pipe(ts(tsProject)); 104 | return tsResult.dts 105 | .pipe(gulp.dest(paths.output)); 106 | }); 107 | 108 | gulp.task('build', function(callback) { 109 | return runSequence( 110 | 'clean', 111 | 'build-index', 112 | compileToModules 113 | .map(function(moduleType) { return 'build-babel-' + moduleType }) 114 | .concat(paths.useTypeScriptForDTS ? ['build-dts'] : []), 115 | callback 116 | ); 117 | }); 118 | 119 | gulp.task('build-ts', function(callback) { 120 | return runSequence( 121 | 'clean', 122 | 'build-index', 123 | 'build-babel-native-modules', 124 | compileToModules 125 | .filter(function(moduleType) { return moduleType !== 'native-modules' }) 126 | .map(function(moduleType) { return 'build-ts-' + moduleType }) 127 | .concat(paths.useTypeScriptForDTS ? ['build-dts'] : []), 128 | callback 129 | ); 130 | }); 131 | -------------------------------------------------------------------------------- /build/tasks/clean.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var paths = require('../paths'); 3 | var del = require('del'); 4 | var vinylPaths = require('vinyl-paths'); 5 | 6 | gulp.task('clean', function() { 7 | return gulp.src([paths.output]) 8 | .pipe(vinylPaths(del)); 9 | }); 10 | -------------------------------------------------------------------------------- /build/tasks/dev.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var tools = require('aurelia-tools'); 3 | 4 | gulp.task('update-own-deps', function(){ 5 | tools.updateOwnDependenciesFromLocalRepositories(); 6 | }); 7 | 8 | gulp.task('build-dev-env', function () { 9 | tools.buildDevEnv(); 10 | }); 11 | -------------------------------------------------------------------------------- /build/tasks/doc.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var paths = require('../paths'); 3 | var typedoc = require('gulp-typedoc'); 4 | var runSequence = require('run-sequence'); 5 | var through2 = require('through2'); 6 | 7 | gulp.task('doc-generate', function(){ 8 | return gulp.src([paths.output + paths.packageName + '.d.ts']) 9 | .pipe(typedoc({ 10 | target: 'es6', 11 | includeDeclarations: true, 12 | moduleResolution: 'node', 13 | json: paths.doc + '/api.json', 14 | name: paths.packageName + '-docs',  15 | mode: 'modules', 16 | excludeExternals: true, 17 | ignoreCompilerErrors: false, 18 | version: true 19 | })); 20 | }); 21 | 22 | gulp.task('doc-shape', function(){ 23 | return gulp.src([paths.doc + '/api.json']) 24 | .pipe(through2.obj(function(file, enc, callback) { 25 | var json = JSON.parse(file.contents.toString('utf8')).children[0]; 26 | 27 | json = { 28 | name: paths.packageName, 29 | children: json.children, 30 | groups: json.groups 31 | }; 32 | 33 | file.contents = new Buffer(JSON.stringify(json)); 34 | this.push(file); 35 | return callback(); 36 | })) 37 | .pipe(gulp.dest(paths.doc)); 38 | }); 39 | 40 | gulp.task('doc', function(callback){ 41 | return runSequence( 42 | 'doc-generate', 43 | 'doc-shape', 44 | callback 45 | ); 46 | }); 47 | -------------------------------------------------------------------------------- /build/tasks/lint.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var paths = require('../paths'); 3 | var eslint = require('gulp-eslint'); 4 | 5 | gulp.task('lint', function() { 6 | return gulp.src([paths.source, paths.unitTests]) 7 | .pipe(eslint()) 8 | .pipe(eslint.format()) 9 | .pipe(eslint.failOnError()); 10 | }); 11 | -------------------------------------------------------------------------------- /build/tasks/prepare-release.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var runSequence = require('run-sequence'); 3 | var paths = require('../paths'); 4 | var conventionalChangelog = require('gulp-conventional-changelog'); 5 | var fs = require('fs'); 6 | var bump = require('gulp-bump'); 7 | var args = require('../args'); 8 | 9 | gulp.task('bump-version', function(){ 10 | return gulp.src(['./package.json', './bower.json']) 11 | .pipe(bump({type:args.bump })) //major|minor|patch|prerelease 12 | .pipe(gulp.dest('./')); 13 | }); 14 | 15 | gulp.task('changelog', function () { 16 | return gulp.src(paths.doc + '/CHANGELOG.md', { 17 | buffer: false 18 | }).pipe(conventionalChangelog({ 19 | preset: 'angular' 20 | })) 21 | .pipe(gulp.dest(paths.doc)); 22 | }); 23 | 24 | gulp.task('prepare-release', function(callback){ 25 | return runSequence( 26 | 'build', 27 | 'lint', 28 | 'bump-version', 29 | 'doc', 30 | 'changelog', 31 | callback 32 | ); 33 | }); 34 | -------------------------------------------------------------------------------- /build/tasks/test.js: -------------------------------------------------------------------------------- 1 | var gulp = require('gulp'); 2 | var Karma = require('karma').Server; 3 | 4 | /** 5 | * Run test once and exit 6 | */ 7 | gulp.task('test', function (done) { 8 | new Karma({ 9 | configFile: __dirname + '/../../karma.conf.js', 10 | singleRun: true 11 | }, done).start(); 12 | }); 13 | 14 | /** 15 | * Watch for file changes and re-run tests on each change 16 | */ 17 | gulp.task('tdd', function (done) { 18 | new Karma({ 19 | configFile: __dirname + '/../../karma.conf.js' 20 | }, done).start(); 21 | }); 22 | 23 | /** 24 | * Run test once with code coverage and exit 25 | */ 26 | gulp.task('cover', function (done) { 27 | new Karma({ 28 | configFile: __dirname + '/../../karma.conf.js', 29 | singleRun: true, 30 | reporters: ['progress', 'coverage'], 31 | preprocessors: { 32 | 'test/**/*.js': ['babel'], 33 | 'src/**/*.js': ['babel', 'coverage'] 34 | }, 35 | coverageReporter: { 36 | dir: 'build/reports/coverage', 37 | reporters: [ 38 | { type: 'html', subdir: 'report-html' }, 39 | { type: 'json', subdir: '.', file: 'coverage-final.json' } 40 | ] 41 | } 42 | }, done).start(); 43 | }); 44 | -------------------------------------------------------------------------------- /build/typescript-options.js: -------------------------------------------------------------------------------- 1 | var tsconfig = require('../tsconfig.json'); 2 | var assign = Object.assign || require('object.assign'); 3 | 4 | module.exports = function(override) { 5 | return assign(tsconfig.compilerOptions, { 6 | "target": override && override.target || "es5", 7 | "typescript": require('typescript') 8 | }, override || {}); 9 | } 10 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | System.config({ 2 | defaultJSExtensions: true, 3 | paths: { 4 | "aurelia-animator-css/*": "dist\\system/*.js", 5 | "github:*": "jspm_packages/github/*", 6 | "npm:*": "jspm_packages/npm/*" 7 | }, 8 | 9 | map: { 10 | "aurelia-metadata": "npm:aurelia-metadata@1.0.0", 11 | "aurelia-pal": "npm:aurelia-pal@1.0.0", 12 | "aurelia-pal-browser": "npm:aurelia-pal-browser@1.0.0", 13 | "aurelia-templating": "npm:aurelia-templating@1.0.0", 14 | "babel": "npm:babel-core@5.8.38", 15 | "babel-runtime": "npm:babel-runtime@5.8.38", 16 | "core-js": "npm:core-js@2.4.1", 17 | "github:jspm/nodelibs-assert@0.1.0": { 18 | "assert": "npm:assert@1.4.1" 19 | }, 20 | "github:jspm/nodelibs-buffer@0.1.0": { 21 | "buffer": "npm:buffer@3.6.0" 22 | }, 23 | "github:jspm/nodelibs-path@0.1.0": { 24 | "path-browserify": "npm:path-browserify@0.0.0" 25 | }, 26 | "github:jspm/nodelibs-process@0.1.2": { 27 | "process": "npm:process@0.11.6" 28 | }, 29 | "github:jspm/nodelibs-util@0.1.0": { 30 | "util": "npm:util@0.10.3" 31 | }, 32 | "github:jspm/nodelibs-vm@0.1.0": { 33 | "vm-browserify": "npm:vm-browserify@0.0.4" 34 | }, 35 | "npm:assert@1.4.1": { 36 | "assert": "github:jspm/nodelibs-assert@0.1.0", 37 | "buffer": "github:jspm/nodelibs-buffer@0.1.0", 38 | "process": "github:jspm/nodelibs-process@0.1.2", 39 | "util": "npm:util@0.10.3" 40 | }, 41 | "npm:aurelia-binding@1.0.0": { 42 | "aurelia-logging": "npm:aurelia-logging@1.0.0", 43 | "aurelia-metadata": "npm:aurelia-metadata@1.0.0", 44 | "aurelia-pal": "npm:aurelia-pal@1.0.0", 45 | "aurelia-task-queue": "npm:aurelia-task-queue@1.0.0" 46 | }, 47 | "npm:aurelia-dependency-injection@1.0.0": { 48 | "aurelia-metadata": "npm:aurelia-metadata@1.0.0", 49 | "aurelia-pal": "npm:aurelia-pal@1.0.0" 50 | }, 51 | "npm:aurelia-loader@1.0.0": { 52 | "aurelia-metadata": "npm:aurelia-metadata@1.0.0", 53 | "aurelia-path": "npm:aurelia-path@1.0.0" 54 | }, 55 | "npm:aurelia-metadata@1.0.0": { 56 | "aurelia-pal": "npm:aurelia-pal@1.0.0" 57 | }, 58 | "npm:aurelia-pal-browser@1.0.0": { 59 | "aurelia-pal": "npm:aurelia-pal@1.0.0" 60 | }, 61 | "npm:aurelia-task-queue@1.0.0": { 62 | "aurelia-pal": "npm:aurelia-pal@1.0.0" 63 | }, 64 | "npm:aurelia-templating@1.0.0": { 65 | "aurelia-binding": "npm:aurelia-binding@1.0.0", 66 | "aurelia-dependency-injection": "npm:aurelia-dependency-injection@1.0.0", 67 | "aurelia-loader": "npm:aurelia-loader@1.0.0", 68 | "aurelia-logging": "npm:aurelia-logging@1.0.0", 69 | "aurelia-metadata": "npm:aurelia-metadata@1.0.0", 70 | "aurelia-pal": "npm:aurelia-pal@1.0.0", 71 | "aurelia-path": "npm:aurelia-path@1.0.0", 72 | "aurelia-task-queue": "npm:aurelia-task-queue@1.0.0" 73 | }, 74 | "npm:babel-runtime@5.8.38": { 75 | "process": "github:jspm/nodelibs-process@0.1.2" 76 | }, 77 | "npm:buffer@3.6.0": { 78 | "base64-js": "npm:base64-js@0.0.8", 79 | "child_process": "github:jspm/nodelibs-child_process@0.1.0", 80 | "fs": "github:jspm/nodelibs-fs@0.1.2", 81 | "ieee754": "npm:ieee754@1.1.6", 82 | "isarray": "npm:isarray@1.0.0", 83 | "process": "github:jspm/nodelibs-process@0.1.2" 84 | }, 85 | "npm:core-js@2.4.1": { 86 | "fs": "github:jspm/nodelibs-fs@0.1.2", 87 | "path": "github:jspm/nodelibs-path@0.1.0", 88 | "process": "github:jspm/nodelibs-process@0.1.2", 89 | "systemjs-json": "github:systemjs/plugin-json@0.1.2" 90 | }, 91 | "npm:inherits@2.0.1": { 92 | "util": "github:jspm/nodelibs-util@0.1.0" 93 | }, 94 | "npm:path-browserify@0.0.0": { 95 | "process": "github:jspm/nodelibs-process@0.1.2" 96 | }, 97 | "npm:process@0.11.6": { 98 | "assert": "github:jspm/nodelibs-assert@0.1.0", 99 | "fs": "github:jspm/nodelibs-fs@0.1.2", 100 | "vm": "github:jspm/nodelibs-vm@0.1.0" 101 | }, 102 | "npm:util@0.10.3": { 103 | "inherits": "npm:inherits@2.0.1", 104 | "process": "github:jspm/nodelibs-process@0.1.2" 105 | }, 106 | "npm:vm-browserify@0.0.4": { 107 | "indexof": "npm:indexof@0.0.1" 108 | } 109 | } 110 | }); 111 | -------------------------------------------------------------------------------- /dist/amd/aurelia-animator-css.js: -------------------------------------------------------------------------------- 1 | define(['exports', 'aurelia-templating', 'aurelia-pal'], function (exports, _aureliaTemplating, _aureliaPal) { 2 | 'use strict'; 3 | 4 | Object.defineProperty(exports, "__esModule", { 5 | value: true 6 | }); 7 | exports.CssAnimator = undefined; 8 | exports.configure = configure; 9 | 10 | 11 | 12 | var CssAnimator = exports.CssAnimator = function () { 13 | function CssAnimator() { 14 | 15 | 16 | this.useAnimationDoneClasses = false; 17 | this.animationEnteredClass = 'au-entered'; 18 | this.animationLeftClass = 'au-left'; 19 | this.isAnimating = false; 20 | 21 | this.verifyKeyframesExist = true; 22 | } 23 | 24 | CssAnimator.prototype._addMultipleEventListener = function _addMultipleEventListener(el, s, fn) { 25 | var evts = s.split(' '); 26 | for (var i = 0, ii = evts.length; i < ii; ++i) { 27 | el.addEventListener(evts[i], fn, false); 28 | } 29 | }; 30 | 31 | CssAnimator.prototype._removeMultipleEventListener = function _removeMultipleEventListener(el, s, fn) { 32 | var evts = s.split(' '); 33 | for (var i = 0, ii = evts.length; i < ii; ++i) { 34 | el.removeEventListener(evts[i], fn, false); 35 | } 36 | }; 37 | 38 | CssAnimator.prototype._getElementAnimationDelay = function _getElementAnimationDelay(element) { 39 | var styl = _aureliaPal.DOM.getComputedStyle(element); 40 | var prop = void 0; 41 | var delay = void 0; 42 | 43 | if (styl.getPropertyValue('animation-delay')) { 44 | prop = 'animation-delay'; 45 | } else if (styl.getPropertyValue('-webkit-animation-delay')) { 46 | prop = '-webkit-animation-delay'; 47 | } else if (styl.getPropertyValue('-moz-animation-delay')) { 48 | prop = '-moz-animation-delay'; 49 | } else { 50 | return 0; 51 | } 52 | 53 | delay = styl.getPropertyValue(prop); 54 | delay = Number(delay.replace(/[^\d\.]/g, '')); 55 | 56 | return delay * 1000; 57 | }; 58 | 59 | CssAnimator.prototype._getElementAnimationNames = function _getElementAnimationNames(element) { 60 | var styl = _aureliaPal.DOM.getComputedStyle(element); 61 | var prefix = void 0; 62 | 63 | if (styl.getPropertyValue('animation-name')) { 64 | prefix = ''; 65 | } else if (styl.getPropertyValue('-webkit-animation-name')) { 66 | prefix = '-webkit-'; 67 | } else if (styl.getPropertyValue('-moz-animation-name')) { 68 | prefix = '-moz-'; 69 | } else { 70 | return []; 71 | } 72 | 73 | var animationNames = styl.getPropertyValue(prefix + 'animation-name'); 74 | return animationNames ? animationNames.split(' ') : []; 75 | }; 76 | 77 | CssAnimator.prototype._performSingleAnimate = function _performSingleAnimate(element, className) { 78 | var _this = this; 79 | 80 | this._triggerDOMEvent(_aureliaTemplating.animationEvent.animateBegin, element); 81 | 82 | return this.addClass(element, className, true).then(function (result) { 83 | _this._triggerDOMEvent(_aureliaTemplating.animationEvent.animateActive, element); 84 | 85 | if (result !== false) { 86 | return _this.removeClass(element, className, true).then(function () { 87 | _this._triggerDOMEvent(_aureliaTemplating.animationEvent.animateDone, element); 88 | }); 89 | } 90 | 91 | return false; 92 | }).catch(function () { 93 | _this._triggerDOMEvent(_aureliaTemplating.animationEvent.animateTimeout, element); 94 | }); 95 | }; 96 | 97 | CssAnimator.prototype._triggerDOMEvent = function _triggerDOMEvent(eventType, element) { 98 | var evt = _aureliaPal.DOM.createCustomEvent(eventType, { bubbles: true, cancelable: true, detail: element }); 99 | _aureliaPal.DOM.dispatchEvent(evt); 100 | }; 101 | 102 | CssAnimator.prototype._animationChangeWithValidKeyframe = function _animationChangeWithValidKeyframe(animationNames, prevAnimationNames) { 103 | var newAnimationNames = animationNames.filter(function (name) { 104 | return prevAnimationNames.indexOf(name) === -1; 105 | }); 106 | 107 | if (newAnimationNames.length === 0) { 108 | return false; 109 | } 110 | 111 | if (!this.verifyKeyframesExist) { 112 | return true; 113 | } 114 | 115 | var keyframesRuleType = window.CSSRule.KEYFRAMES_RULE || window.CSSRule.MOZ_KEYFRAMES_RULE || window.CSSRule.WEBKIT_KEYFRAMES_RULE; 116 | 117 | var styleSheets = document.styleSheets; 118 | 119 | try { 120 | for (var i = 0; i < styleSheets.length; ++i) { 121 | var cssRules = null; 122 | 123 | try { 124 | cssRules = styleSheets[i].cssRules; 125 | } catch (e) {} 126 | 127 | if (!cssRules) { 128 | continue; 129 | } 130 | 131 | for (var j = 0; j < cssRules.length; ++j) { 132 | var cssRule = cssRules[j]; 133 | 134 | if (cssRule.type === keyframesRuleType) { 135 | if (newAnimationNames.indexOf(cssRule.name) !== -1) { 136 | return true; 137 | } 138 | } 139 | } 140 | } 141 | } catch (e) {} 142 | 143 | return false; 144 | }; 145 | 146 | CssAnimator.prototype.animate = function animate(element, className) { 147 | var _this2 = this; 148 | 149 | if (Array.isArray(element)) { 150 | return Promise.all(element.map(function (el) { 151 | return _this2._performSingleAnimate(el, className); 152 | })); 153 | } 154 | 155 | return this._performSingleAnimate(element, className); 156 | }; 157 | 158 | CssAnimator.prototype.runSequence = function runSequence(animations) { 159 | var _this3 = this; 160 | 161 | this._triggerDOMEvent(_aureliaTemplating.animationEvent.sequenceBegin, null); 162 | 163 | return animations.reduce(function (p, anim) { 164 | return p.then(function () { 165 | return _this3.animate(anim.element, anim.className); 166 | }); 167 | }, Promise.resolve(true)).then(function () { 168 | _this3._triggerDOMEvent(_aureliaTemplating.animationEvent.sequenceDone, null); 169 | }); 170 | }; 171 | 172 | CssAnimator.prototype._stateAnim = function _stateAnim(element, direction, doneClass) { 173 | var _this4 = this; 174 | 175 | var auClass = 'au-' + direction; 176 | var auClassActive = auClass + '-active'; 177 | return new Promise(function (resolve, reject) { 178 | var classList = element.classList; 179 | 180 | _this4._triggerDOMEvent(_aureliaTemplating.animationEvent[direction + 'Begin'], element); 181 | 182 | if (_this4.useAnimationDoneClasses) { 183 | classList.remove(_this4.animationEnteredClass); 184 | classList.remove(_this4.animationLeftClass); 185 | } 186 | 187 | classList.add(auClass); 188 | var prevAnimationNames = _this4._getElementAnimationNames(element); 189 | 190 | var _animStart = void 0; 191 | var animHasStarted = false; 192 | _this4._addMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart = function animStart(evAnimStart) { 193 | if (evAnimStart.target !== element) { 194 | return; 195 | } 196 | animHasStarted = true; 197 | _this4.isAnimating = true; 198 | 199 | _this4._triggerDOMEvent(_aureliaTemplating.animationEvent[direction + 'Active'], element); 200 | 201 | evAnimStart.stopPropagation(); 202 | 203 | evAnimStart.target.removeEventListener(evAnimStart.type, _animStart); 204 | }, false); 205 | 206 | var _animEnd = void 0; 207 | _this4._addMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd = function animEnd(evAnimEnd) { 208 | if (!animHasStarted) { 209 | return; 210 | } 211 | if (evAnimEnd.target !== element) { 212 | return; 213 | } 214 | 215 | evAnimEnd.stopPropagation(); 216 | 217 | classList.remove(auClassActive); 218 | classList.remove(auClass); 219 | 220 | evAnimEnd.target.removeEventListener(evAnimEnd.type, _animEnd); 221 | 222 | if (_this4.useAnimationDoneClasses && doneClass !== undefined && doneClass !== null) { 223 | classList.add(doneClass); 224 | } 225 | 226 | _this4.isAnimating = false; 227 | _this4._triggerDOMEvent(_aureliaTemplating.animationEvent[direction + 'Done'], element); 228 | 229 | resolve(true); 230 | }, false); 231 | 232 | var parent = element.parentElement; 233 | var attrib = 'data-animator-pending' + direction; 234 | 235 | var cleanupAnimation = function cleanupAnimation() { 236 | var animationNames = _this4._getElementAnimationNames(element); 237 | if (!_this4._animationChangeWithValidKeyframe(animationNames, prevAnimationNames)) { 238 | classList.remove(auClassActive); 239 | classList.remove(auClass); 240 | 241 | _this4._removeMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd); 242 | _this4._removeMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart); 243 | 244 | _this4._triggerDOMEvent(_aureliaTemplating.animationEvent[direction + 'Timeout'], element); 245 | resolve(false); 246 | } 247 | parent && parent.setAttribute(attrib, +(parent.getAttribute(attrib) || 1) - 1); 248 | }; 249 | 250 | if (parent !== null && parent !== undefined && (parent.classList.contains('au-stagger') || parent.classList.contains('au-stagger-' + direction))) { 251 | var offset = +(parent.getAttribute(attrib) || 0); 252 | parent.setAttribute(attrib, offset + 1); 253 | var delay = _this4._getElementAnimationDelay(parent) * offset; 254 | _this4._triggerDOMEvent(_aureliaTemplating.animationEvent.staggerNext, element); 255 | 256 | setTimeout(function () { 257 | classList.add(auClassActive); 258 | cleanupAnimation(); 259 | }, delay); 260 | } else { 261 | classList.add(auClassActive); 262 | cleanupAnimation(); 263 | } 264 | }); 265 | }; 266 | 267 | CssAnimator.prototype.enter = function enter(element) { 268 | return this._stateAnim(element, 'enter', this.animationEnteredClass); 269 | }; 270 | 271 | CssAnimator.prototype.leave = function leave(element) { 272 | return this._stateAnim(element, 'leave', this.animationLeftClass); 273 | }; 274 | 275 | CssAnimator.prototype.removeClass = function removeClass(element, className) { 276 | var _this5 = this; 277 | 278 | var suppressEvents = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; 279 | 280 | return new Promise(function (resolve, reject) { 281 | var classList = element.classList; 282 | 283 | if (!classList.contains(className) && !classList.contains(className + '-add')) { 284 | resolve(false); 285 | return; 286 | } 287 | 288 | if (suppressEvents !== true) { 289 | _this5._triggerDOMEvent(_aureliaTemplating.animationEvent.removeClassBegin, element); 290 | } 291 | 292 | if (classList.contains(className + '-add')) { 293 | classList.remove(className + '-add'); 294 | classList.add(className); 295 | } 296 | 297 | classList.remove(className); 298 | var prevAnimationNames = _this5._getElementAnimationNames(element); 299 | 300 | var _animStart2 = void 0; 301 | var animHasStarted = false; 302 | _this5._addMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart2 = function animStart(evAnimStart) { 303 | if (evAnimStart.target !== element) { 304 | return; 305 | } 306 | animHasStarted = true; 307 | _this5.isAnimating = true; 308 | 309 | if (suppressEvents !== true) { 310 | _this5._triggerDOMEvent(_aureliaTemplating.animationEvent.removeClassActive, element); 311 | } 312 | 313 | evAnimStart.stopPropagation(); 314 | 315 | evAnimStart.target.removeEventListener(evAnimStart.type, _animStart2); 316 | }, false); 317 | 318 | var _animEnd2 = void 0; 319 | _this5._addMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd2 = function animEnd(evAnimEnd) { 320 | if (!animHasStarted) { 321 | return; 322 | } 323 | if (evAnimEnd.target !== element) { 324 | return; 325 | } 326 | 327 | if (!element.classList.contains(className + '-remove')) { 328 | resolve(true); 329 | } 330 | 331 | evAnimEnd.stopPropagation(); 332 | 333 | classList.remove(className); 334 | 335 | classList.remove(className + '-remove'); 336 | 337 | evAnimEnd.target.removeEventListener(evAnimEnd.type, _animEnd2); 338 | 339 | _this5.isAnimating = false; 340 | 341 | if (suppressEvents !== true) { 342 | _this5._triggerDOMEvent(_aureliaTemplating.animationEvent.removeClassDone, element); 343 | } 344 | 345 | resolve(true); 346 | }, false); 347 | 348 | classList.add(className + '-remove'); 349 | 350 | var animationNames = _this5._getElementAnimationNames(element); 351 | if (!_this5._animationChangeWithValidKeyframe(animationNames, prevAnimationNames)) { 352 | classList.remove(className + '-remove'); 353 | classList.remove(className); 354 | 355 | _this5._removeMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd2); 356 | _this5._removeMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart2); 357 | 358 | if (suppressEvents !== true) { 359 | _this5._triggerDOMEvent(_aureliaTemplating.animationEvent.removeClassTimeout, element); 360 | } 361 | 362 | resolve(false); 363 | } 364 | }); 365 | }; 366 | 367 | CssAnimator.prototype.addClass = function addClass(element, className) { 368 | var _this6 = this; 369 | 370 | var suppressEvents = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; 371 | 372 | return new Promise(function (resolve, reject) { 373 | var classList = element.classList; 374 | 375 | if (suppressEvents !== true) { 376 | _this6._triggerDOMEvent(_aureliaTemplating.animationEvent.addClassBegin, element); 377 | } 378 | 379 | if (classList.contains(className + '-remove')) { 380 | classList.remove(className + '-remove'); 381 | classList.remove(className); 382 | } 383 | 384 | var _animStart3 = void 0; 385 | var animHasStarted = false; 386 | _this6._addMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart3 = function animStart(evAnimStart) { 387 | if (evAnimStart.target !== element) { 388 | return; 389 | } 390 | animHasStarted = true; 391 | _this6.isAnimating = true; 392 | 393 | if (suppressEvents !== true) { 394 | _this6._triggerDOMEvent(_aureliaTemplating.animationEvent.addClassActive, element); 395 | } 396 | 397 | evAnimStart.stopPropagation(); 398 | 399 | evAnimStart.target.removeEventListener(evAnimStart.type, _animStart3); 400 | }, false); 401 | 402 | var _animEnd3 = void 0; 403 | _this6._addMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd3 = function animEnd(evAnimEnd) { 404 | if (!animHasStarted) { 405 | return; 406 | } 407 | if (evAnimEnd.target !== element) { 408 | return; 409 | } 410 | 411 | if (!element.classList.contains(className + '-add')) { 412 | resolve(true); 413 | } 414 | 415 | evAnimEnd.stopPropagation(); 416 | 417 | classList.add(className); 418 | 419 | classList.remove(className + '-add'); 420 | 421 | evAnimEnd.target.removeEventListener(evAnimEnd.type, _animEnd3); 422 | 423 | _this6.isAnimating = false; 424 | 425 | if (suppressEvents !== true) { 426 | _this6._triggerDOMEvent(_aureliaTemplating.animationEvent.addClassDone, element); 427 | } 428 | 429 | resolve(true); 430 | }, false); 431 | 432 | var prevAnimationNames = _this6._getElementAnimationNames(element); 433 | 434 | classList.add(className + '-add'); 435 | 436 | var animationNames = _this6._getElementAnimationNames(element); 437 | if (!_this6._animationChangeWithValidKeyframe(animationNames, prevAnimationNames)) { 438 | classList.remove(className + '-add'); 439 | classList.add(className); 440 | 441 | _this6._removeMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd3); 442 | _this6._removeMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart3); 443 | 444 | if (suppressEvents !== true) { 445 | _this6._triggerDOMEvent(_aureliaTemplating.animationEvent.addClassTimeout, element); 446 | } 447 | 448 | resolve(false); 449 | } 450 | }); 451 | }; 452 | 453 | return CssAnimator; 454 | }(); 455 | 456 | function configure(config, callback) { 457 | var animator = config.container.get(CssAnimator); 458 | config.container.get(_aureliaTemplating.TemplatingEngine).configureAnimator(animator); 459 | if (typeof callback === 'function') { 460 | callback(animator); 461 | } 462 | } 463 | }); -------------------------------------------------------------------------------- /dist/amd/index.js: -------------------------------------------------------------------------------- 1 | define(['exports', './aurelia-animator-css'], function (exports, _aureliaAnimatorCss) { 2 | 'use strict'; 3 | 4 | Object.defineProperty(exports, "__esModule", { 5 | value: true 6 | }); 7 | Object.keys(_aureliaAnimatorCss).forEach(function (key) { 8 | if (key === "default" || key === "__esModule") return; 9 | Object.defineProperty(exports, key, { 10 | enumerable: true, 11 | get: function () { 12 | return _aureliaAnimatorCss[key]; 13 | } 14 | }); 15 | }); 16 | }); -------------------------------------------------------------------------------- /dist/aurelia-animator-css.d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | animationEvent, 3 | TemplatingEngine 4 | } from 'aurelia-templating'; 5 | import { 6 | DOM 7 | } from 'aurelia-pal'; 8 | export declare interface CssAnimation { 9 | className: string; 10 | element: Element; 11 | } 12 | 13 | /** 14 | * An implementation of the Animator using CSS3-Animations. 15 | */ 16 | /** 17 | * An implementation of the Animator using CSS3-Animations. 18 | */ 19 | export declare class CssAnimator { 20 | 21 | /** 22 | * Creates an instance of CssAnimator. 23 | */ 24 | constructor(); 25 | 26 | /* Public API Begin */ 27 | /** 28 | * Execute a single animation. 29 | * @param element Element to animate 30 | * @param className Properties to animate or name of the effect to use. For css animators this represents the className to be added and removed right after the animation is done. 31 | * @param options options for the animation (duration, easing, ...) 32 | * @returns Resolved when the animation is done 33 | */ 34 | animate(element: Element | Array, className: string): Promise; 35 | 36 | /** 37 | * Run a sequence of animations one after the other. 38 | * @param sequence An array of effectNames or classNames 39 | * @returns Resolved when all animations are done 40 | */ 41 | runSequence(animations: Array): Promise; 42 | 43 | /** 44 | * Execute an 'enter' animation on an element 45 | * @param element Element to animate 46 | * @returns Resolved when the animation is done 47 | */ 48 | enter(element: Element): Promise; 49 | 50 | /** 51 | * Execute a 'leave' animation on an element 52 | * @param element Element to animate 53 | * @returns Resolved when the animation is done 54 | */ 55 | leave(element: Element): Promise; 56 | 57 | /** 58 | * Add a class to an element to trigger an animation. 59 | * @param element Element to animate 60 | * @param className Properties to animate or name of the effect to use 61 | * @param suppressEvents Indicates whether or not to suppress animation events. 62 | * @returns Resolved when the animation is done 63 | */ 64 | removeClass(element: Element, className: string, suppressEvents?: boolean): Promise; 65 | 66 | /** 67 | * Add a class to an element to trigger an animation. 68 | * @param element Element to animate 69 | * @param className Properties to animate or name of the effect to use 70 | * @param suppressEvents Indicates whether or not to suppress animation events. 71 | * @returns Resolved when the animation is done 72 | */ 73 | addClass(element: Element, className: string, suppressEvents?: boolean): Promise; 74 | } 75 | 76 | /* Public API End */ 77 | /** 78 | * Configuires the CssAnimator as the default animator for Aurelia. 79 | * @param config The FrameworkConfiguration instance. 80 | * @param callback A configuration callback provided by the plugin consumer. 81 | */ 82 | export declare function configure(config: Object, callback?: ((animator: CssAnimator) => void)): void; -------------------------------------------------------------------------------- /dist/aurelia-animator-css.js: -------------------------------------------------------------------------------- 1 | import {animationEvent,TemplatingEngine} from 'aurelia-templating'; 2 | import {DOM} from 'aurelia-pal'; 3 | 4 | interface CssAnimation { 5 | className: string; 6 | element: Element; 7 | } 8 | 9 | /** 10 | * An implementation of the Animator using CSS3-Animations. 11 | */ 12 | export class CssAnimator { 13 | /** 14 | * Creates an instance of CssAnimator. 15 | */ 16 | constructor() { 17 | this.useAnimationDoneClasses = false; 18 | this.animationEnteredClass = 'au-entered'; 19 | this.animationLeftClass = 'au-left'; 20 | this.isAnimating = false; 21 | // toggle this on to save performance at the cost of animations referring 22 | // to missing keyframes breaking detection of termination 23 | this.verifyKeyframesExist = true; 24 | } 25 | 26 | /** 27 | * Add multiple listeners at once to the given element 28 | * 29 | * @param el the element to attach listeners to 30 | * @param s collection of events to bind listeners to 31 | * @param fn callback that gets executed 32 | */ 33 | _addMultipleEventListener(el: Element, s: string, fn: Function): void { 34 | let evts = s.split(' '); 35 | for (let i = 0, ii = evts.length; i < ii; ++i) { 36 | el.addEventListener(evts[i], fn, false); 37 | } 38 | } 39 | 40 | /** 41 | * Remove multiple listeners at once from the given element 42 | * 43 | * @param el the element 44 | * @param s collection of events to remove 45 | * @param fn callback to remove 46 | */ 47 | _removeMultipleEventListener(el: Element, s: string, fn: Function): void { 48 | let evts = s.split(' '); 49 | for (let i = 0, ii = evts.length; i < ii; ++i) { 50 | el.removeEventListener(evts[i], fn, false); 51 | } 52 | } 53 | 54 | /** 55 | * Vendor-prefix save method to get the animation-delay 56 | * 57 | * @param element the element to inspect 58 | * @returns animation-delay in seconds 59 | */ 60 | _getElementAnimationDelay(element: Element): number { 61 | let styl = DOM.getComputedStyle(element); 62 | let prop; 63 | let delay; 64 | 65 | if (styl.getPropertyValue('animation-delay')) { 66 | prop = 'animation-delay'; 67 | } else if (styl.getPropertyValue('-webkit-animation-delay')) { 68 | prop = '-webkit-animation-delay'; 69 | } else if (styl.getPropertyValue('-moz-animation-delay')) { 70 | prop = '-moz-animation-delay'; 71 | } else { 72 | return 0; 73 | } 74 | 75 | delay = styl.getPropertyValue(prop); 76 | delay = Number(delay.replace(/[^\d\.]/g, '')); 77 | 78 | return (delay * 1000); 79 | } 80 | 81 | /** 82 | * Vendor-prefix safe method to get the animation names 83 | * 84 | * @param element the element to inspect 85 | * @returns array of animation names 86 | */ 87 | _getElementAnimationNames(element: Element): Array { 88 | let styl = DOM.getComputedStyle(element); 89 | let prefix; 90 | 91 | if (styl.getPropertyValue('animation-name')) { 92 | prefix = ''; 93 | } else if (styl.getPropertyValue('-webkit-animation-name')) { 94 | prefix = '-webkit-'; 95 | } else if (styl.getPropertyValue('-moz-animation-name')) { 96 | prefix = '-moz-'; 97 | } else { 98 | return []; 99 | } 100 | 101 | let animationNames = styl.getPropertyValue(prefix + 'animation-name'); 102 | return animationNames ? animationNames.split(' ') : []; 103 | } 104 | 105 | /** 106 | * Run an animation for the given element with the specified className 107 | * 108 | * @param element the element to be animated 109 | * @param className the class to be added and removed 110 | * @returns {Promise} 111 | */ 112 | _performSingleAnimate(element: Element, className: string): Promise { 113 | this._triggerDOMEvent(animationEvent.animateBegin, element); 114 | 115 | return this.addClass(element, className, true) 116 | .then((result) => { 117 | this._triggerDOMEvent(animationEvent.animateActive, element); 118 | 119 | if (result !== false) { 120 | return this.removeClass(element, className, true) 121 | .then(() => { 122 | this._triggerDOMEvent(animationEvent.animateDone, element); 123 | }); 124 | } 125 | 126 | return false; 127 | }) 128 | .catch(() => { 129 | this._triggerDOMEvent(animationEvent.animateTimeout, element); 130 | }); 131 | } 132 | 133 | /** 134 | * Triggers a DOM-Event with the given type as name and adds the provided element as detail 135 | * @param eventType the event type 136 | * @param element the element to be dispatched as event detail 137 | */ 138 | _triggerDOMEvent(eventType: string, element: Element): void { 139 | let evt = DOM.createCustomEvent(eventType, { bubbles: true, cancelable: true, detail: element }); 140 | DOM.dispatchEvent(evt); 141 | } 142 | 143 | /** 144 | * Returns true if there is a new animation with valid keyframes 145 | * @param animationNames the current animation style. 146 | * @param prevAnimationNames the previous animation style 147 | * @private 148 | */ 149 | _animationChangeWithValidKeyframe(animationNames: Array, prevAnimationNames: Array): bool { 150 | let newAnimationNames = animationNames.filter(name => prevAnimationNames.indexOf(name) === -1); 151 | 152 | if (newAnimationNames.length === 0) { 153 | return false; 154 | } 155 | 156 | if (!this.verifyKeyframesExist) { 157 | return true; 158 | } 159 | 160 | const keyframesRuleType = window.CSSRule.KEYFRAMES_RULE || 161 | window.CSSRule.MOZ_KEYFRAMES_RULE || 162 | window.CSSRule.WEBKIT_KEYFRAMES_RULE; 163 | 164 | // loop through the stylesheets searching for the keyframes. no cache is 165 | // used in case of dynamic changes to the stylesheets. 166 | let styleSheets = document.styleSheets; 167 | 168 | try { 169 | for (let i = 0; i < styleSheets.length; ++i) { 170 | let cssRules = null; 171 | 172 | try { 173 | cssRules = styleSheets[i].cssRules; 174 | } catch (e) { 175 | // do nothing 176 | } 177 | 178 | if (!cssRules) { 179 | continue; 180 | } 181 | 182 | for (let j = 0; j < cssRules.length; ++j) { 183 | let cssRule = cssRules[j]; 184 | 185 | if (cssRule.type === keyframesRuleType) { 186 | if (newAnimationNames.indexOf(cssRule.name) !== -1) { 187 | return true; 188 | } 189 | } 190 | } 191 | } 192 | } catch (e) { 193 | //do nothing 194 | } 195 | 196 | return false; 197 | } 198 | 199 | /* Public API Begin */ 200 | /** 201 | * Execute a single animation. 202 | * @param element Element to animate 203 | * @param className Properties to animate or name of the effect to use. For css animators this represents the className to be added and removed right after the animation is done. 204 | * @param options options for the animation (duration, easing, ...) 205 | * @returns Resolved when the animation is done 206 | */ 207 | animate(element: Element | Array, className: string): Promise { 208 | if (Array.isArray(element)) { 209 | return Promise.all(element.map((el) => { 210 | return this._performSingleAnimate(el, className); 211 | })); 212 | } 213 | 214 | return this._performSingleAnimate(element, className); 215 | } 216 | 217 | /** 218 | * Run a sequence of animations one after the other. 219 | * @param sequence An array of effectNames or classNames 220 | * @returns Resolved when all animations are done 221 | */ 222 | runSequence(animations: Array): Promise { 223 | this._triggerDOMEvent(animationEvent.sequenceBegin, null); 224 | 225 | return animations.reduce((p, anim) => { 226 | return p.then(() => { return this.animate(anim.element, anim.className); }); 227 | }, Promise.resolve(true)).then(() => { 228 | this._triggerDOMEvent(animationEvent.sequenceDone, null); 229 | }); 230 | } 231 | 232 | /** 233 | * Animates element on enter or leave 234 | * @param element element to animate 235 | * @param direction 'enter' or 'leave' 236 | * @param doneClass class to apply when done 237 | * @private 238 | */ 239 | _stateAnim(element: Element, direction: string, doneClass: string) { 240 | const auClass = 'au-' + direction; 241 | const auClassActive = auClass + '-active'; 242 | return new Promise((resolve, reject) => { 243 | const classList = element.classList; 244 | 245 | this._triggerDOMEvent(animationEvent[direction + 'Begin'], element); 246 | 247 | // Step 1.2: remove done classes 248 | if (this.useAnimationDoneClasses) { 249 | classList.remove(this.animationEnteredClass); 250 | classList.remove(this.animationLeftClass); 251 | } 252 | 253 | // Step 2: Add animation preparation class 254 | classList.add(auClass); 255 | const prevAnimationNames = this._getElementAnimationNames(element); 256 | 257 | // Step 3: setup event to check whether animations started 258 | let animStart; 259 | let animHasStarted = false; 260 | this._addMultipleEventListener(element, 'webkitAnimationStart animationstart', animStart = (evAnimStart) => { 261 | if (evAnimStart.target !== element) { 262 | return; 263 | } 264 | animHasStarted = true; 265 | this.isAnimating = true; 266 | 267 | this._triggerDOMEvent(animationEvent[direction + 'Active'], element); 268 | 269 | // Stop event propagation, bubbling will otherwise prevent parent animation 270 | evAnimStart.stopPropagation(); 271 | 272 | evAnimStart.target.removeEventListener(evAnimStart.type, animStart); 273 | }, false); 274 | 275 | // Step 3.1: Wait for animation to finish 276 | let animEnd; 277 | this._addMultipleEventListener(element, 'webkitAnimationEnd animationend', animEnd = (evAnimEnd) => { 278 | if (!animHasStarted) { 279 | return; 280 | } 281 | if (evAnimEnd.target !== element) { 282 | return; 283 | } 284 | 285 | // Step 3.1.0: Stop event propagation, bubbling will otherwise prevent parent animation 286 | evAnimEnd.stopPropagation(); 287 | 288 | // Step 3.1.1: remove animation classes 289 | classList.remove(auClassActive); 290 | classList.remove(auClass); 291 | 292 | // Step 3.1.2 remove animationend listener 293 | evAnimEnd.target.removeEventListener(evAnimEnd.type, animEnd); 294 | 295 | // Step 3.1.3 in case animation done animations are active, add the defined done class to the element 296 | if (this.useAnimationDoneClasses && 297 | doneClass !== undefined && 298 | doneClass !== null) { 299 | classList.add(doneClass); 300 | } 301 | 302 | this.isAnimating = false; 303 | this._triggerDOMEvent(animationEvent[direction + 'Done'], element); 304 | 305 | resolve(true); 306 | }, false); 307 | 308 | // Step 4: check if parent element is defined to stagger animations otherwise trigger active immediately 309 | const parent = element.parentElement; 310 | const attrib = 'data-animator-pending' + direction; 311 | 312 | const cleanupAnimation = () => { 313 | // Step 5: if no animations scheduled cleanup animation classes 314 | const animationNames = this._getElementAnimationNames(element); 315 | if (!this._animationChangeWithValidKeyframe(animationNames, prevAnimationNames)) { 316 | classList.remove(auClassActive); 317 | classList.remove(auClass); 318 | 319 | this._removeMultipleEventListener(element, 'webkitAnimationEnd animationend', animEnd); 320 | this._removeMultipleEventListener(element, 'webkitAnimationStart animationstart', animStart); 321 | 322 | this._triggerDOMEvent(animationEvent[direction + 'Timeout'], element); 323 | resolve(false); 324 | } 325 | parent && parent.setAttribute(attrib, +(parent.getAttribute(attrib) || 1) - 1); 326 | }; 327 | 328 | if (parent !== null && parent !== undefined && 329 | (parent.classList.contains('au-stagger') || parent.classList.contains('au-stagger-' + direction))) { 330 | const offset = +(parent.getAttribute(attrib) || 0); 331 | parent.setAttribute(attrib, offset + 1); 332 | const delay = this._getElementAnimationDelay(parent) * offset; 333 | this._triggerDOMEvent(animationEvent.staggerNext, element); 334 | 335 | setTimeout(() => { 336 | classList.add(auClassActive); 337 | cleanupAnimation(); 338 | }, delay); 339 | } else { 340 | classList.add(auClassActive); 341 | cleanupAnimation(); 342 | } 343 | }); 344 | } 345 | 346 | /** 347 | * Execute an 'enter' animation on an element 348 | * @param element Element to animate 349 | * @returns Resolved when the animation is done 350 | */ 351 | enter(element: Element): Promise { 352 | return this._stateAnim(element, 'enter', this.animationEnteredClass); 353 | } 354 | 355 | /** 356 | * Execute a 'leave' animation on an element 357 | * @param element Element to animate 358 | * @returns Resolved when the animation is done 359 | */ 360 | leave(element: Element): Promise { 361 | return this._stateAnim(element, 'leave', this.animationLeftClass); 362 | } 363 | 364 | /** 365 | * Add a class to an element to trigger an animation. 366 | * @param element Element to animate 367 | * @param className Properties to animate or name of the effect to use 368 | * @param suppressEvents Indicates whether or not to suppress animation events. 369 | * @returns Resolved when the animation is done 370 | */ 371 | removeClass(element: Element, className: string, suppressEvents: boolean = false): Promise { 372 | return new Promise((resolve, reject) => { 373 | let classList = element.classList; 374 | 375 | // if neither the class exists on the element, nor is not currently being added, resolve immediately. 376 | if (!classList.contains(className) && !classList.contains(className + '-add')) { 377 | resolve(false); 378 | return; 379 | } 380 | 381 | if (suppressEvents !== true) { 382 | this._triggerDOMEvent(animationEvent.removeClassBegin, element); 383 | } 384 | 385 | // Step 1: If the 'addClass' animation is in progress, finish it prematurely. 386 | if (classList.contains(className + '-add')) { 387 | classList.remove(className + '-add'); 388 | classList.add(className); 389 | } 390 | 391 | // Step 2: Remove final className, so animation can start 392 | classList.remove(className); 393 | let prevAnimationNames = this._getElementAnimationNames(element); 394 | 395 | // Step 3: setup event to check whether animations started 396 | let animStart; 397 | let animHasStarted = false; 398 | this._addMultipleEventListener(element, 'webkitAnimationStart animationstart', animStart = (evAnimStart) => { 399 | if (evAnimStart.target !== element) { 400 | return; 401 | } 402 | animHasStarted = true; 403 | this.isAnimating = true; 404 | 405 | if (suppressEvents !== true) { 406 | this._triggerDOMEvent(animationEvent.removeClassActive, element); 407 | } 408 | 409 | // Stop event propagation, bubbling will otherwise prevent parent animation 410 | evAnimStart.stopPropagation(); 411 | 412 | evAnimStart.target.removeEventListener(evAnimStart.type, animStart); 413 | }, false); 414 | 415 | // Step 3.1: Wait for animation to finish 416 | let animEnd; 417 | this._addMultipleEventListener(element, 'webkitAnimationEnd animationend', animEnd = (evAnimEnd) => { 418 | if (!animHasStarted) { 419 | return; 420 | } 421 | if (evAnimEnd.target !== element) { 422 | return; 423 | } 424 | 425 | // Step 3.1.0: Do nothing if a new addClass animation has started and ended the removeClass animation prematurely 426 | if (!element.classList.contains(className + '-remove')) { 427 | resolve(true); 428 | } 429 | 430 | // Step 3.1.1: Stop event propagation, bubbling will otherwise prevent parent animation 431 | evAnimEnd.stopPropagation(); 432 | 433 | // Step 3.1.2: Remove the class 434 | classList.remove(className); 435 | 436 | // Step 3.1.3: Remove -remove suffixed class 437 | classList.remove(className + '-remove'); 438 | 439 | // Step 3.1.4: remove animationend listener 440 | evAnimEnd.target.removeEventListener(evAnimEnd.type, animEnd); 441 | 442 | this.isAnimating = false; 443 | 444 | if (suppressEvents !== true) { 445 | this._triggerDOMEvent(animationEvent.removeClassDone, element); 446 | } 447 | 448 | resolve(true); 449 | }, false); 450 | 451 | 452 | // Step 4: Add given className + -remove suffix to kick off animation 453 | classList.add(className + '-remove'); 454 | 455 | // Step 5: if no animations happened cleanup animation classes and remove final class 456 | let animationNames = this._getElementAnimationNames(element); 457 | if (!this._animationChangeWithValidKeyframe(animationNames, prevAnimationNames)) { 458 | classList.remove(className + '-remove'); 459 | classList.remove(className); 460 | 461 | this._removeMultipleEventListener(element, 'webkitAnimationEnd animationend', animEnd); 462 | this._removeMultipleEventListener(element, 'webkitAnimationStart animationstart', animStart); 463 | 464 | if (suppressEvents !== true) { 465 | this._triggerDOMEvent(animationEvent.removeClassTimeout, element); 466 | } 467 | 468 | resolve(false); 469 | } 470 | }); 471 | } 472 | 473 | /** 474 | * Add a class to an element to trigger an animation. 475 | * @param element Element to animate 476 | * @param className Properties to animate or name of the effect to use 477 | * @param suppressEvents Indicates whether or not to suppress animation events. 478 | * @returns Resolved when the animation is done 479 | */ 480 | addClass(element: Element, className: string, suppressEvents: boolean = false): Promise { 481 | return new Promise((resolve, reject) => { 482 | let classList = element.classList; 483 | 484 | if (suppressEvents !== true) { 485 | this._triggerDOMEvent(animationEvent.addClassBegin, element); 486 | } 487 | 488 | // Step 1: If the 'removeClass' animation is in progress, finish it prematurely. 489 | if (classList.contains(className + '-remove')) { 490 | classList.remove(className + '-remove'); 491 | classList.remove(className); 492 | } 493 | 494 | // Step 2: setup event to check whether animations started 495 | let animStart; 496 | let animHasStarted = false; 497 | this._addMultipleEventListener(element, 'webkitAnimationStart animationstart', animStart = (evAnimStart) => { 498 | if (evAnimStart.target !== element) { 499 | return; 500 | } 501 | animHasStarted = true; 502 | this.isAnimating = true; 503 | 504 | if (suppressEvents !== true) { 505 | this._triggerDOMEvent(animationEvent.addClassActive, element); 506 | } 507 | 508 | // Stop event propagation, bubbling will otherwise prevent parent animation 509 | evAnimStart.stopPropagation(); 510 | 511 | evAnimStart.target.removeEventListener(evAnimStart.type, animStart); 512 | }, false); 513 | 514 | // Step 2.1: Wait for animation to finish 515 | let animEnd; 516 | this._addMultipleEventListener(element, 'webkitAnimationEnd animationend', animEnd = (evAnimEnd) => { 517 | if (!animHasStarted) { 518 | return; 519 | } 520 | if (evAnimEnd.target !== element) { 521 | return; 522 | } 523 | 524 | // Step 2.1.0: Do nothing if a new removeClass animation has started and ended the addClass animation prematurely 525 | if (!element.classList.contains(className + '-add')) { 526 | resolve(true); 527 | } 528 | 529 | // Step 2.1.1: Stop event propagation, bubbling will otherwise prevent parent animation 530 | evAnimEnd.stopPropagation(); 531 | 532 | // Step 2.1.2: Add final className 533 | classList.add(className); 534 | 535 | // Step 2.1.3: Remove -add suffixed class 536 | classList.remove(className + '-add'); 537 | 538 | // Step 2.1.4: remove animationend listener 539 | evAnimEnd.target.removeEventListener(evAnimEnd.type, animEnd); 540 | 541 | this.isAnimating = false; 542 | 543 | if (suppressEvents !== true) { 544 | this._triggerDOMEvent(animationEvent.addClassDone, element); 545 | } 546 | 547 | resolve(true); 548 | }, false); 549 | 550 | let prevAnimationNames = this._getElementAnimationNames(element); 551 | 552 | // Step 3: Add given className + -add suffix to kick off animation 553 | classList.add(className + '-add'); 554 | 555 | // Step 4: if no animations happened cleanup animation classes and add final class 556 | let animationNames = this._getElementAnimationNames(element); 557 | if (!this._animationChangeWithValidKeyframe(animationNames, prevAnimationNames)) { 558 | classList.remove(className + '-add'); 559 | classList.add(className); 560 | 561 | this._removeMultipleEventListener(element, 'webkitAnimationEnd animationend', animEnd); 562 | this._removeMultipleEventListener(element, 'webkitAnimationStart animationstart', animStart); 563 | 564 | if (suppressEvents !== true) { 565 | this._triggerDOMEvent(animationEvent.addClassTimeout, element); 566 | } 567 | 568 | resolve(false); 569 | } 570 | }); 571 | } 572 | 573 | /* Public API End */ 574 | } 575 | 576 | /** 577 | * Configuires the CssAnimator as the default animator for Aurelia. 578 | * @param config The FrameworkConfiguration instance. 579 | * @param callback A configuration callback provided by the plugin consumer. 580 | */ 581 | export function configure(config: Object, callback?:(animator:CssAnimator) => void): void { 582 | let animator = config.container.get(CssAnimator); 583 | config.container.get(TemplatingEngine).configureAnimator(animator); 584 | if (typeof callback === 'function') { callback(animator); } 585 | } 586 | -------------------------------------------------------------------------------- /dist/commonjs/aurelia-animator-css.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.CssAnimator = undefined; 7 | exports.configure = configure; 8 | 9 | var _aureliaTemplating = require('aurelia-templating'); 10 | 11 | var _aureliaPal = require('aurelia-pal'); 12 | 13 | 14 | 15 | var CssAnimator = exports.CssAnimator = function () { 16 | function CssAnimator() { 17 | 18 | 19 | this.useAnimationDoneClasses = false; 20 | this.animationEnteredClass = 'au-entered'; 21 | this.animationLeftClass = 'au-left'; 22 | this.isAnimating = false; 23 | 24 | this.verifyKeyframesExist = true; 25 | } 26 | 27 | CssAnimator.prototype._addMultipleEventListener = function _addMultipleEventListener(el, s, fn) { 28 | var evts = s.split(' '); 29 | for (var i = 0, ii = evts.length; i < ii; ++i) { 30 | el.addEventListener(evts[i], fn, false); 31 | } 32 | }; 33 | 34 | CssAnimator.prototype._removeMultipleEventListener = function _removeMultipleEventListener(el, s, fn) { 35 | var evts = s.split(' '); 36 | for (var i = 0, ii = evts.length; i < ii; ++i) { 37 | el.removeEventListener(evts[i], fn, false); 38 | } 39 | }; 40 | 41 | CssAnimator.prototype._getElementAnimationDelay = function _getElementAnimationDelay(element) { 42 | var styl = _aureliaPal.DOM.getComputedStyle(element); 43 | var prop = void 0; 44 | var delay = void 0; 45 | 46 | if (styl.getPropertyValue('animation-delay')) { 47 | prop = 'animation-delay'; 48 | } else if (styl.getPropertyValue('-webkit-animation-delay')) { 49 | prop = '-webkit-animation-delay'; 50 | } else if (styl.getPropertyValue('-moz-animation-delay')) { 51 | prop = '-moz-animation-delay'; 52 | } else { 53 | return 0; 54 | } 55 | 56 | delay = styl.getPropertyValue(prop); 57 | delay = Number(delay.replace(/[^\d\.]/g, '')); 58 | 59 | return delay * 1000; 60 | }; 61 | 62 | CssAnimator.prototype._getElementAnimationNames = function _getElementAnimationNames(element) { 63 | var styl = _aureliaPal.DOM.getComputedStyle(element); 64 | var prefix = void 0; 65 | 66 | if (styl.getPropertyValue('animation-name')) { 67 | prefix = ''; 68 | } else if (styl.getPropertyValue('-webkit-animation-name')) { 69 | prefix = '-webkit-'; 70 | } else if (styl.getPropertyValue('-moz-animation-name')) { 71 | prefix = '-moz-'; 72 | } else { 73 | return []; 74 | } 75 | 76 | var animationNames = styl.getPropertyValue(prefix + 'animation-name'); 77 | return animationNames ? animationNames.split(' ') : []; 78 | }; 79 | 80 | CssAnimator.prototype._performSingleAnimate = function _performSingleAnimate(element, className) { 81 | var _this = this; 82 | 83 | this._triggerDOMEvent(_aureliaTemplating.animationEvent.animateBegin, element); 84 | 85 | return this.addClass(element, className, true).then(function (result) { 86 | _this._triggerDOMEvent(_aureliaTemplating.animationEvent.animateActive, element); 87 | 88 | if (result !== false) { 89 | return _this.removeClass(element, className, true).then(function () { 90 | _this._triggerDOMEvent(_aureliaTemplating.animationEvent.animateDone, element); 91 | }); 92 | } 93 | 94 | return false; 95 | }).catch(function () { 96 | _this._triggerDOMEvent(_aureliaTemplating.animationEvent.animateTimeout, element); 97 | }); 98 | }; 99 | 100 | CssAnimator.prototype._triggerDOMEvent = function _triggerDOMEvent(eventType, element) { 101 | var evt = _aureliaPal.DOM.createCustomEvent(eventType, { bubbles: true, cancelable: true, detail: element }); 102 | _aureliaPal.DOM.dispatchEvent(evt); 103 | }; 104 | 105 | CssAnimator.prototype._animationChangeWithValidKeyframe = function _animationChangeWithValidKeyframe(animationNames, prevAnimationNames) { 106 | var newAnimationNames = animationNames.filter(function (name) { 107 | return prevAnimationNames.indexOf(name) === -1; 108 | }); 109 | 110 | if (newAnimationNames.length === 0) { 111 | return false; 112 | } 113 | 114 | if (!this.verifyKeyframesExist) { 115 | return true; 116 | } 117 | 118 | var keyframesRuleType = window.CSSRule.KEYFRAMES_RULE || window.CSSRule.MOZ_KEYFRAMES_RULE || window.CSSRule.WEBKIT_KEYFRAMES_RULE; 119 | 120 | var styleSheets = document.styleSheets; 121 | 122 | try { 123 | for (var i = 0; i < styleSheets.length; ++i) { 124 | var cssRules = null; 125 | 126 | try { 127 | cssRules = styleSheets[i].cssRules; 128 | } catch (e) {} 129 | 130 | if (!cssRules) { 131 | continue; 132 | } 133 | 134 | for (var j = 0; j < cssRules.length; ++j) { 135 | var cssRule = cssRules[j]; 136 | 137 | if (cssRule.type === keyframesRuleType) { 138 | if (newAnimationNames.indexOf(cssRule.name) !== -1) { 139 | return true; 140 | } 141 | } 142 | } 143 | } 144 | } catch (e) {} 145 | 146 | return false; 147 | }; 148 | 149 | CssAnimator.prototype.animate = function animate(element, className) { 150 | var _this2 = this; 151 | 152 | if (Array.isArray(element)) { 153 | return Promise.all(element.map(function (el) { 154 | return _this2._performSingleAnimate(el, className); 155 | })); 156 | } 157 | 158 | return this._performSingleAnimate(element, className); 159 | }; 160 | 161 | CssAnimator.prototype.runSequence = function runSequence(animations) { 162 | var _this3 = this; 163 | 164 | this._triggerDOMEvent(_aureliaTemplating.animationEvent.sequenceBegin, null); 165 | 166 | return animations.reduce(function (p, anim) { 167 | return p.then(function () { 168 | return _this3.animate(anim.element, anim.className); 169 | }); 170 | }, Promise.resolve(true)).then(function () { 171 | _this3._triggerDOMEvent(_aureliaTemplating.animationEvent.sequenceDone, null); 172 | }); 173 | }; 174 | 175 | CssAnimator.prototype._stateAnim = function _stateAnim(element, direction, doneClass) { 176 | var _this4 = this; 177 | 178 | var auClass = 'au-' + direction; 179 | var auClassActive = auClass + '-active'; 180 | return new Promise(function (resolve, reject) { 181 | var classList = element.classList; 182 | 183 | _this4._triggerDOMEvent(_aureliaTemplating.animationEvent[direction + 'Begin'], element); 184 | 185 | if (_this4.useAnimationDoneClasses) { 186 | classList.remove(_this4.animationEnteredClass); 187 | classList.remove(_this4.animationLeftClass); 188 | } 189 | 190 | classList.add(auClass); 191 | var prevAnimationNames = _this4._getElementAnimationNames(element); 192 | 193 | var _animStart = void 0; 194 | var animHasStarted = false; 195 | _this4._addMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart = function animStart(evAnimStart) { 196 | if (evAnimStart.target !== element) { 197 | return; 198 | } 199 | animHasStarted = true; 200 | _this4.isAnimating = true; 201 | 202 | _this4._triggerDOMEvent(_aureliaTemplating.animationEvent[direction + 'Active'], element); 203 | 204 | evAnimStart.stopPropagation(); 205 | 206 | evAnimStart.target.removeEventListener(evAnimStart.type, _animStart); 207 | }, false); 208 | 209 | var _animEnd = void 0; 210 | _this4._addMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd = function animEnd(evAnimEnd) { 211 | if (!animHasStarted) { 212 | return; 213 | } 214 | if (evAnimEnd.target !== element) { 215 | return; 216 | } 217 | 218 | evAnimEnd.stopPropagation(); 219 | 220 | classList.remove(auClassActive); 221 | classList.remove(auClass); 222 | 223 | evAnimEnd.target.removeEventListener(evAnimEnd.type, _animEnd); 224 | 225 | if (_this4.useAnimationDoneClasses && doneClass !== undefined && doneClass !== null) { 226 | classList.add(doneClass); 227 | } 228 | 229 | _this4.isAnimating = false; 230 | _this4._triggerDOMEvent(_aureliaTemplating.animationEvent[direction + 'Done'], element); 231 | 232 | resolve(true); 233 | }, false); 234 | 235 | var parent = element.parentElement; 236 | var attrib = 'data-animator-pending' + direction; 237 | 238 | var cleanupAnimation = function cleanupAnimation() { 239 | var animationNames = _this4._getElementAnimationNames(element); 240 | if (!_this4._animationChangeWithValidKeyframe(animationNames, prevAnimationNames)) { 241 | classList.remove(auClassActive); 242 | classList.remove(auClass); 243 | 244 | _this4._removeMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd); 245 | _this4._removeMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart); 246 | 247 | _this4._triggerDOMEvent(_aureliaTemplating.animationEvent[direction + 'Timeout'], element); 248 | resolve(false); 249 | } 250 | parent && parent.setAttribute(attrib, +(parent.getAttribute(attrib) || 1) - 1); 251 | }; 252 | 253 | if (parent !== null && parent !== undefined && (parent.classList.contains('au-stagger') || parent.classList.contains('au-stagger-' + direction))) { 254 | var offset = +(parent.getAttribute(attrib) || 0); 255 | parent.setAttribute(attrib, offset + 1); 256 | var delay = _this4._getElementAnimationDelay(parent) * offset; 257 | _this4._triggerDOMEvent(_aureliaTemplating.animationEvent.staggerNext, element); 258 | 259 | setTimeout(function () { 260 | classList.add(auClassActive); 261 | cleanupAnimation(); 262 | }, delay); 263 | } else { 264 | classList.add(auClassActive); 265 | cleanupAnimation(); 266 | } 267 | }); 268 | }; 269 | 270 | CssAnimator.prototype.enter = function enter(element) { 271 | return this._stateAnim(element, 'enter', this.animationEnteredClass); 272 | }; 273 | 274 | CssAnimator.prototype.leave = function leave(element) { 275 | return this._stateAnim(element, 'leave', this.animationLeftClass); 276 | }; 277 | 278 | CssAnimator.prototype.removeClass = function removeClass(element, className) { 279 | var _this5 = this; 280 | 281 | var suppressEvents = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; 282 | 283 | return new Promise(function (resolve, reject) { 284 | var classList = element.classList; 285 | 286 | if (!classList.contains(className) && !classList.contains(className + '-add')) { 287 | resolve(false); 288 | return; 289 | } 290 | 291 | if (suppressEvents !== true) { 292 | _this5._triggerDOMEvent(_aureliaTemplating.animationEvent.removeClassBegin, element); 293 | } 294 | 295 | if (classList.contains(className + '-add')) { 296 | classList.remove(className + '-add'); 297 | classList.add(className); 298 | } 299 | 300 | classList.remove(className); 301 | var prevAnimationNames = _this5._getElementAnimationNames(element); 302 | 303 | var _animStart2 = void 0; 304 | var animHasStarted = false; 305 | _this5._addMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart2 = function animStart(evAnimStart) { 306 | if (evAnimStart.target !== element) { 307 | return; 308 | } 309 | animHasStarted = true; 310 | _this5.isAnimating = true; 311 | 312 | if (suppressEvents !== true) { 313 | _this5._triggerDOMEvent(_aureliaTemplating.animationEvent.removeClassActive, element); 314 | } 315 | 316 | evAnimStart.stopPropagation(); 317 | 318 | evAnimStart.target.removeEventListener(evAnimStart.type, _animStart2); 319 | }, false); 320 | 321 | var _animEnd2 = void 0; 322 | _this5._addMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd2 = function animEnd(evAnimEnd) { 323 | if (!animHasStarted) { 324 | return; 325 | } 326 | if (evAnimEnd.target !== element) { 327 | return; 328 | } 329 | 330 | if (!element.classList.contains(className + '-remove')) { 331 | resolve(true); 332 | } 333 | 334 | evAnimEnd.stopPropagation(); 335 | 336 | classList.remove(className); 337 | 338 | classList.remove(className + '-remove'); 339 | 340 | evAnimEnd.target.removeEventListener(evAnimEnd.type, _animEnd2); 341 | 342 | _this5.isAnimating = false; 343 | 344 | if (suppressEvents !== true) { 345 | _this5._triggerDOMEvent(_aureliaTemplating.animationEvent.removeClassDone, element); 346 | } 347 | 348 | resolve(true); 349 | }, false); 350 | 351 | classList.add(className + '-remove'); 352 | 353 | var animationNames = _this5._getElementAnimationNames(element); 354 | if (!_this5._animationChangeWithValidKeyframe(animationNames, prevAnimationNames)) { 355 | classList.remove(className + '-remove'); 356 | classList.remove(className); 357 | 358 | _this5._removeMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd2); 359 | _this5._removeMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart2); 360 | 361 | if (suppressEvents !== true) { 362 | _this5._triggerDOMEvent(_aureliaTemplating.animationEvent.removeClassTimeout, element); 363 | } 364 | 365 | resolve(false); 366 | } 367 | }); 368 | }; 369 | 370 | CssAnimator.prototype.addClass = function addClass(element, className) { 371 | var _this6 = this; 372 | 373 | var suppressEvents = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; 374 | 375 | return new Promise(function (resolve, reject) { 376 | var classList = element.classList; 377 | 378 | if (suppressEvents !== true) { 379 | _this6._triggerDOMEvent(_aureliaTemplating.animationEvent.addClassBegin, element); 380 | } 381 | 382 | if (classList.contains(className + '-remove')) { 383 | classList.remove(className + '-remove'); 384 | classList.remove(className); 385 | } 386 | 387 | var _animStart3 = void 0; 388 | var animHasStarted = false; 389 | _this6._addMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart3 = function animStart(evAnimStart) { 390 | if (evAnimStart.target !== element) { 391 | return; 392 | } 393 | animHasStarted = true; 394 | _this6.isAnimating = true; 395 | 396 | if (suppressEvents !== true) { 397 | _this6._triggerDOMEvent(_aureliaTemplating.animationEvent.addClassActive, element); 398 | } 399 | 400 | evAnimStart.stopPropagation(); 401 | 402 | evAnimStart.target.removeEventListener(evAnimStart.type, _animStart3); 403 | }, false); 404 | 405 | var _animEnd3 = void 0; 406 | _this6._addMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd3 = function animEnd(evAnimEnd) { 407 | if (!animHasStarted) { 408 | return; 409 | } 410 | if (evAnimEnd.target !== element) { 411 | return; 412 | } 413 | 414 | if (!element.classList.contains(className + '-add')) { 415 | resolve(true); 416 | } 417 | 418 | evAnimEnd.stopPropagation(); 419 | 420 | classList.add(className); 421 | 422 | classList.remove(className + '-add'); 423 | 424 | evAnimEnd.target.removeEventListener(evAnimEnd.type, _animEnd3); 425 | 426 | _this6.isAnimating = false; 427 | 428 | if (suppressEvents !== true) { 429 | _this6._triggerDOMEvent(_aureliaTemplating.animationEvent.addClassDone, element); 430 | } 431 | 432 | resolve(true); 433 | }, false); 434 | 435 | var prevAnimationNames = _this6._getElementAnimationNames(element); 436 | 437 | classList.add(className + '-add'); 438 | 439 | var animationNames = _this6._getElementAnimationNames(element); 440 | if (!_this6._animationChangeWithValidKeyframe(animationNames, prevAnimationNames)) { 441 | classList.remove(className + '-add'); 442 | classList.add(className); 443 | 444 | _this6._removeMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd3); 445 | _this6._removeMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart3); 446 | 447 | if (suppressEvents !== true) { 448 | _this6._triggerDOMEvent(_aureliaTemplating.animationEvent.addClassTimeout, element); 449 | } 450 | 451 | resolve(false); 452 | } 453 | }); 454 | }; 455 | 456 | return CssAnimator; 457 | }(); 458 | 459 | function configure(config, callback) { 460 | var animator = config.container.get(CssAnimator); 461 | config.container.get(_aureliaTemplating.TemplatingEngine).configureAnimator(animator); 462 | if (typeof callback === 'function') { 463 | callback(animator); 464 | } 465 | } -------------------------------------------------------------------------------- /dist/commonjs/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _aureliaAnimatorCss = require('./aurelia-animator-css'); 8 | 9 | Object.keys(_aureliaAnimatorCss).forEach(function (key) { 10 | if (key === "default" || key === "__esModule") return; 11 | Object.defineProperty(exports, key, { 12 | enumerable: true, 13 | get: function get() { 14 | return _aureliaAnimatorCss[key]; 15 | } 16 | }); 17 | }); -------------------------------------------------------------------------------- /dist/es2015/aurelia-animator-css.js: -------------------------------------------------------------------------------- 1 | import { animationEvent, TemplatingEngine } from 'aurelia-templating'; 2 | import { DOM } from 'aurelia-pal'; 3 | 4 | export let CssAnimator = class CssAnimator { 5 | constructor() { 6 | this.useAnimationDoneClasses = false; 7 | this.animationEnteredClass = 'au-entered'; 8 | this.animationLeftClass = 'au-left'; 9 | this.isAnimating = false; 10 | 11 | this.verifyKeyframesExist = true; 12 | } 13 | 14 | _addMultipleEventListener(el, s, fn) { 15 | let evts = s.split(' '); 16 | for (let i = 0, ii = evts.length; i < ii; ++i) { 17 | el.addEventListener(evts[i], fn, false); 18 | } 19 | } 20 | 21 | _removeMultipleEventListener(el, s, fn) { 22 | let evts = s.split(' '); 23 | for (let i = 0, ii = evts.length; i < ii; ++i) { 24 | el.removeEventListener(evts[i], fn, false); 25 | } 26 | } 27 | 28 | _getElementAnimationDelay(element) { 29 | let styl = DOM.getComputedStyle(element); 30 | let prop; 31 | let delay; 32 | 33 | if (styl.getPropertyValue('animation-delay')) { 34 | prop = 'animation-delay'; 35 | } else if (styl.getPropertyValue('-webkit-animation-delay')) { 36 | prop = '-webkit-animation-delay'; 37 | } else if (styl.getPropertyValue('-moz-animation-delay')) { 38 | prop = '-moz-animation-delay'; 39 | } else { 40 | return 0; 41 | } 42 | 43 | delay = styl.getPropertyValue(prop); 44 | delay = Number(delay.replace(/[^\d\.]/g, '')); 45 | 46 | return delay * 1000; 47 | } 48 | 49 | _getElementAnimationNames(element) { 50 | let styl = DOM.getComputedStyle(element); 51 | let prefix; 52 | 53 | if (styl.getPropertyValue('animation-name')) { 54 | prefix = ''; 55 | } else if (styl.getPropertyValue('-webkit-animation-name')) { 56 | prefix = '-webkit-'; 57 | } else if (styl.getPropertyValue('-moz-animation-name')) { 58 | prefix = '-moz-'; 59 | } else { 60 | return []; 61 | } 62 | 63 | let animationNames = styl.getPropertyValue(prefix + 'animation-name'); 64 | return animationNames ? animationNames.split(' ') : []; 65 | } 66 | 67 | _performSingleAnimate(element, className) { 68 | this._triggerDOMEvent(animationEvent.animateBegin, element); 69 | 70 | return this.addClass(element, className, true).then(result => { 71 | this._triggerDOMEvent(animationEvent.animateActive, element); 72 | 73 | if (result !== false) { 74 | return this.removeClass(element, className, true).then(() => { 75 | this._triggerDOMEvent(animationEvent.animateDone, element); 76 | }); 77 | } 78 | 79 | return false; 80 | }).catch(() => { 81 | this._triggerDOMEvent(animationEvent.animateTimeout, element); 82 | }); 83 | } 84 | 85 | _triggerDOMEvent(eventType, element) { 86 | let evt = DOM.createCustomEvent(eventType, { bubbles: true, cancelable: true, detail: element }); 87 | DOM.dispatchEvent(evt); 88 | } 89 | 90 | _animationChangeWithValidKeyframe(animationNames, prevAnimationNames) { 91 | let newAnimationNames = animationNames.filter(name => prevAnimationNames.indexOf(name) === -1); 92 | 93 | if (newAnimationNames.length === 0) { 94 | return false; 95 | } 96 | 97 | if (!this.verifyKeyframesExist) { 98 | return true; 99 | } 100 | 101 | const keyframesRuleType = window.CSSRule.KEYFRAMES_RULE || window.CSSRule.MOZ_KEYFRAMES_RULE || window.CSSRule.WEBKIT_KEYFRAMES_RULE; 102 | 103 | let styleSheets = document.styleSheets; 104 | 105 | try { 106 | for (let i = 0; i < styleSheets.length; ++i) { 107 | let cssRules = null; 108 | 109 | try { 110 | cssRules = styleSheets[i].cssRules; 111 | } catch (e) {} 112 | 113 | if (!cssRules) { 114 | continue; 115 | } 116 | 117 | for (let j = 0; j < cssRules.length; ++j) { 118 | let cssRule = cssRules[j]; 119 | 120 | if (cssRule.type === keyframesRuleType) { 121 | if (newAnimationNames.indexOf(cssRule.name) !== -1) { 122 | return true; 123 | } 124 | } 125 | } 126 | } 127 | } catch (e) {} 128 | 129 | return false; 130 | } 131 | 132 | animate(element, className) { 133 | if (Array.isArray(element)) { 134 | return Promise.all(element.map(el => { 135 | return this._performSingleAnimate(el, className); 136 | })); 137 | } 138 | 139 | return this._performSingleAnimate(element, className); 140 | } 141 | 142 | runSequence(animations) { 143 | this._triggerDOMEvent(animationEvent.sequenceBegin, null); 144 | 145 | return animations.reduce((p, anim) => { 146 | return p.then(() => { 147 | return this.animate(anim.element, anim.className); 148 | }); 149 | }, Promise.resolve(true)).then(() => { 150 | this._triggerDOMEvent(animationEvent.sequenceDone, null); 151 | }); 152 | } 153 | 154 | _stateAnim(element, direction, doneClass) { 155 | const auClass = 'au-' + direction; 156 | const auClassActive = auClass + '-active'; 157 | return new Promise((resolve, reject) => { 158 | const classList = element.classList; 159 | 160 | this._triggerDOMEvent(animationEvent[direction + 'Begin'], element); 161 | 162 | if (this.useAnimationDoneClasses) { 163 | classList.remove(this.animationEnteredClass); 164 | classList.remove(this.animationLeftClass); 165 | } 166 | 167 | classList.add(auClass); 168 | const prevAnimationNames = this._getElementAnimationNames(element); 169 | 170 | let animStart; 171 | let animHasStarted = false; 172 | this._addMultipleEventListener(element, 'webkitAnimationStart animationstart', animStart = evAnimStart => { 173 | if (evAnimStart.target !== element) { 174 | return; 175 | } 176 | animHasStarted = true; 177 | this.isAnimating = true; 178 | 179 | this._triggerDOMEvent(animationEvent[direction + 'Active'], element); 180 | 181 | evAnimStart.stopPropagation(); 182 | 183 | evAnimStart.target.removeEventListener(evAnimStart.type, animStart); 184 | }, false); 185 | 186 | let animEnd; 187 | this._addMultipleEventListener(element, 'webkitAnimationEnd animationend', animEnd = evAnimEnd => { 188 | if (!animHasStarted) { 189 | return; 190 | } 191 | if (evAnimEnd.target !== element) { 192 | return; 193 | } 194 | 195 | evAnimEnd.stopPropagation(); 196 | 197 | classList.remove(auClassActive); 198 | classList.remove(auClass); 199 | 200 | evAnimEnd.target.removeEventListener(evAnimEnd.type, animEnd); 201 | 202 | if (this.useAnimationDoneClasses && doneClass !== undefined && doneClass !== null) { 203 | classList.add(doneClass); 204 | } 205 | 206 | this.isAnimating = false; 207 | this._triggerDOMEvent(animationEvent[direction + 'Done'], element); 208 | 209 | resolve(true); 210 | }, false); 211 | 212 | const parent = element.parentElement; 213 | const attrib = 'data-animator-pending' + direction; 214 | 215 | const cleanupAnimation = () => { 216 | const animationNames = this._getElementAnimationNames(element); 217 | if (!this._animationChangeWithValidKeyframe(animationNames, prevAnimationNames)) { 218 | classList.remove(auClassActive); 219 | classList.remove(auClass); 220 | 221 | this._removeMultipleEventListener(element, 'webkitAnimationEnd animationend', animEnd); 222 | this._removeMultipleEventListener(element, 'webkitAnimationStart animationstart', animStart); 223 | 224 | this._triggerDOMEvent(animationEvent[direction + 'Timeout'], element); 225 | resolve(false); 226 | } 227 | parent && parent.setAttribute(attrib, +(parent.getAttribute(attrib) || 1) - 1); 228 | }; 229 | 230 | if (parent !== null && parent !== undefined && (parent.classList.contains('au-stagger') || parent.classList.contains('au-stagger-' + direction))) { 231 | const offset = +(parent.getAttribute(attrib) || 0); 232 | parent.setAttribute(attrib, offset + 1); 233 | const delay = this._getElementAnimationDelay(parent) * offset; 234 | this._triggerDOMEvent(animationEvent.staggerNext, element); 235 | 236 | setTimeout(() => { 237 | classList.add(auClassActive); 238 | cleanupAnimation(); 239 | }, delay); 240 | } else { 241 | classList.add(auClassActive); 242 | cleanupAnimation(); 243 | } 244 | }); 245 | } 246 | 247 | enter(element) { 248 | return this._stateAnim(element, 'enter', this.animationEnteredClass); 249 | } 250 | 251 | leave(element) { 252 | return this._stateAnim(element, 'leave', this.animationLeftClass); 253 | } 254 | 255 | removeClass(element, className, suppressEvents = false) { 256 | return new Promise((resolve, reject) => { 257 | let classList = element.classList; 258 | 259 | if (!classList.contains(className) && !classList.contains(className + '-add')) { 260 | resolve(false); 261 | return; 262 | } 263 | 264 | if (suppressEvents !== true) { 265 | this._triggerDOMEvent(animationEvent.removeClassBegin, element); 266 | } 267 | 268 | if (classList.contains(className + '-add')) { 269 | classList.remove(className + '-add'); 270 | classList.add(className); 271 | } 272 | 273 | classList.remove(className); 274 | let prevAnimationNames = this._getElementAnimationNames(element); 275 | 276 | let animStart; 277 | let animHasStarted = false; 278 | this._addMultipleEventListener(element, 'webkitAnimationStart animationstart', animStart = evAnimStart => { 279 | if (evAnimStart.target !== element) { 280 | return; 281 | } 282 | animHasStarted = true; 283 | this.isAnimating = true; 284 | 285 | if (suppressEvents !== true) { 286 | this._triggerDOMEvent(animationEvent.removeClassActive, element); 287 | } 288 | 289 | evAnimStart.stopPropagation(); 290 | 291 | evAnimStart.target.removeEventListener(evAnimStart.type, animStart); 292 | }, false); 293 | 294 | let animEnd; 295 | this._addMultipleEventListener(element, 'webkitAnimationEnd animationend', animEnd = evAnimEnd => { 296 | if (!animHasStarted) { 297 | return; 298 | } 299 | if (evAnimEnd.target !== element) { 300 | return; 301 | } 302 | 303 | if (!element.classList.contains(className + '-remove')) { 304 | resolve(true); 305 | } 306 | 307 | evAnimEnd.stopPropagation(); 308 | 309 | classList.remove(className); 310 | 311 | classList.remove(className + '-remove'); 312 | 313 | evAnimEnd.target.removeEventListener(evAnimEnd.type, animEnd); 314 | 315 | this.isAnimating = false; 316 | 317 | if (suppressEvents !== true) { 318 | this._triggerDOMEvent(animationEvent.removeClassDone, element); 319 | } 320 | 321 | resolve(true); 322 | }, false); 323 | 324 | classList.add(className + '-remove'); 325 | 326 | let animationNames = this._getElementAnimationNames(element); 327 | if (!this._animationChangeWithValidKeyframe(animationNames, prevAnimationNames)) { 328 | classList.remove(className + '-remove'); 329 | classList.remove(className); 330 | 331 | this._removeMultipleEventListener(element, 'webkitAnimationEnd animationend', animEnd); 332 | this._removeMultipleEventListener(element, 'webkitAnimationStart animationstart', animStart); 333 | 334 | if (suppressEvents !== true) { 335 | this._triggerDOMEvent(animationEvent.removeClassTimeout, element); 336 | } 337 | 338 | resolve(false); 339 | } 340 | }); 341 | } 342 | 343 | addClass(element, className, suppressEvents = false) { 344 | return new Promise((resolve, reject) => { 345 | let classList = element.classList; 346 | 347 | if (suppressEvents !== true) { 348 | this._triggerDOMEvent(animationEvent.addClassBegin, element); 349 | } 350 | 351 | if (classList.contains(className + '-remove')) { 352 | classList.remove(className + '-remove'); 353 | classList.remove(className); 354 | } 355 | 356 | let animStart; 357 | let animHasStarted = false; 358 | this._addMultipleEventListener(element, 'webkitAnimationStart animationstart', animStart = evAnimStart => { 359 | if (evAnimStart.target !== element) { 360 | return; 361 | } 362 | animHasStarted = true; 363 | this.isAnimating = true; 364 | 365 | if (suppressEvents !== true) { 366 | this._triggerDOMEvent(animationEvent.addClassActive, element); 367 | } 368 | 369 | evAnimStart.stopPropagation(); 370 | 371 | evAnimStart.target.removeEventListener(evAnimStart.type, animStart); 372 | }, false); 373 | 374 | let animEnd; 375 | this._addMultipleEventListener(element, 'webkitAnimationEnd animationend', animEnd = evAnimEnd => { 376 | if (!animHasStarted) { 377 | return; 378 | } 379 | if (evAnimEnd.target !== element) { 380 | return; 381 | } 382 | 383 | if (!element.classList.contains(className + '-add')) { 384 | resolve(true); 385 | } 386 | 387 | evAnimEnd.stopPropagation(); 388 | 389 | classList.add(className); 390 | 391 | classList.remove(className + '-add'); 392 | 393 | evAnimEnd.target.removeEventListener(evAnimEnd.type, animEnd); 394 | 395 | this.isAnimating = false; 396 | 397 | if (suppressEvents !== true) { 398 | this._triggerDOMEvent(animationEvent.addClassDone, element); 399 | } 400 | 401 | resolve(true); 402 | }, false); 403 | 404 | let prevAnimationNames = this._getElementAnimationNames(element); 405 | 406 | classList.add(className + '-add'); 407 | 408 | let animationNames = this._getElementAnimationNames(element); 409 | if (!this._animationChangeWithValidKeyframe(animationNames, prevAnimationNames)) { 410 | classList.remove(className + '-add'); 411 | classList.add(className); 412 | 413 | this._removeMultipleEventListener(element, 'webkitAnimationEnd animationend', animEnd); 414 | this._removeMultipleEventListener(element, 'webkitAnimationStart animationstart', animStart); 415 | 416 | if (suppressEvents !== true) { 417 | this._triggerDOMEvent(animationEvent.addClassTimeout, element); 418 | } 419 | 420 | resolve(false); 421 | } 422 | }); 423 | } 424 | 425 | }; 426 | 427 | export function configure(config, callback) { 428 | let animator = config.container.get(CssAnimator); 429 | config.container.get(TemplatingEngine).configureAnimator(animator); 430 | if (typeof callback === 'function') { 431 | callback(animator); 432 | } 433 | } -------------------------------------------------------------------------------- /dist/es2015/index.js: -------------------------------------------------------------------------------- 1 | export * from './aurelia-animator-css'; -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from 'aurelia-animator-css/aurelia-animator-css'; -------------------------------------------------------------------------------- /dist/native-modules/aurelia-animator-css.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | import { animationEvent, TemplatingEngine } from 'aurelia-templating'; 4 | import { DOM } from 'aurelia-pal'; 5 | 6 | export var CssAnimator = function () { 7 | function CssAnimator() { 8 | 9 | 10 | this.useAnimationDoneClasses = false; 11 | this.animationEnteredClass = 'au-entered'; 12 | this.animationLeftClass = 'au-left'; 13 | this.isAnimating = false; 14 | 15 | this.verifyKeyframesExist = true; 16 | } 17 | 18 | CssAnimator.prototype._addMultipleEventListener = function _addMultipleEventListener(el, s, fn) { 19 | var evts = s.split(' '); 20 | for (var i = 0, ii = evts.length; i < ii; ++i) { 21 | el.addEventListener(evts[i], fn, false); 22 | } 23 | }; 24 | 25 | CssAnimator.prototype._removeMultipleEventListener = function _removeMultipleEventListener(el, s, fn) { 26 | var evts = s.split(' '); 27 | for (var i = 0, ii = evts.length; i < ii; ++i) { 28 | el.removeEventListener(evts[i], fn, false); 29 | } 30 | }; 31 | 32 | CssAnimator.prototype._getElementAnimationDelay = function _getElementAnimationDelay(element) { 33 | var styl = DOM.getComputedStyle(element); 34 | var prop = void 0; 35 | var delay = void 0; 36 | 37 | if (styl.getPropertyValue('animation-delay')) { 38 | prop = 'animation-delay'; 39 | } else if (styl.getPropertyValue('-webkit-animation-delay')) { 40 | prop = '-webkit-animation-delay'; 41 | } else if (styl.getPropertyValue('-moz-animation-delay')) { 42 | prop = '-moz-animation-delay'; 43 | } else { 44 | return 0; 45 | } 46 | 47 | delay = styl.getPropertyValue(prop); 48 | delay = Number(delay.replace(/[^\d\.]/g, '')); 49 | 50 | return delay * 1000; 51 | }; 52 | 53 | CssAnimator.prototype._getElementAnimationNames = function _getElementAnimationNames(element) { 54 | var styl = DOM.getComputedStyle(element); 55 | var prefix = void 0; 56 | 57 | if (styl.getPropertyValue('animation-name')) { 58 | prefix = ''; 59 | } else if (styl.getPropertyValue('-webkit-animation-name')) { 60 | prefix = '-webkit-'; 61 | } else if (styl.getPropertyValue('-moz-animation-name')) { 62 | prefix = '-moz-'; 63 | } else { 64 | return []; 65 | } 66 | 67 | var animationNames = styl.getPropertyValue(prefix + 'animation-name'); 68 | return animationNames ? animationNames.split(' ') : []; 69 | }; 70 | 71 | CssAnimator.prototype._performSingleAnimate = function _performSingleAnimate(element, className) { 72 | var _this = this; 73 | 74 | this._triggerDOMEvent(animationEvent.animateBegin, element); 75 | 76 | return this.addClass(element, className, true).then(function (result) { 77 | _this._triggerDOMEvent(animationEvent.animateActive, element); 78 | 79 | if (result !== false) { 80 | return _this.removeClass(element, className, true).then(function () { 81 | _this._triggerDOMEvent(animationEvent.animateDone, element); 82 | }); 83 | } 84 | 85 | return false; 86 | }).catch(function () { 87 | _this._triggerDOMEvent(animationEvent.animateTimeout, element); 88 | }); 89 | }; 90 | 91 | CssAnimator.prototype._triggerDOMEvent = function _triggerDOMEvent(eventType, element) { 92 | var evt = DOM.createCustomEvent(eventType, { bubbles: true, cancelable: true, detail: element }); 93 | DOM.dispatchEvent(evt); 94 | }; 95 | 96 | CssAnimator.prototype._animationChangeWithValidKeyframe = function _animationChangeWithValidKeyframe(animationNames, prevAnimationNames) { 97 | var newAnimationNames = animationNames.filter(function (name) { 98 | return prevAnimationNames.indexOf(name) === -1; 99 | }); 100 | 101 | if (newAnimationNames.length === 0) { 102 | return false; 103 | } 104 | 105 | if (!this.verifyKeyframesExist) { 106 | return true; 107 | } 108 | 109 | var keyframesRuleType = window.CSSRule.KEYFRAMES_RULE || window.CSSRule.MOZ_KEYFRAMES_RULE || window.CSSRule.WEBKIT_KEYFRAMES_RULE; 110 | 111 | var styleSheets = document.styleSheets; 112 | 113 | try { 114 | for (var i = 0; i < styleSheets.length; ++i) { 115 | var cssRules = null; 116 | 117 | try { 118 | cssRules = styleSheets[i].cssRules; 119 | } catch (e) {} 120 | 121 | if (!cssRules) { 122 | continue; 123 | } 124 | 125 | for (var j = 0; j < cssRules.length; ++j) { 126 | var cssRule = cssRules[j]; 127 | 128 | if (cssRule.type === keyframesRuleType) { 129 | if (newAnimationNames.indexOf(cssRule.name) !== -1) { 130 | return true; 131 | } 132 | } 133 | } 134 | } 135 | } catch (e) {} 136 | 137 | return false; 138 | }; 139 | 140 | CssAnimator.prototype.animate = function animate(element, className) { 141 | var _this2 = this; 142 | 143 | if (Array.isArray(element)) { 144 | return Promise.all(element.map(function (el) { 145 | return _this2._performSingleAnimate(el, className); 146 | })); 147 | } 148 | 149 | return this._performSingleAnimate(element, className); 150 | }; 151 | 152 | CssAnimator.prototype.runSequence = function runSequence(animations) { 153 | var _this3 = this; 154 | 155 | this._triggerDOMEvent(animationEvent.sequenceBegin, null); 156 | 157 | return animations.reduce(function (p, anim) { 158 | return p.then(function () { 159 | return _this3.animate(anim.element, anim.className); 160 | }); 161 | }, Promise.resolve(true)).then(function () { 162 | _this3._triggerDOMEvent(animationEvent.sequenceDone, null); 163 | }); 164 | }; 165 | 166 | CssAnimator.prototype._stateAnim = function _stateAnim(element, direction, doneClass) { 167 | var _this4 = this; 168 | 169 | var auClass = 'au-' + direction; 170 | var auClassActive = auClass + '-active'; 171 | return new Promise(function (resolve, reject) { 172 | var classList = element.classList; 173 | 174 | _this4._triggerDOMEvent(animationEvent[direction + 'Begin'], element); 175 | 176 | if (_this4.useAnimationDoneClasses) { 177 | classList.remove(_this4.animationEnteredClass); 178 | classList.remove(_this4.animationLeftClass); 179 | } 180 | 181 | classList.add(auClass); 182 | var prevAnimationNames = _this4._getElementAnimationNames(element); 183 | 184 | var _animStart = void 0; 185 | var animHasStarted = false; 186 | _this4._addMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart = function animStart(evAnimStart) { 187 | if (evAnimStart.target !== element) { 188 | return; 189 | } 190 | animHasStarted = true; 191 | _this4.isAnimating = true; 192 | 193 | _this4._triggerDOMEvent(animationEvent[direction + 'Active'], element); 194 | 195 | evAnimStart.stopPropagation(); 196 | 197 | evAnimStart.target.removeEventListener(evAnimStart.type, _animStart); 198 | }, false); 199 | 200 | var _animEnd = void 0; 201 | _this4._addMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd = function animEnd(evAnimEnd) { 202 | if (!animHasStarted) { 203 | return; 204 | } 205 | if (evAnimEnd.target !== element) { 206 | return; 207 | } 208 | 209 | evAnimEnd.stopPropagation(); 210 | 211 | classList.remove(auClassActive); 212 | classList.remove(auClass); 213 | 214 | evAnimEnd.target.removeEventListener(evAnimEnd.type, _animEnd); 215 | 216 | if (_this4.useAnimationDoneClasses && doneClass !== undefined && doneClass !== null) { 217 | classList.add(doneClass); 218 | } 219 | 220 | _this4.isAnimating = false; 221 | _this4._triggerDOMEvent(animationEvent[direction + 'Done'], element); 222 | 223 | resolve(true); 224 | }, false); 225 | 226 | var parent = element.parentElement; 227 | var attrib = 'data-animator-pending' + direction; 228 | 229 | var cleanupAnimation = function cleanupAnimation() { 230 | var animationNames = _this4._getElementAnimationNames(element); 231 | if (!_this4._animationChangeWithValidKeyframe(animationNames, prevAnimationNames)) { 232 | classList.remove(auClassActive); 233 | classList.remove(auClass); 234 | 235 | _this4._removeMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd); 236 | _this4._removeMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart); 237 | 238 | _this4._triggerDOMEvent(animationEvent[direction + 'Timeout'], element); 239 | resolve(false); 240 | } 241 | parent && parent.setAttribute(attrib, +(parent.getAttribute(attrib) || 1) - 1); 242 | }; 243 | 244 | if (parent !== null && parent !== undefined && (parent.classList.contains('au-stagger') || parent.classList.contains('au-stagger-' + direction))) { 245 | var offset = +(parent.getAttribute(attrib) || 0); 246 | parent.setAttribute(attrib, offset + 1); 247 | var delay = _this4._getElementAnimationDelay(parent) * offset; 248 | _this4._triggerDOMEvent(animationEvent.staggerNext, element); 249 | 250 | setTimeout(function () { 251 | classList.add(auClassActive); 252 | cleanupAnimation(); 253 | }, delay); 254 | } else { 255 | classList.add(auClassActive); 256 | cleanupAnimation(); 257 | } 258 | }); 259 | }; 260 | 261 | CssAnimator.prototype.enter = function enter(element) { 262 | return this._stateAnim(element, 'enter', this.animationEnteredClass); 263 | }; 264 | 265 | CssAnimator.prototype.leave = function leave(element) { 266 | return this._stateAnim(element, 'leave', this.animationLeftClass); 267 | }; 268 | 269 | CssAnimator.prototype.removeClass = function removeClass(element, className) { 270 | var _this5 = this; 271 | 272 | var suppressEvents = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; 273 | 274 | return new Promise(function (resolve, reject) { 275 | var classList = element.classList; 276 | 277 | if (!classList.contains(className) && !classList.contains(className + '-add')) { 278 | resolve(false); 279 | return; 280 | } 281 | 282 | if (suppressEvents !== true) { 283 | _this5._triggerDOMEvent(animationEvent.removeClassBegin, element); 284 | } 285 | 286 | if (classList.contains(className + '-add')) { 287 | classList.remove(className + '-add'); 288 | classList.add(className); 289 | } 290 | 291 | classList.remove(className); 292 | var prevAnimationNames = _this5._getElementAnimationNames(element); 293 | 294 | var _animStart2 = void 0; 295 | var animHasStarted = false; 296 | _this5._addMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart2 = function animStart(evAnimStart) { 297 | if (evAnimStart.target !== element) { 298 | return; 299 | } 300 | animHasStarted = true; 301 | _this5.isAnimating = true; 302 | 303 | if (suppressEvents !== true) { 304 | _this5._triggerDOMEvent(animationEvent.removeClassActive, element); 305 | } 306 | 307 | evAnimStart.stopPropagation(); 308 | 309 | evAnimStart.target.removeEventListener(evAnimStart.type, _animStart2); 310 | }, false); 311 | 312 | var _animEnd2 = void 0; 313 | _this5._addMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd2 = function animEnd(evAnimEnd) { 314 | if (!animHasStarted) { 315 | return; 316 | } 317 | if (evAnimEnd.target !== element) { 318 | return; 319 | } 320 | 321 | if (!element.classList.contains(className + '-remove')) { 322 | resolve(true); 323 | } 324 | 325 | evAnimEnd.stopPropagation(); 326 | 327 | classList.remove(className); 328 | 329 | classList.remove(className + '-remove'); 330 | 331 | evAnimEnd.target.removeEventListener(evAnimEnd.type, _animEnd2); 332 | 333 | _this5.isAnimating = false; 334 | 335 | if (suppressEvents !== true) { 336 | _this5._triggerDOMEvent(animationEvent.removeClassDone, element); 337 | } 338 | 339 | resolve(true); 340 | }, false); 341 | 342 | classList.add(className + '-remove'); 343 | 344 | var animationNames = _this5._getElementAnimationNames(element); 345 | if (!_this5._animationChangeWithValidKeyframe(animationNames, prevAnimationNames)) { 346 | classList.remove(className + '-remove'); 347 | classList.remove(className); 348 | 349 | _this5._removeMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd2); 350 | _this5._removeMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart2); 351 | 352 | if (suppressEvents !== true) { 353 | _this5._triggerDOMEvent(animationEvent.removeClassTimeout, element); 354 | } 355 | 356 | resolve(false); 357 | } 358 | }); 359 | }; 360 | 361 | CssAnimator.prototype.addClass = function addClass(element, className) { 362 | var _this6 = this; 363 | 364 | var suppressEvents = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; 365 | 366 | return new Promise(function (resolve, reject) { 367 | var classList = element.classList; 368 | 369 | if (suppressEvents !== true) { 370 | _this6._triggerDOMEvent(animationEvent.addClassBegin, element); 371 | } 372 | 373 | if (classList.contains(className + '-remove')) { 374 | classList.remove(className + '-remove'); 375 | classList.remove(className); 376 | } 377 | 378 | var _animStart3 = void 0; 379 | var animHasStarted = false; 380 | _this6._addMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart3 = function animStart(evAnimStart) { 381 | if (evAnimStart.target !== element) { 382 | return; 383 | } 384 | animHasStarted = true; 385 | _this6.isAnimating = true; 386 | 387 | if (suppressEvents !== true) { 388 | _this6._triggerDOMEvent(animationEvent.addClassActive, element); 389 | } 390 | 391 | evAnimStart.stopPropagation(); 392 | 393 | evAnimStart.target.removeEventListener(evAnimStart.type, _animStart3); 394 | }, false); 395 | 396 | var _animEnd3 = void 0; 397 | _this6._addMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd3 = function animEnd(evAnimEnd) { 398 | if (!animHasStarted) { 399 | return; 400 | } 401 | if (evAnimEnd.target !== element) { 402 | return; 403 | } 404 | 405 | if (!element.classList.contains(className + '-add')) { 406 | resolve(true); 407 | } 408 | 409 | evAnimEnd.stopPropagation(); 410 | 411 | classList.add(className); 412 | 413 | classList.remove(className + '-add'); 414 | 415 | evAnimEnd.target.removeEventListener(evAnimEnd.type, _animEnd3); 416 | 417 | _this6.isAnimating = false; 418 | 419 | if (suppressEvents !== true) { 420 | _this6._triggerDOMEvent(animationEvent.addClassDone, element); 421 | } 422 | 423 | resolve(true); 424 | }, false); 425 | 426 | var prevAnimationNames = _this6._getElementAnimationNames(element); 427 | 428 | classList.add(className + '-add'); 429 | 430 | var animationNames = _this6._getElementAnimationNames(element); 431 | if (!_this6._animationChangeWithValidKeyframe(animationNames, prevAnimationNames)) { 432 | classList.remove(className + '-add'); 433 | classList.add(className); 434 | 435 | _this6._removeMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd3); 436 | _this6._removeMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart3); 437 | 438 | if (suppressEvents !== true) { 439 | _this6._triggerDOMEvent(animationEvent.addClassTimeout, element); 440 | } 441 | 442 | resolve(false); 443 | } 444 | }); 445 | }; 446 | 447 | return CssAnimator; 448 | }(); 449 | 450 | export function configure(config, callback) { 451 | var animator = config.container.get(CssAnimator); 452 | config.container.get(TemplatingEngine).configureAnimator(animator); 453 | if (typeof callback === 'function') { 454 | callback(animator); 455 | } 456 | } -------------------------------------------------------------------------------- /dist/native-modules/index.js: -------------------------------------------------------------------------------- 1 | export * from './aurelia-animator-css'; -------------------------------------------------------------------------------- /dist/system/aurelia-animator-css.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | System.register(['aurelia-templating', 'aurelia-pal'], function (_export, _context) { 4 | "use strict"; 5 | 6 | var animationEvent, TemplatingEngine, DOM, CssAnimator; 7 | 8 | 9 | 10 | function configure(config, callback) { 11 | var animator = config.container.get(CssAnimator); 12 | config.container.get(TemplatingEngine).configureAnimator(animator); 13 | if (typeof callback === 'function') { 14 | callback(animator); 15 | } 16 | } 17 | 18 | _export('configure', configure); 19 | 20 | return { 21 | setters: [function (_aureliaTemplating) { 22 | animationEvent = _aureliaTemplating.animationEvent; 23 | TemplatingEngine = _aureliaTemplating.TemplatingEngine; 24 | }, function (_aureliaPal) { 25 | DOM = _aureliaPal.DOM; 26 | }], 27 | execute: function () { 28 | _export('CssAnimator', CssAnimator = function () { 29 | function CssAnimator() { 30 | 31 | 32 | this.useAnimationDoneClasses = false; 33 | this.animationEnteredClass = 'au-entered'; 34 | this.animationLeftClass = 'au-left'; 35 | this.isAnimating = false; 36 | 37 | this.verifyKeyframesExist = true; 38 | } 39 | 40 | CssAnimator.prototype._addMultipleEventListener = function _addMultipleEventListener(el, s, fn) { 41 | var evts = s.split(' '); 42 | for (var i = 0, ii = evts.length; i < ii; ++i) { 43 | el.addEventListener(evts[i], fn, false); 44 | } 45 | }; 46 | 47 | CssAnimator.prototype._removeMultipleEventListener = function _removeMultipleEventListener(el, s, fn) { 48 | var evts = s.split(' '); 49 | for (var i = 0, ii = evts.length; i < ii; ++i) { 50 | el.removeEventListener(evts[i], fn, false); 51 | } 52 | }; 53 | 54 | CssAnimator.prototype._getElementAnimationDelay = function _getElementAnimationDelay(element) { 55 | var styl = DOM.getComputedStyle(element); 56 | var prop = void 0; 57 | var delay = void 0; 58 | 59 | if (styl.getPropertyValue('animation-delay')) { 60 | prop = 'animation-delay'; 61 | } else if (styl.getPropertyValue('-webkit-animation-delay')) { 62 | prop = '-webkit-animation-delay'; 63 | } else if (styl.getPropertyValue('-moz-animation-delay')) { 64 | prop = '-moz-animation-delay'; 65 | } else { 66 | return 0; 67 | } 68 | 69 | delay = styl.getPropertyValue(prop); 70 | delay = Number(delay.replace(/[^\d\.]/g, '')); 71 | 72 | return delay * 1000; 73 | }; 74 | 75 | CssAnimator.prototype._getElementAnimationNames = function _getElementAnimationNames(element) { 76 | var styl = DOM.getComputedStyle(element); 77 | var prefix = void 0; 78 | 79 | if (styl.getPropertyValue('animation-name')) { 80 | prefix = ''; 81 | } else if (styl.getPropertyValue('-webkit-animation-name')) { 82 | prefix = '-webkit-'; 83 | } else if (styl.getPropertyValue('-moz-animation-name')) { 84 | prefix = '-moz-'; 85 | } else { 86 | return []; 87 | } 88 | 89 | var animationNames = styl.getPropertyValue(prefix + 'animation-name'); 90 | return animationNames ? animationNames.split(' ') : []; 91 | }; 92 | 93 | CssAnimator.prototype._performSingleAnimate = function _performSingleAnimate(element, className) { 94 | var _this = this; 95 | 96 | this._triggerDOMEvent(animationEvent.animateBegin, element); 97 | 98 | return this.addClass(element, className, true).then(function (result) { 99 | _this._triggerDOMEvent(animationEvent.animateActive, element); 100 | 101 | if (result !== false) { 102 | return _this.removeClass(element, className, true).then(function () { 103 | _this._triggerDOMEvent(animationEvent.animateDone, element); 104 | }); 105 | } 106 | 107 | return false; 108 | }).catch(function () { 109 | _this._triggerDOMEvent(animationEvent.animateTimeout, element); 110 | }); 111 | }; 112 | 113 | CssAnimator.prototype._triggerDOMEvent = function _triggerDOMEvent(eventType, element) { 114 | var evt = DOM.createCustomEvent(eventType, { bubbles: true, cancelable: true, detail: element }); 115 | DOM.dispatchEvent(evt); 116 | }; 117 | 118 | CssAnimator.prototype._animationChangeWithValidKeyframe = function _animationChangeWithValidKeyframe(animationNames, prevAnimationNames) { 119 | var newAnimationNames = animationNames.filter(function (name) { 120 | return prevAnimationNames.indexOf(name) === -1; 121 | }); 122 | 123 | if (newAnimationNames.length === 0) { 124 | return false; 125 | } 126 | 127 | if (!this.verifyKeyframesExist) { 128 | return true; 129 | } 130 | 131 | var keyframesRuleType = window.CSSRule.KEYFRAMES_RULE || window.CSSRule.MOZ_KEYFRAMES_RULE || window.CSSRule.WEBKIT_KEYFRAMES_RULE; 132 | 133 | var styleSheets = document.styleSheets; 134 | 135 | try { 136 | for (var i = 0; i < styleSheets.length; ++i) { 137 | var cssRules = null; 138 | 139 | try { 140 | cssRules = styleSheets[i].cssRules; 141 | } catch (e) {} 142 | 143 | if (!cssRules) { 144 | continue; 145 | } 146 | 147 | for (var j = 0; j < cssRules.length; ++j) { 148 | var cssRule = cssRules[j]; 149 | 150 | if (cssRule.type === keyframesRuleType) { 151 | if (newAnimationNames.indexOf(cssRule.name) !== -1) { 152 | return true; 153 | } 154 | } 155 | } 156 | } 157 | } catch (e) {} 158 | 159 | return false; 160 | }; 161 | 162 | CssAnimator.prototype.animate = function animate(element, className) { 163 | var _this2 = this; 164 | 165 | if (Array.isArray(element)) { 166 | return Promise.all(element.map(function (el) { 167 | return _this2._performSingleAnimate(el, className); 168 | })); 169 | } 170 | 171 | return this._performSingleAnimate(element, className); 172 | }; 173 | 174 | CssAnimator.prototype.runSequence = function runSequence(animations) { 175 | var _this3 = this; 176 | 177 | this._triggerDOMEvent(animationEvent.sequenceBegin, null); 178 | 179 | return animations.reduce(function (p, anim) { 180 | return p.then(function () { 181 | return _this3.animate(anim.element, anim.className); 182 | }); 183 | }, Promise.resolve(true)).then(function () { 184 | _this3._triggerDOMEvent(animationEvent.sequenceDone, null); 185 | }); 186 | }; 187 | 188 | CssAnimator.prototype._stateAnim = function _stateAnim(element, direction, doneClass) { 189 | var _this4 = this; 190 | 191 | var auClass = 'au-' + direction; 192 | var auClassActive = auClass + '-active'; 193 | return new Promise(function (resolve, reject) { 194 | var classList = element.classList; 195 | 196 | _this4._triggerDOMEvent(animationEvent[direction + 'Begin'], element); 197 | 198 | if (_this4.useAnimationDoneClasses) { 199 | classList.remove(_this4.animationEnteredClass); 200 | classList.remove(_this4.animationLeftClass); 201 | } 202 | 203 | classList.add(auClass); 204 | var prevAnimationNames = _this4._getElementAnimationNames(element); 205 | 206 | var _animStart = void 0; 207 | var animHasStarted = false; 208 | _this4._addMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart = function animStart(evAnimStart) { 209 | if (evAnimStart.target !== element) { 210 | return; 211 | } 212 | animHasStarted = true; 213 | _this4.isAnimating = true; 214 | 215 | _this4._triggerDOMEvent(animationEvent[direction + 'Active'], element); 216 | 217 | evAnimStart.stopPropagation(); 218 | 219 | evAnimStart.target.removeEventListener(evAnimStart.type, _animStart); 220 | }, false); 221 | 222 | var _animEnd = void 0; 223 | _this4._addMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd = function animEnd(evAnimEnd) { 224 | if (!animHasStarted) { 225 | return; 226 | } 227 | if (evAnimEnd.target !== element) { 228 | return; 229 | } 230 | 231 | evAnimEnd.stopPropagation(); 232 | 233 | classList.remove(auClassActive); 234 | classList.remove(auClass); 235 | 236 | evAnimEnd.target.removeEventListener(evAnimEnd.type, _animEnd); 237 | 238 | if (_this4.useAnimationDoneClasses && doneClass !== undefined && doneClass !== null) { 239 | classList.add(doneClass); 240 | } 241 | 242 | _this4.isAnimating = false; 243 | _this4._triggerDOMEvent(animationEvent[direction + 'Done'], element); 244 | 245 | resolve(true); 246 | }, false); 247 | 248 | var parent = element.parentElement; 249 | var attrib = 'data-animator-pending' + direction; 250 | 251 | var cleanupAnimation = function cleanupAnimation() { 252 | var animationNames = _this4._getElementAnimationNames(element); 253 | if (!_this4._animationChangeWithValidKeyframe(animationNames, prevAnimationNames)) { 254 | classList.remove(auClassActive); 255 | classList.remove(auClass); 256 | 257 | _this4._removeMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd); 258 | _this4._removeMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart); 259 | 260 | _this4._triggerDOMEvent(animationEvent[direction + 'Timeout'], element); 261 | resolve(false); 262 | } 263 | parent && parent.setAttribute(attrib, +(parent.getAttribute(attrib) || 1) - 1); 264 | }; 265 | 266 | if (parent !== null && parent !== undefined && (parent.classList.contains('au-stagger') || parent.classList.contains('au-stagger-' + direction))) { 267 | var offset = +(parent.getAttribute(attrib) || 0); 268 | parent.setAttribute(attrib, offset + 1); 269 | var delay = _this4._getElementAnimationDelay(parent) * offset; 270 | _this4._triggerDOMEvent(animationEvent.staggerNext, element); 271 | 272 | setTimeout(function () { 273 | classList.add(auClassActive); 274 | cleanupAnimation(); 275 | }, delay); 276 | } else { 277 | classList.add(auClassActive); 278 | cleanupAnimation(); 279 | } 280 | }); 281 | }; 282 | 283 | CssAnimator.prototype.enter = function enter(element) { 284 | return this._stateAnim(element, 'enter', this.animationEnteredClass); 285 | }; 286 | 287 | CssAnimator.prototype.leave = function leave(element) { 288 | return this._stateAnim(element, 'leave', this.animationLeftClass); 289 | }; 290 | 291 | CssAnimator.prototype.removeClass = function removeClass(element, className) { 292 | var _this5 = this; 293 | 294 | var suppressEvents = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; 295 | 296 | return new Promise(function (resolve, reject) { 297 | var classList = element.classList; 298 | 299 | if (!classList.contains(className) && !classList.contains(className + '-add')) { 300 | resolve(false); 301 | return; 302 | } 303 | 304 | if (suppressEvents !== true) { 305 | _this5._triggerDOMEvent(animationEvent.removeClassBegin, element); 306 | } 307 | 308 | if (classList.contains(className + '-add')) { 309 | classList.remove(className + '-add'); 310 | classList.add(className); 311 | } 312 | 313 | classList.remove(className); 314 | var prevAnimationNames = _this5._getElementAnimationNames(element); 315 | 316 | var _animStart2 = void 0; 317 | var animHasStarted = false; 318 | _this5._addMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart2 = function animStart(evAnimStart) { 319 | if (evAnimStart.target !== element) { 320 | return; 321 | } 322 | animHasStarted = true; 323 | _this5.isAnimating = true; 324 | 325 | if (suppressEvents !== true) { 326 | _this5._triggerDOMEvent(animationEvent.removeClassActive, element); 327 | } 328 | 329 | evAnimStart.stopPropagation(); 330 | 331 | evAnimStart.target.removeEventListener(evAnimStart.type, _animStart2); 332 | }, false); 333 | 334 | var _animEnd2 = void 0; 335 | _this5._addMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd2 = function animEnd(evAnimEnd) { 336 | if (!animHasStarted) { 337 | return; 338 | } 339 | if (evAnimEnd.target !== element) { 340 | return; 341 | } 342 | 343 | if (!element.classList.contains(className + '-remove')) { 344 | resolve(true); 345 | } 346 | 347 | evAnimEnd.stopPropagation(); 348 | 349 | classList.remove(className); 350 | 351 | classList.remove(className + '-remove'); 352 | 353 | evAnimEnd.target.removeEventListener(evAnimEnd.type, _animEnd2); 354 | 355 | _this5.isAnimating = false; 356 | 357 | if (suppressEvents !== true) { 358 | _this5._triggerDOMEvent(animationEvent.removeClassDone, element); 359 | } 360 | 361 | resolve(true); 362 | }, false); 363 | 364 | classList.add(className + '-remove'); 365 | 366 | var animationNames = _this5._getElementAnimationNames(element); 367 | if (!_this5._animationChangeWithValidKeyframe(animationNames, prevAnimationNames)) { 368 | classList.remove(className + '-remove'); 369 | classList.remove(className); 370 | 371 | _this5._removeMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd2); 372 | _this5._removeMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart2); 373 | 374 | if (suppressEvents !== true) { 375 | _this5._triggerDOMEvent(animationEvent.removeClassTimeout, element); 376 | } 377 | 378 | resolve(false); 379 | } 380 | }); 381 | }; 382 | 383 | CssAnimator.prototype.addClass = function addClass(element, className) { 384 | var _this6 = this; 385 | 386 | var suppressEvents = arguments.length <= 2 || arguments[2] === undefined ? false : arguments[2]; 387 | 388 | return new Promise(function (resolve, reject) { 389 | var classList = element.classList; 390 | 391 | if (suppressEvents !== true) { 392 | _this6._triggerDOMEvent(animationEvent.addClassBegin, element); 393 | } 394 | 395 | if (classList.contains(className + '-remove')) { 396 | classList.remove(className + '-remove'); 397 | classList.remove(className); 398 | } 399 | 400 | var _animStart3 = void 0; 401 | var animHasStarted = false; 402 | _this6._addMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart3 = function animStart(evAnimStart) { 403 | if (evAnimStart.target !== element) { 404 | return; 405 | } 406 | animHasStarted = true; 407 | _this6.isAnimating = true; 408 | 409 | if (suppressEvents !== true) { 410 | _this6._triggerDOMEvent(animationEvent.addClassActive, element); 411 | } 412 | 413 | evAnimStart.stopPropagation(); 414 | 415 | evAnimStart.target.removeEventListener(evAnimStart.type, _animStart3); 416 | }, false); 417 | 418 | var _animEnd3 = void 0; 419 | _this6._addMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd3 = function animEnd(evAnimEnd) { 420 | if (!animHasStarted) { 421 | return; 422 | } 423 | if (evAnimEnd.target !== element) { 424 | return; 425 | } 426 | 427 | if (!element.classList.contains(className + '-add')) { 428 | resolve(true); 429 | } 430 | 431 | evAnimEnd.stopPropagation(); 432 | 433 | classList.add(className); 434 | 435 | classList.remove(className + '-add'); 436 | 437 | evAnimEnd.target.removeEventListener(evAnimEnd.type, _animEnd3); 438 | 439 | _this6.isAnimating = false; 440 | 441 | if (suppressEvents !== true) { 442 | _this6._triggerDOMEvent(animationEvent.addClassDone, element); 443 | } 444 | 445 | resolve(true); 446 | }, false); 447 | 448 | var prevAnimationNames = _this6._getElementAnimationNames(element); 449 | 450 | classList.add(className + '-add'); 451 | 452 | var animationNames = _this6._getElementAnimationNames(element); 453 | if (!_this6._animationChangeWithValidKeyframe(animationNames, prevAnimationNames)) { 454 | classList.remove(className + '-add'); 455 | classList.add(className); 456 | 457 | _this6._removeMultipleEventListener(element, 'webkitAnimationEnd animationend', _animEnd3); 458 | _this6._removeMultipleEventListener(element, 'webkitAnimationStart animationstart', _animStart3); 459 | 460 | if (suppressEvents !== true) { 461 | _this6._triggerDOMEvent(animationEvent.addClassTimeout, element); 462 | } 463 | 464 | resolve(false); 465 | } 466 | }); 467 | }; 468 | 469 | return CssAnimator; 470 | }()); 471 | 472 | _export('CssAnimator', CssAnimator); 473 | } 474 | }; 475 | }); -------------------------------------------------------------------------------- /dist/system/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | System.register(['./aurelia-animator-css'], function (_export, _context) { 4 | "use strict"; 5 | 6 | return { 7 | setters: [function (_aureliaAnimatorCss) { 8 | var _exportObj = {}; 9 | 10 | for (var _key in _aureliaAnimatorCss) { 11 | if (_key !== "default" && key !== "__esModule") _exportObj[_key] = _aureliaAnimatorCss[_key]; 12 | } 13 | 14 | _export(_exportObj); 15 | }], 16 | execute: function () {} 17 | }; 18 | }); -------------------------------------------------------------------------------- /doc/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## [1.0.4](https://github.com/aurelia/animator-css/compare/1.0.3...v1.0.4) (2017-10-23) 3 | 4 | ### Bug Fixes 5 | 6 | * Resolve a race condition with addClass/removeClass 7 | * Prematurely ends animations if their opposite is triggered 8 | 9 | 10 | ## [1.0.3](https://github.com/aurelia/animator-css/compare/1.0.2...v1.0.3) (2017-10-02) 11 | 12 | 13 | ### Bug Fixes 14 | 15 | * **events:** start and end events capture bubbles ([bb85e90](https://github.com/aurelia/animator-css/commit/bb85e90)) 16 | * **stagger:** add leave animation stagger ([#62](https://github.com/aurelia/animator-css/issues/62)) ([74904c4](https://github.com/aurelia/animator-css/commit/74904c4)) 17 | 18 | 19 | 20 | 21 | ## [1.0.2](https://github.com/aurelia/animator-css/compare/1.0.1...v1.0.2) (2017-04-05) 22 | 23 | 24 | ### Bug Fixes 25 | 26 | * **lint:** fixes linting issues ([80ce3d1](https://github.com/aurelia/animator-css/commit/80ce3d1)) 27 | * **stagger:** delay calculation ([6112d34](https://github.com/aurelia/animator-css/commit/6112d34)), closes [#55](https://github.com/aurelia/animator-css/issues/55) 28 | * **types:** switch to Element interface ([de66cf0](https://github.com/aurelia/animator-css/commit/de66cf0)) 29 | 30 | 31 | 32 | 33 | ## [1.0.1](https://github.com/aurelia/animator-css/compare/1.0.0...v1.0.1) (2016-09-05) 34 | 35 | 36 | ### Bug Fixes 37 | 38 | * **CssAnimator:** clear event handlers when there's no animation ([7683336](https://github.com/aurelia/animator-css/commit/7683336)), closes [#47](https://github.com/aurelia/animator-css/issues/47) 39 | * **cssRules:** add additional try/catch ([4e21ea8](https://github.com/aurelia/animator-css/commit/4e21ea8)), closes [#40](https://github.com/aurelia/animator-css/issues/40) 40 | 41 | 42 | 43 | 44 | # [1.0.0](https://github.com/aurelia/animator-css/compare/1.0.0-rc.1.0.0...v1.0.0) (2016-07-27) 45 | 46 | 47 | 48 | 49 | # [1.0.0-rc.1.0.0](https://github.com/aurelia/animator-css/compare/1.0.0-beta.2.0.1...v1.0.0-rc.1.0.0) (2016-06-22) 50 | 51 | 52 | 53 | ### 1.0.0-beta.1.2.1 (2016-05-10) 54 | 55 | 56 | ### 1.0.0-beta.1.2.0 (2016-03-22) 57 | 58 | 59 | #### Bug Fixes 60 | 61 | * **cssRules:** add try catch ([d66df21c](https://github.com/aurelia/animator-css/commit/d66df21c84a889d6867a20fad9639920109b5af6)) 62 | 63 | 64 | ### 1.0.0-beta.1.1.2 (2016-03-01) 65 | 66 | 67 | #### Bug Fixes 68 | 69 | * **CssAnimator:** stagger only child elements ([219b5c79](https://github.com/aurelia/animator-css/commit/219b5c791209c70b78e287dc7654eb174f1e00d2)) 70 | 71 | 72 | ### 1.0.0-beta.1.1.1 (2016-02-08) 73 | 74 | 75 | ### 1.0.0-beta.1.1.0 (2016-01-29) 76 | 77 | 78 | #### Features 79 | 80 | * **all:** update jspm meta; core-js; aurelia deps ([b6f0a247](https://github.com/aurelia/animator-css/commit/b6f0a247f73264d21f2d23f960fe7c3263bab121)) 81 | 82 | 83 | ### 1.0.0-beta.1.0.3 (2016-01-08) 84 | 85 | 86 | #### Bug Fixes 87 | 88 | * **cssRule:** fix cssrule null ([d26e0bd8](https://github.com/aurelia/animator-css/commit/d26e0bd8755bce93b5a349c3e8fb120e0551b2d3)) 89 | 90 | 91 | ### 1.0.0-beta.1.0.2 (2015-12-16) 92 | 93 | 94 | #### Bug Fixes 95 | 96 | * **npm-convention:** npm test works ([d44f2ca5](https://github.com/aurelia/animator-css/commit/d44f2ca513ce3a87ff17dbeae191c4ab0c4f28e0)) 97 | * **race-condition:** 98 | * fix for missing animationend event under stress ([f7963e3e](https://github.com/aurelia/animator-css/commit/f7963e3e76871704822408b32bd0caa5f111cd93)) 99 | * test for animation occurring without timeout ([3eefc533](https://github.com/aurelia/animator-css/commit/3eefc5334556403adb9284b9051187ba27f72939)) 100 | * **tests:** Fixed ReferenceError in two tests ([f976e0b0](https://github.com/aurelia/animator-css/commit/f976e0b09a98136d702144747b4475547c349cb8)) 101 | 102 | 103 | ## 1.0.0-beta.1.0.1 (2015-11-17) 104 | 105 | 106 | #### Bug Fixes 107 | 108 | * **classes:** fix timing issue of add and remove class ([4e756c34](https://github.com/aurelia/animator-css/commit/4e756c346e5945d75ecbe9122e8035e63edbfc8c)) 109 | * **typing:** add testcase for timing issue ([893059a8](https://github.com/aurelia/animator-css/commit/893059a868fab7cace7f805bf265c78d3d8fcfff)) 110 | 111 | 112 | ### 1.0.0-beta.1 (2015-11-16) 113 | 114 | 115 | ## 0.18.0 (2015-11-10) 116 | 117 | 118 | #### Bug Fixes 119 | 120 | * **all:** update to use TemplatingEngine to install animator ([a06a44e9](https://github.com/aurelia/animator-css/commit/a06a44e94f1bff4546e7ad50f119fcc0c5daf5d1)) 121 | * **animator:** remove unneeded move method ([2eac6407](https://github.com/aurelia/animator-css/commit/2eac6407ad92f72997f6566e3c3011963733dc82)) 122 | 123 | 124 | ## 0.17.0 (2015-10-13) 125 | 126 | 127 | #### Bug Fixes 128 | 129 | * **CssAnimation:** add export to interface ([8fbea194](https://github.com/aurelia/animator-css/commit/8fbea194e4ea8bf070b02dd969ed0a80042b0f53)) 130 | * **all:** 131 | * update to latest plugin api ([3a5210c0](https://github.com/aurelia/animator-css/commit/3a5210c0289dc3d65460b6b61f71fa88bb32c138)) 132 | * update compiler ([982f6f60](https://github.com/aurelia/animator-css/commit/982f6f60513e801672213be31a6aed2f39458fe3)) 133 | * **build:** 134 | * update linting, testing and tools ([8a14252d](https://github.com/aurelia/animator-css/commit/8a14252db8dcd28cde743f33f291a03949e5e1d7)) 135 | * add built files for jspm registry ([a136d93c](https://github.com/aurelia/animator-css/commit/a136d93c384d5674db7a51fa3d9979a4dbf3cd18)) 136 | * **deps:** 137 | * Checkin missing build ([87504049](https://github.com/aurelia/animator-css/commit/87504049e2535ad74c40526327b2942b74e3ab90)) 138 | * Update dependency, fix CssAnimator import ([a0f9ebf3](https://github.com/aurelia/animator-css/commit/a0f9ebf3c23a5b1929d67147d74be97ecd5addb9)) 139 | * **index:** update to new framework configuration api ([8930d386](https://github.com/aurelia/animator-css/commit/8930d3864a10b63d4680f1be316a124a2d4b4fdc)) 140 | * **instance:** 141 | * fix instance passing ([9236153b](https://github.com/aurelia/animator-css/commit/9236153b45e61a9e400680ef3aa2623ca1d40f1d)) 142 | * ensure callback returns DI instance ([06b20691](https://github.com/aurelia/animator-css/commit/06b20691a4ef06517671e4e6eb6035510621b322)) 143 | * **package:** 144 | * add metadata dependency ([2328ea2f](https://github.com/aurelia/animator-css/commit/2328ea2f4e9142d4f8ee5c1c6727213ea13964f3)) 145 | * update dependencies ([17e4eb9e](https://github.com/aurelia/animator-css/commit/17e4eb9efbee0cf08a753b26c751a4b390311aed)) 146 | * **stagger:** check for null/undefined on stagger parent ([cb5dfa33](https://github.com/aurelia/animator-css/commit/cb5dfa3305bd5c330690d76db65cb0ac4be549a4)) 147 | * **test:** Fixes tests and updates dependencies ([fd0bf514](https://github.com/aurelia/animator-css/commit/fd0bf514c9cd1086ead7f9ad6eb40d765b6ab58d)) 148 | * **tests:** 149 | * split up unit tests ([ba524a6d](https://github.com/aurelia/animator-css/commit/ba524a6d4dfc112d6dc20b9cbeb062249f691894)) 150 | * Fix tests and remove JSPM jquery ([22fe4de8](https://github.com/aurelia/animator-css/commit/22fe4de88e66d8b3188fd3dd7abb4078857c6088)) 151 | * **timeout:** fixes timeouts for animation triggers ([9fb1ffa6](https://github.com/aurelia/animator-css/commit/9fb1ffa60fb4d926d4e4e587529693be8ea005ce)) 152 | * **typing:** add type information ([f28fdba9](https://github.com/aurelia/animator-css/commit/f28fdba95b77fb8a162af3ea58d55ad4741b1ff9)) 153 | 154 | 155 | #### Features 156 | 157 | * **all:** 158 | * incorporate pal ([ede4dfc0](https://github.com/aurelia/animator-css/commit/ede4dfc04fd1f3b980d790062e25785528f114b6)) 159 | * add more type info ([8fd02a8f](https://github.com/aurelia/animator-css/commit/8fd02a8fa7ce5211efd1e83b1d22f1710a99e8df)) 160 | * add plugin api support ([19f4f740](https://github.com/aurelia/animator-css/commit/19f4f74054da97177e13d3e55333a5f7deddab15)) 161 | * **anim:** implements animator-css service ([83f23e16](https://github.com/aurelia/animator-css/commit/83f23e16c2cf080cc79c0434e60e6173ddb3d8a9)) 162 | * **anim-delay:** respect set animation delay ([f54e624a](https://github.com/aurelia/animator-css/commit/f54e624af40bc22526106a9617ce24cb73a000a7)) 163 | * **anim-done:** indicate when animation is done via classes ([6779c9ff](https://github.com/aurelia/animator-css/commit/6779c9ffc02d9083fd8f1bbc8da60989713ab008)) 164 | * **beta:** new methods, events and refactoring ([1bb61d76](https://github.com/aurelia/animator-css/commit/1bb61d7650aaaa62a556b80e572656e9491c08f2)) 165 | * **events:** trigger animation events ([affc3710](https://github.com/aurelia/animator-css/commit/affc3710b96fbf4d165e58cec81bbf33e3b099cf)) 166 | * **isAnimating:** indicator for whether an animation is active ([277f81bf](https://github.com/aurelia/animator-css/commit/277f81bf97d5193487c6da24ce2fd1273823cd53)) 167 | * **stagger:** 168 | * add split stagger feature ([eb27d3e6](https://github.com/aurelia/animator-css/commit/eb27d3e6e8e54bf176abbb5a04ec6734c8e81aff)) 169 | * add staggering animations ([d57d22b6](https://github.com/aurelia/animator-css/commit/d57d22b6dd6f4653c8463e27b41cedd38f7c7df3)) 170 | 171 | 172 | ## 0.16.0 (2015-09-05) 173 | 174 | 175 | #### Bug Fixes 176 | 177 | * **CssAnimation:** add export to interface ([8fbea194](https://github.com/aurelia/animator-css/commit/8fbea194e4ea8bf070b02dd969ed0a80042b0f53)) 178 | * **build:** update linting, testing and tools ([8a14252d](https://github.com/aurelia/animator-css/commit/8a14252db8dcd28cde743f33f291a03949e5e1d7)) 179 | 180 | 181 | ## 0.15.0 (2015-08-14) 182 | 183 | 184 | #### Bug Fixes 185 | 186 | * **index:** update to new framework configuration api ([8930d386](https://github.com/aurelia/animator-css/commit/8930d3864a10b63d4680f1be316a124a2d4b4fdc)) 187 | * **typing:** add type information ([f28fdba9](https://github.com/aurelia/animator-css/commit/f28fdba95b77fb8a162af3ea58d55ad4741b1ff9)) 188 | 189 | 190 | #### Features 191 | 192 | * **all:** add more type info ([8fd02a8f](https://github.com/aurelia/animator-css/commit/8fd02a8fa7ce5211efd1e83b1d22f1710a99e8df)) 193 | 194 | 195 | ### 0.14.1 (2015-07-29) 196 | 197 | 198 | #### Bug Fixes 199 | 200 | * **package:** add metadata dependency ([2328ea2f](https://github.com/aurelia/animator-css/commit/2328ea2f4e9142d4f8ee5c1c6727213ea13964f3)) 201 | * **tests:** split up unit tests ([ba524a6d](https://github.com/aurelia/animator-css/commit/ba524a6d4dfc112d6dc20b9cbeb062249f691894)) 202 | 203 | 204 | #### Features 205 | 206 | * **stagger:** add split stagger feature ([eb27d3e6](https://github.com/aurelia/animator-css/commit/eb27d3e6e8e54bf176abbb5a04ec6734c8e81aff)) 207 | 208 | 209 | ## 0.4.0 (2015-07-02) 210 | 211 | 212 | #### Features 213 | 214 | * **beta:** new methods, events and refactoring ([1bb61d76](https://github.com/aurelia/animator-css/commit/1bb61d7650aaaa62a556b80e572656e9491c08f2)) 215 | * **events:** trigger animation events ([affc3710](https://github.com/aurelia/animator-css/commit/affc3710b96fbf4d165e58cec81bbf33e3b099cf)) 216 | 217 | 218 | ### 0.3.2 (2015-06-09) 219 | 220 | 221 | #### Bug Fixes 222 | 223 | * **timeout:** fixes timeouts for animation triggers ([9fb1ffa6](https://github.com/aurelia/animator-css/commit/9fb1ffa60fb4d926d4e4e587529693be8ea005ce)) 224 | 225 | 226 | ### 0.3.1 (2015-06-09) 227 | 228 | 229 | #### Bug Fixes 230 | 231 | * **instance:** 232 | * fix instance passing ([9236153b](https://github.com/aurelia/animator-css/commit/9236153b45e61a9e400680ef3aa2623ca1d40f1d)) 233 | * ensure callback returns DI instance ([06b20691](https://github.com/aurelia/animator-css/commit/06b20691a4ef06517671e4e6eb6035510621b322)) 234 | 235 | 236 | #### Features 237 | 238 | * **anim-done:** indicate when animation is done via classes ([6779c9ff](https://github.com/aurelia/animator-css/commit/6779c9ffc02d9083fd8f1bbc8da60989713ab008)) 239 | 240 | 241 | ## 0.3.0 (2015-06-08) 242 | 243 | 244 | #### Bug Fixes 245 | 246 | * **stagger:** check for null/undefined on stagger parent ([cb5dfa33](https://github.com/aurelia/animator-css/commit/cb5dfa3305bd5c330690d76db65cb0ac4be549a4)) 247 | 248 | 249 | #### Features 250 | 251 | * **isAnimating:** indicator for whether an animation is active ([277f81bf](https://github.com/aurelia/animator-css/commit/277f81bf97d5193487c6da24ce2fd1273823cd53)) 252 | 253 | 254 | ## 0.2.0 (2015-05-01) 255 | 256 | 257 | #### Bug Fixes 258 | 259 | * **all:** update to latest plugin api ([3a5210c0](https://github.com/aurelia/animator-css/commit/3a5210c0289dc3d65460b6b61f71fa88bb32c138)) 260 | 261 | 262 | #### Features 263 | 264 | * **stagger:** add staggering animations ([d57d22b6](https://github.com/aurelia/animator-css/commit/d57d22b6dd6f4653c8463e27b41cedd38f7c7df3)) 265 | 266 | 267 | ### 0.1.0 (2015-04-13) 268 | 269 | 270 | #### Bug Fixes 271 | 272 | * **all:** update compiler ([982f6f60](https://github.com/aurelia/animator-css/commit/982f6f60513e801672213be31a6aed2f39458fe3)) 273 | * **build:** add built files for jspm registry ([a136d93c](https://github.com/aurelia/animator-css/commit/a136d93c384d5674db7a51fa3d9979a4dbf3cd18)) 274 | * **deps:** 275 | * Checkin missing build ([87504049](https://github.com/aurelia/animator-css/commit/87504049e2535ad74c40526327b2942b74e3ab90)) 276 | * Update dependency, fix CssAnimator import ([a0f9ebf3](https://github.com/aurelia/animator-css/commit/a0f9ebf3c23a5b1929d67147d74be97ecd5addb9)) 277 | * **package:** update dependencies ([17e4eb9e](https://github.com/aurelia/animator-css/commit/17e4eb9efbee0cf08a753b26c751a4b390311aed)) 278 | * **test:** Fixes tests and updates dependencies ([fd0bf514](https://github.com/aurelia/animator-css/commit/fd0bf514c9cd1086ead7f9ad6eb40d765b6ab58d)) 279 | * **tests:** Fix tests and remove JSPM jquery ([22fe4de8](https://github.com/aurelia/animator-css/commit/22fe4de88e66d8b3188fd3dd7abb4078857c6088)) 280 | 281 | 282 | #### Features 283 | 284 | * **all:** add plugin api support ([19f4f740](https://github.com/aurelia/animator-css/commit/19f4f74054da97177e13d3e55333a5f7deddab15)) 285 | * **anim:** implements animator-css service ([83f23e16](https://github.com/aurelia/animator-css/commit/83f23e16c2cf080cc79c0434e60e6173ddb3d8a9)) 286 | * **anim-delay:** respect set animation delay ([f54e624a](https://github.com/aurelia/animator-css/commit/f54e624af40bc22526106a9617ce24cb73a000a7)) 287 | -------------------------------------------------------------------------------- /doc/api.json: -------------------------------------------------------------------------------- 1 | {"name":"aurelia-animator-css","children":[{"id":5,"name":"CssAnimator","kind":128,"kindString":"Class","flags":{"isExported":true},"comment":{"shortText":"An implementation of the Animator using CSS3-Animations."},"children":[{"id":6,"name":"constructor","kind":512,"kindString":"Constructor","flags":{"isExported":true},"comment":{"shortText":"Creates an instance of CssAnimator."},"signatures":[{"id":7,"name":"new CssAnimator","kind":16384,"kindString":"Constructor signature","flags":{},"comment":{"shortText":"Creates an instance of CssAnimator."},"type":{"type":"reference","name":"CssAnimator","id":5}}]},{"id":26,"name":"addClass","kind":2048,"kindString":"Method","flags":{"isExported":true},"signatures":[{"id":27,"name":"addClass","kind":4096,"kindString":"Call signature","flags":{},"comment":{"shortText":"Add a class to an element to trigger an animation.","returns":"Resolved when the animation is done\n"},"parameters":[{"id":28,"name":"element","kind":32768,"kindString":"Parameter","flags":{},"comment":{"text":"Element to animate"},"type":{"type":"reference","name":"Element"}},{"id":29,"name":"className","kind":32768,"kindString":"Parameter","flags":{},"comment":{"text":"Properties to animate or name of the effect to use"},"type":{"type":"instrinct","name":"string"}},{"id":30,"name":"suppressEvents","kind":32768,"kindString":"Parameter","flags":{"isOptional":true},"comment":{"text":"Indicates whether or not to suppress animation events."},"type":{"type":"instrinct","name":"boolean"}}],"type":{"type":"reference","name":"Promise","typeArguments":[{"type":"instrinct","name":"boolean"}]}}]},{"id":8,"name":"animate","kind":2048,"kindString":"Method","flags":{"isExported":true},"signatures":[{"id":9,"name":"animate","kind":4096,"kindString":"Call signature","flags":{},"comment":{"shortText":"Execute a single animation.","returns":"Resolved when the animation is done\n"},"parameters":[{"id":10,"name":"element","kind":32768,"kindString":"Parameter","flags":{},"comment":{"text":"Element to animate"},"type":{"type":"union","types":[{"type":"reference","name":"Element"},{"type":"reference","name":"Array","typeArguments":[{"type":"reference","name":"Element"}]}]}},{"id":11,"name":"className","kind":32768,"kindString":"Parameter","flags":{},"comment":{"text":"Properties to animate or name of the effect to use. For css animators this represents the className to be added and removed right after the animation is done."},"type":{"type":"instrinct","name":"string"}}],"type":{"type":"reference","name":"Promise","typeArguments":[{"type":"instrinct","name":"boolean"}]}}]},{"id":15,"name":"enter","kind":2048,"kindString":"Method","flags":{"isExported":true},"signatures":[{"id":16,"name":"enter","kind":4096,"kindString":"Call signature","flags":{},"comment":{"shortText":"Execute an 'enter' animation on an element","returns":"Resolved when the animation is done\n"},"parameters":[{"id":17,"name":"element","kind":32768,"kindString":"Parameter","flags":{},"comment":{"text":"Element to animate"},"type":{"type":"reference","name":"Element"}}],"type":{"type":"reference","name":"Promise","typeArguments":[{"type":"instrinct","name":"boolean"}]}}]},{"id":18,"name":"leave","kind":2048,"kindString":"Method","flags":{"isExported":true},"signatures":[{"id":19,"name":"leave","kind":4096,"kindString":"Call signature","flags":{},"comment":{"shortText":"Execute a 'leave' animation on an element","returns":"Resolved when the animation is done\n"},"parameters":[{"id":20,"name":"element","kind":32768,"kindString":"Parameter","flags":{},"comment":{"text":"Element to animate"},"type":{"type":"reference","name":"Element"}}],"type":{"type":"reference","name":"Promise","typeArguments":[{"type":"instrinct","name":"boolean"}]}}]},{"id":21,"name":"removeClass","kind":2048,"kindString":"Method","flags":{"isExported":true},"signatures":[{"id":22,"name":"removeClass","kind":4096,"kindString":"Call signature","flags":{},"comment":{"shortText":"Add a class to an element to trigger an animation.","returns":"Resolved when the animation is done\n"},"parameters":[{"id":23,"name":"element","kind":32768,"kindString":"Parameter","flags":{},"comment":{"text":"Element to animate"},"type":{"type":"reference","name":"Element"}},{"id":24,"name":"className","kind":32768,"kindString":"Parameter","flags":{},"comment":{"text":"Properties to animate or name of the effect to use"},"type":{"type":"instrinct","name":"string"}},{"id":25,"name":"suppressEvents","kind":32768,"kindString":"Parameter","flags":{"isOptional":true},"comment":{"text":"Indicates whether or not to suppress animation events."},"type":{"type":"instrinct","name":"boolean"}}],"type":{"type":"reference","name":"Promise","typeArguments":[{"type":"instrinct","name":"boolean"}]}}]},{"id":12,"name":"runSequence","kind":2048,"kindString":"Method","flags":{"isExported":true},"signatures":[{"id":13,"name":"runSequence","kind":4096,"kindString":"Call signature","flags":{},"comment":{"shortText":"Run a sequence of animations one after the other.","returns":"Resolved when all animations are done\n"},"parameters":[{"id":14,"name":"animations","kind":32768,"kindString":"Parameter","flags":{},"type":{"type":"reference","name":"Array","typeArguments":[{"type":"reference","name":"CssAnimation","id":2}]}}],"type":{"type":"reference","name":"Promise","typeArguments":[{"type":"instrinct","name":"boolean"}]}}]}],"groups":[{"title":"Constructors","kind":512,"children":[6]},{"title":"Methods","kind":2048,"children":[26,8,15,18,21,12]}]},{"id":2,"name":"CssAnimation","kind":256,"kindString":"Interface","flags":{"isExported":true},"children":[{"id":3,"name":"className","kind":1024,"kindString":"Property","flags":{"isExported":true},"type":{"type":"instrinct","name":"string"}},{"id":4,"name":"element","kind":1024,"kindString":"Property","flags":{"isExported":true},"type":{"type":"reference","name":"Element"}}],"groups":[{"title":"Properties","kind":1024,"children":[3,4]}]},{"id":31,"name":"configure","kind":64,"kindString":"Function","flags":{"isExported":true},"signatures":[{"id":32,"name":"configure","kind":4096,"kindString":"Call signature","flags":{},"comment":{"shortText":"Configuires the CssAnimator as the default animator for Aurelia."},"parameters":[{"id":33,"name":"config","kind":32768,"kindString":"Parameter","flags":{},"comment":{"text":"The FrameworkConfiguration instance."},"type":{"type":"reference","name":"Object"}},{"id":34,"name":"callback","kind":32768,"kindString":"Parameter","flags":{"isOptional":true},"comment":{"text":"A configuration callback provided by the plugin consumer.\n"},"type":{"type":"reflection","declaration":{"id":35,"name":"__type","kind":65536,"kindString":"Type literal","flags":{},"signatures":[{"id":36,"name":"__call","kind":4096,"kindString":"Call signature","flags":{},"parameters":[{"id":37,"name":"animator","kind":32768,"kindString":"Parameter","flags":{},"type":{"type":"reference","name":"CssAnimator","id":5}}],"type":{"type":"instrinct","name":"void"}}]}}}],"type":{"type":"instrinct","name":"void"}}]}],"groups":[{"title":"Classes","kind":128,"children":[5]},{"title":"Interfaces","kind":256,"children":[2]},{"title":"Functions","kind":64,"children":[31]}]} -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | require('require-dir')('build/tasks'); 2 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Fri Dec 05 2014 16:49:29 GMT-0500 (EST) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jspm', 'jasmine-jquery', 'jasmine'], 14 | 15 | jspm: { 16 | // Edit this to your needs 17 | loadFiles: ['src/**/*.js', 'test/**/*.js'] 18 | }, 19 | 20 | // list of files / patterns to load in the browser 21 | files: [ 22 | 'node_modules/jquery/dist/jquery.min.js', 23 | 'node_modules/karma-jasmine-jquery/', 24 | { 25 | pattern: 'test/fixtures/*.html', 26 | watched: true, 27 | included: false, 28 | served: true 29 | } 30 | ], 31 | 32 | 33 | // list of files to exclude 34 | exclude: [ 35 | ], 36 | 37 | 38 | // preprocess matching files before serving them to the browser 39 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 40 | preprocessors: { 41 | 'test/**/*.js': ['babel'], 42 | 'src/**/*.js': ['babel'] 43 | }, 44 | 'babelPreprocessor': { 45 | options: { 46 | sourceMap: 'inline', 47 | presets: [ 'es2015-loose', 'stage-1'], 48 | plugins: [ 49 | 'syntax-flow', 50 | 'transform-decorators-legacy', 51 | 'transform-flow-strip-types' 52 | ] 53 | } 54 | }, 55 | 56 | // test results reporter to use 57 | // possible values: 'dots', 'progress' 58 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 59 | reporters: ['progress'], 60 | 61 | 62 | // web server port 63 | port: 9876, 64 | 65 | 66 | // enable / disable colors in the output (reporters and logs) 67 | colors: true, 68 | 69 | 70 | // level of logging 71 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 72 | logLevel: config.LOG_INFO, 73 | 74 | 75 | // enable / disable watching file and executing tests whenever any file changes 76 | autoWatch: true, 77 | 78 | 79 | // start these browsers 80 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 81 | browsers: ['Chrome'], 82 | 83 | 84 | // Continuous Integration mode 85 | // if true, Karma captures browsers, runs the tests and exits 86 | singleRun: false 87 | }); 88 | }; 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aurelia-animator-css", 3 | "version": "1.0.4", 4 | "description": "An implementation of the abstract Animator interface from templating which enables css-based animations.", 5 | "keywords": [ 6 | "aurelia", 7 | "animation", 8 | "css", 9 | "plugin" 10 | ], 11 | "homepage": "http://aurelia.io", 12 | "bugs": { 13 | "url": "https://github.com/aurelia/animator-css/issues" 14 | }, 15 | "license": "MIT", 16 | "author": "Rob Eisenberg (http://robeisenberg.com/)", 17 | "main": "dist/commonjs/aurelia-animator-css.js", 18 | "typings": "dist/aurelia-animator-css.d.ts", 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/aurelia/animator-css" 22 | }, 23 | "scripts": { 24 | "test": "gulp build && gulp test" 25 | }, 26 | "jspm": { 27 | "registry": "npm", 28 | "jspmPackage": true, 29 | "main": "aurelia-animator-css", 30 | "format": "amd", 31 | "directories": { 32 | "dist": "dist/amd" 33 | }, 34 | "peerDependencies": { 35 | "aurelia-metadata": "^1.0.0", 36 | "aurelia-pal": "^1.0.0", 37 | "aurelia-templating": "^1.0.0" 38 | }, 39 | "dependencies": { 40 | "aurelia-metadata": "^1.0.0", 41 | "aurelia-pal": "^1.0.0", 42 | "aurelia-templating": "^1.0.0" 43 | }, 44 | "devDependencies": { 45 | "aurelia-pal-browser": "^1.0.0-rc.1.0.0", 46 | "babel": "babel-core@^5.8.24", 47 | "babel-runtime": "^5.8.24", 48 | "core-js": "^2.0.3" 49 | } 50 | }, 51 | "dependencies": { 52 | "aurelia-metadata": "^1.0.0", 53 | "aurelia-pal": "^1.0.0", 54 | "aurelia-templating": "^1.0.0" 55 | }, 56 | "devDependencies": { 57 | "aurelia-tools": "^0.2.4", 58 | "babel-dts-generator": "^0.6.1", 59 | "babel-eslint": "^6.1.2", 60 | "babel-plugin-syntax-flow": "^6.8.0", 61 | "babel-plugin-transform-decorators-legacy": "^1.3.4", 62 | "babel-plugin-transform-es2015-modules-amd": "^6.8.0", 63 | "babel-plugin-transform-es2015-modules-commonjs": "^6.11.5", 64 | "babel-plugin-transform-es2015-modules-systemjs": "^6.11.6", 65 | "babel-plugin-transform-flow-strip-types": "^6.8.0", 66 | "babel-preset-es2015": "^6.9.0", 67 | "babel-preset-es2015-loose": "^7.0.0", 68 | "babel-preset-es2015-loose-native-modules": "^1.0.0", 69 | "babel-preset-stage-1": "^6.5.0", 70 | "del": "^2.2.1", 71 | "gulp": "^3.9.1", 72 | "gulp-babel": "^6.1.2", 73 | "gulp-bump": "^2.2.0", 74 | "gulp-concat": "^2.6.0", 75 | "gulp-conventional-changelog": "^1.1.0", 76 | "gulp-eslint": "^3.0.1", 77 | "gulp-ignore": "^2.0.1", 78 | "gulp-insert": "^0.5.0", 79 | "gulp-rename": "^1.2.2", 80 | "gulp-typedoc": "^2.0.0", 81 | "gulp-typedoc-extractor": "0.0.8", 82 | "gulp-typescript": "^2.13.6", 83 | "gulp-util": "^3.0.7", 84 | "jasmine-core": "^2.4.1", 85 | "jasmine-jquery": "^2.1.1", 86 | "jquery": "^3.1.0", 87 | "karma": "^1.1.2", 88 | "karma-babel-preprocessor": "^6.0.1", 89 | "karma-chrome-launcher": "^1.0.1", 90 | "karma-coverage": "^1.1.1", 91 | "karma-jasmine": "^1.0.2", 92 | "karma-jasmine-jquery": "^0.1.1", 93 | "karma-jspm": "^2.2.0", 94 | "merge2": "^1.0.2", 95 | "object.assign": "^4.0.4", 96 | "require-dir": "^0.3.0", 97 | "run-sequence": "^1.2.2", 98 | "through2": "^2.0.1", 99 | "typedoc": "^0.4.4", 100 | "typescript": "^1.9.0-dev.20160622-1.0", 101 | "vinyl": "^1.1.1", 102 | "vinyl-paths": "^2.1.0", 103 | "yargs": "^4.8.1" 104 | }, 105 | "aurelia": { 106 | "documentation": { 107 | "articles": [] 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/animator.js: -------------------------------------------------------------------------------- 1 | import { animationEvent } from 'aurelia-templating'; 2 | import { DOM } from 'aurelia-pal'; 3 | 4 | interface CssAnimation { 5 | className: string; 6 | element: Element; 7 | } 8 | 9 | /** 10 | * An implementation of the Animator using CSS3-Animations. 11 | */ 12 | export class CssAnimator { 13 | /** 14 | * Creates an instance of CssAnimator. 15 | */ 16 | constructor() { 17 | this.useAnimationDoneClasses = false; 18 | this.animationEnteredClass = 'au-entered'; 19 | this.animationLeftClass = 'au-left'; 20 | this.isAnimating = false; 21 | // toggle this on to save performance at the cost of animations referring 22 | // to missing keyframes breaking detection of termination 23 | this.verifyKeyframesExist = true; 24 | } 25 | 26 | /** 27 | * Add multiple listeners at once to the given element 28 | * 29 | * @param el the element to attach listeners to 30 | * @param s collection of events to bind listeners to 31 | * @param fn callback that gets executed 32 | */ 33 | _addMultipleEventListener(el: Element, s: string, fn: Function): void { 34 | let evts = s.split(' '); 35 | for (let i = 0, ii = evts.length; i < ii; ++i) { 36 | el.addEventListener(evts[i], fn, false); 37 | } 38 | } 39 | 40 | /** 41 | * Remove multiple listeners at once from the given element 42 | * 43 | * @param el the element 44 | * @param s collection of events to remove 45 | * @param fn callback to remove 46 | */ 47 | _removeMultipleEventListener(el: Element, s: string, fn: Function): void { 48 | let evts = s.split(' '); 49 | for (let i = 0, ii = evts.length; i < ii; ++i) { 50 | el.removeEventListener(evts[i], fn, false); 51 | } 52 | } 53 | 54 | /** 55 | * Vendor-prefix save method to get the animation-delay 56 | * 57 | * @param element the element to inspect 58 | * @returns animation-delay in seconds 59 | */ 60 | _getElementAnimationDelay(element: Element): number { 61 | let styl = DOM.getComputedStyle(element); 62 | let prop; 63 | let delay; 64 | 65 | if (styl.getPropertyValue('animation-delay')) { 66 | prop = 'animation-delay'; 67 | } else if (styl.getPropertyValue('-webkit-animation-delay')) { 68 | prop = '-webkit-animation-delay'; 69 | } else if (styl.getPropertyValue('-moz-animation-delay')) { 70 | prop = '-moz-animation-delay'; 71 | } else { 72 | return 0; 73 | } 74 | 75 | delay = styl.getPropertyValue(prop); 76 | delay = Number(delay.replace(/[^\d\.]/g, '')); 77 | 78 | return (delay * 1000); 79 | } 80 | 81 | /** 82 | * Vendor-prefix safe method to get the animation names 83 | * 84 | * @param element the element to inspect 85 | * @returns array of animation names 86 | */ 87 | _getElementAnimationNames(element: Element): Array { 88 | let styl = DOM.getComputedStyle(element); 89 | let prefix; 90 | 91 | if (styl.getPropertyValue('animation-name')) { 92 | prefix = ''; 93 | } else if (styl.getPropertyValue('-webkit-animation-name')) { 94 | prefix = '-webkit-'; 95 | } else if (styl.getPropertyValue('-moz-animation-name')) { 96 | prefix = '-moz-'; 97 | } else { 98 | return []; 99 | } 100 | 101 | let animationNames = styl.getPropertyValue(prefix + 'animation-name'); 102 | return animationNames ? animationNames.split(' ') : []; 103 | } 104 | 105 | /** 106 | * Run an animation for the given element with the specified className 107 | * 108 | * @param element the element to be animated 109 | * @param className the class to be added and removed 110 | * @returns {Promise} 111 | */ 112 | _performSingleAnimate(element: Element, className: string): Promise { 113 | this._triggerDOMEvent(animationEvent.animateBegin, element); 114 | 115 | return this.addClass(element, className, true) 116 | .then((result) => { 117 | this._triggerDOMEvent(animationEvent.animateActive, element); 118 | 119 | if (result !== false) { 120 | return this.removeClass(element, className, true) 121 | .then(() => { 122 | this._triggerDOMEvent(animationEvent.animateDone, element); 123 | }); 124 | } 125 | 126 | return false; 127 | }) 128 | .catch(() => { 129 | this._triggerDOMEvent(animationEvent.animateTimeout, element); 130 | }); 131 | } 132 | 133 | /** 134 | * Triggers a DOM-Event with the given type as name and adds the provided element as detail 135 | * @param eventType the event type 136 | * @param element the element to be dispatched as event detail 137 | */ 138 | _triggerDOMEvent(eventType: string, element: Element): void { 139 | let evt = DOM.createCustomEvent(eventType, { bubbles: true, cancelable: true, detail: element }); 140 | DOM.dispatchEvent(evt); 141 | } 142 | 143 | /** 144 | * Returns true if there is a new animation with valid keyframes 145 | * @param animationNames the current animation style. 146 | * @param prevAnimationNames the previous animation style 147 | * @private 148 | */ 149 | _animationChangeWithValidKeyframe(animationNames: Array, prevAnimationNames: Array): bool { 150 | let newAnimationNames = animationNames.filter(name => prevAnimationNames.indexOf(name) === -1); 151 | 152 | if (newAnimationNames.length === 0) { 153 | return false; 154 | } 155 | 156 | if (!this.verifyKeyframesExist) { 157 | return true; 158 | } 159 | 160 | const keyframesRuleType = window.CSSRule.KEYFRAMES_RULE || 161 | window.CSSRule.MOZ_KEYFRAMES_RULE || 162 | window.CSSRule.WEBKIT_KEYFRAMES_RULE; 163 | 164 | // loop through the stylesheets searching for the keyframes. no cache is 165 | // used in case of dynamic changes to the stylesheets. 166 | let styleSheets = document.styleSheets; 167 | 168 | try { 169 | for (let i = 0; i < styleSheets.length; ++i) { 170 | let cssRules = null; 171 | 172 | try { 173 | cssRules = styleSheets[i].cssRules; 174 | } catch (e) { 175 | // do nothing 176 | } 177 | 178 | if (!cssRules) { 179 | continue; 180 | } 181 | 182 | for (let j = 0; j < cssRules.length; ++j) { 183 | let cssRule = cssRules[j]; 184 | 185 | if (cssRule.type === keyframesRuleType) { 186 | if (newAnimationNames.indexOf(cssRule.name) !== -1) { 187 | return true; 188 | } 189 | } 190 | } 191 | } 192 | } catch (e) { 193 | //do nothing 194 | } 195 | 196 | return false; 197 | } 198 | 199 | /* Public API Begin */ 200 | /** 201 | * Execute a single animation. 202 | * @param element Element to animate 203 | * @param className Properties to animate or name of the effect to use. For css animators this represents the className to be added and removed right after the animation is done. 204 | * @param options options for the animation (duration, easing, ...) 205 | * @returns Resolved when the animation is done 206 | */ 207 | animate(element: Element | Array, className: string): Promise { 208 | if (Array.isArray(element)) { 209 | return Promise.all(element.map((el) => { 210 | return this._performSingleAnimate(el, className); 211 | })); 212 | } 213 | 214 | return this._performSingleAnimate(element, className); 215 | } 216 | 217 | /** 218 | * Run a sequence of animations one after the other. 219 | * @param sequence An array of effectNames or classNames 220 | * @returns Resolved when all animations are done 221 | */ 222 | runSequence(animations: Array): Promise { 223 | this._triggerDOMEvent(animationEvent.sequenceBegin, null); 224 | 225 | return animations.reduce((p, anim) => { 226 | return p.then(() => { return this.animate(anim.element, anim.className); }); 227 | }, Promise.resolve(true)).then(() => { 228 | this._triggerDOMEvent(animationEvent.sequenceDone, null); 229 | }); 230 | } 231 | 232 | /** 233 | * Animates element on enter or leave 234 | * @param element element to animate 235 | * @param direction 'enter' or 'leave' 236 | * @param doneClass class to apply when done 237 | * @private 238 | */ 239 | _stateAnim(element: Element, direction: string, doneClass: string) { 240 | const auClass = 'au-' + direction; 241 | const auClassActive = auClass + '-active'; 242 | return new Promise((resolve, reject) => { 243 | const classList = element.classList; 244 | 245 | this._triggerDOMEvent(animationEvent[direction + 'Begin'], element); 246 | 247 | // Step 1.2: remove done classes 248 | if (this.useAnimationDoneClasses) { 249 | classList.remove(this.animationEnteredClass); 250 | classList.remove(this.animationLeftClass); 251 | } 252 | 253 | // Step 2: Add animation preparation class 254 | classList.add(auClass); 255 | const prevAnimationNames = this._getElementAnimationNames(element); 256 | 257 | // Step 3: setup event to check whether animations started 258 | let animStart; 259 | let animHasStarted = false; 260 | this._addMultipleEventListener(element, 'webkitAnimationStart animationstart', animStart = (evAnimStart) => { 261 | if (evAnimStart.target !== element) { 262 | return; 263 | } 264 | animHasStarted = true; 265 | this.isAnimating = true; 266 | 267 | this._triggerDOMEvent(animationEvent[direction + 'Active'], element); 268 | 269 | // Stop event propagation, bubbling will otherwise prevent parent animation 270 | evAnimStart.stopPropagation(); 271 | 272 | evAnimStart.target.removeEventListener(evAnimStart.type, animStart); 273 | }, false); 274 | 275 | // Step 3.1: Wait for animation to finish 276 | let animEnd; 277 | this._addMultipleEventListener(element, 'webkitAnimationEnd animationend', animEnd = (evAnimEnd) => { 278 | if (!animHasStarted) { 279 | return; 280 | } 281 | if (evAnimEnd.target !== element) { 282 | return; 283 | } 284 | 285 | // Step 3.1.0: Stop event propagation, bubbling will otherwise prevent parent animation 286 | evAnimEnd.stopPropagation(); 287 | 288 | // Step 3.1.1: remove animation classes 289 | classList.remove(auClassActive); 290 | classList.remove(auClass); 291 | 292 | // Step 3.1.2 remove animationend listener 293 | evAnimEnd.target.removeEventListener(evAnimEnd.type, animEnd); 294 | 295 | // Step 3.1.3 in case animation done animations are active, add the defined done class to the element 296 | if (this.useAnimationDoneClasses && 297 | doneClass !== undefined && 298 | doneClass !== null) { 299 | classList.add(doneClass); 300 | } 301 | 302 | this.isAnimating = false; 303 | this._triggerDOMEvent(animationEvent[direction + 'Done'], element); 304 | 305 | resolve(true); 306 | }, false); 307 | 308 | // Step 4: check if parent element is defined to stagger animations otherwise trigger active immediately 309 | const parent = element.parentElement; 310 | const attrib = 'data-animator-pending' + direction; 311 | 312 | const cleanupAnimation = () => { 313 | // Step 5: if no animations scheduled cleanup animation classes 314 | const animationNames = this._getElementAnimationNames(element); 315 | if (!this._animationChangeWithValidKeyframe(animationNames, prevAnimationNames)) { 316 | classList.remove(auClassActive); 317 | classList.remove(auClass); 318 | 319 | this._removeMultipleEventListener(element, 'webkitAnimationEnd animationend', animEnd); 320 | this._removeMultipleEventListener(element, 'webkitAnimationStart animationstart', animStart); 321 | 322 | this._triggerDOMEvent(animationEvent[direction + 'Timeout'], element); 323 | resolve(false); 324 | } 325 | parent && parent.setAttribute(attrib, +(parent.getAttribute(attrib) || 1) - 1); 326 | }; 327 | 328 | if (parent !== null && parent !== undefined && 329 | (parent.classList.contains('au-stagger') || parent.classList.contains('au-stagger-' + direction))) { 330 | const offset = +(parent.getAttribute(attrib) || 0); 331 | parent.setAttribute(attrib, offset + 1); 332 | const delay = this._getElementAnimationDelay(parent) * offset; 333 | this._triggerDOMEvent(animationEvent.staggerNext, element); 334 | 335 | setTimeout(() => { 336 | classList.add(auClassActive); 337 | cleanupAnimation(); 338 | }, delay); 339 | } else { 340 | classList.add(auClassActive); 341 | cleanupAnimation(); 342 | } 343 | }); 344 | } 345 | 346 | /** 347 | * Execute an 'enter' animation on an element 348 | * @param element Element to animate 349 | * @returns Resolved when the animation is done 350 | */ 351 | enter(element: Element): Promise { 352 | return this._stateAnim(element, 'enter', this.animationEnteredClass); 353 | } 354 | 355 | /** 356 | * Execute a 'leave' animation on an element 357 | * @param element Element to animate 358 | * @returns Resolved when the animation is done 359 | */ 360 | leave(element: Element): Promise { 361 | return this._stateAnim(element, 'leave', this.animationLeftClass); 362 | } 363 | 364 | /** 365 | * Add a class to an element to trigger an animation. 366 | * @param element Element to animate 367 | * @param className Properties to animate or name of the effect to use 368 | * @param suppressEvents Indicates whether or not to suppress animation events. 369 | * @returns Resolved when the animation is done 370 | */ 371 | removeClass(element: Element, className: string, suppressEvents: boolean = false): Promise { 372 | return new Promise((resolve, reject) => { 373 | let classList = element.classList; 374 | 375 | // if neither the class exists on the element, nor is not currently being added, resolve immediately. 376 | if (!classList.contains(className) && !classList.contains(className + '-add')) { 377 | resolve(false); 378 | return; 379 | } 380 | 381 | if (suppressEvents !== true) { 382 | this._triggerDOMEvent(animationEvent.removeClassBegin, element); 383 | } 384 | 385 | // Step 1: If the 'addClass' animation is in progress, finish it prematurely. 386 | if (classList.contains(className + '-add')) { 387 | classList.remove(className + '-add'); 388 | classList.add(className); 389 | } 390 | 391 | // Step 2: Remove final className, so animation can start 392 | classList.remove(className); 393 | let prevAnimationNames = this._getElementAnimationNames(element); 394 | 395 | // Step 3: setup event to check whether animations started 396 | let animStart; 397 | let animHasStarted = false; 398 | this._addMultipleEventListener(element, 'webkitAnimationStart animationstart', animStart = (evAnimStart) => { 399 | if (evAnimStart.target !== element) { 400 | return; 401 | } 402 | animHasStarted = true; 403 | this.isAnimating = true; 404 | 405 | if (suppressEvents !== true) { 406 | this._triggerDOMEvent(animationEvent.removeClassActive, element); 407 | } 408 | 409 | // Stop event propagation, bubbling will otherwise prevent parent animation 410 | evAnimStart.stopPropagation(); 411 | 412 | evAnimStart.target.removeEventListener(evAnimStart.type, animStart); 413 | }, false); 414 | 415 | // Step 3.1: Wait for animation to finish 416 | let animEnd; 417 | this._addMultipleEventListener(element, 'webkitAnimationEnd animationend', animEnd = (evAnimEnd) => { 418 | if (!animHasStarted) { 419 | return; 420 | } 421 | if (evAnimEnd.target !== element) { 422 | return; 423 | } 424 | 425 | // Step 3.1.0: Do nothing if a new addClass animation has started and ended the removeClass animation prematurely 426 | if (!element.classList.contains(className + '-remove')) { 427 | resolve(true); 428 | } 429 | 430 | // Step 3.1.1: Stop event propagation, bubbling will otherwise prevent parent animation 431 | evAnimEnd.stopPropagation(); 432 | 433 | // Step 3.1.2: Remove the class 434 | classList.remove(className); 435 | 436 | // Step 3.1.3: Remove -remove suffixed class 437 | classList.remove(className + '-remove'); 438 | 439 | // Step 3.1.4: remove animationend listener 440 | evAnimEnd.target.removeEventListener(evAnimEnd.type, animEnd); 441 | 442 | this.isAnimating = false; 443 | 444 | if (suppressEvents !== true) { 445 | this._triggerDOMEvent(animationEvent.removeClassDone, element); 446 | } 447 | 448 | resolve(true); 449 | }, false); 450 | 451 | 452 | // Step 4: Add given className + -remove suffix to kick off animation 453 | classList.add(className + '-remove'); 454 | 455 | // Step 5: if no animations happened cleanup animation classes and remove final class 456 | let animationNames = this._getElementAnimationNames(element); 457 | if (!this._animationChangeWithValidKeyframe(animationNames, prevAnimationNames)) { 458 | classList.remove(className + '-remove'); 459 | classList.remove(className); 460 | 461 | this._removeMultipleEventListener(element, 'webkitAnimationEnd animationend', animEnd); 462 | this._removeMultipleEventListener(element, 'webkitAnimationStart animationstart', animStart); 463 | 464 | if (suppressEvents !== true) { 465 | this._triggerDOMEvent(animationEvent.removeClassTimeout, element); 466 | } 467 | 468 | resolve(false); 469 | } 470 | }); 471 | } 472 | 473 | /** 474 | * Add a class to an element to trigger an animation. 475 | * @param element Element to animate 476 | * @param className Properties to animate or name of the effect to use 477 | * @param suppressEvents Indicates whether or not to suppress animation events. 478 | * @returns Resolved when the animation is done 479 | */ 480 | addClass(element: Element, className: string, suppressEvents: boolean = false): Promise { 481 | return new Promise((resolve, reject) => { 482 | let classList = element.classList; 483 | 484 | if (suppressEvents !== true) { 485 | this._triggerDOMEvent(animationEvent.addClassBegin, element); 486 | } 487 | 488 | // Step 1: If the 'removeClass' animation is in progress, finish it prematurely. 489 | if (classList.contains(className + '-remove')) { 490 | classList.remove(className + '-remove'); 491 | classList.remove(className); 492 | } 493 | 494 | // Step 2: setup event to check whether animations started 495 | let animStart; 496 | let animHasStarted = false; 497 | this._addMultipleEventListener(element, 'webkitAnimationStart animationstart', animStart = (evAnimStart) => { 498 | if (evAnimStart.target !== element) { 499 | return; 500 | } 501 | animHasStarted = true; 502 | this.isAnimating = true; 503 | 504 | if (suppressEvents !== true) { 505 | this._triggerDOMEvent(animationEvent.addClassActive, element); 506 | } 507 | 508 | // Stop event propagation, bubbling will otherwise prevent parent animation 509 | evAnimStart.stopPropagation(); 510 | 511 | evAnimStart.target.removeEventListener(evAnimStart.type, animStart); 512 | }, false); 513 | 514 | // Step 2.1: Wait for animation to finish 515 | let animEnd; 516 | this._addMultipleEventListener(element, 'webkitAnimationEnd animationend', animEnd = (evAnimEnd) => { 517 | if (!animHasStarted) { 518 | return; 519 | } 520 | if (evAnimEnd.target !== element) { 521 | return; 522 | } 523 | 524 | // Step 2.1.0: Do nothing if a new removeClass animation has started and ended the addClass animation prematurely 525 | if (!element.classList.contains(className + '-add')) { 526 | resolve(true); 527 | } 528 | 529 | // Step 2.1.1: Stop event propagation, bubbling will otherwise prevent parent animation 530 | evAnimEnd.stopPropagation(); 531 | 532 | // Step 2.1.2: Add final className 533 | classList.add(className); 534 | 535 | // Step 2.1.3: Remove -add suffixed class 536 | classList.remove(className + '-add'); 537 | 538 | // Step 2.1.4: remove animationend listener 539 | evAnimEnd.target.removeEventListener(evAnimEnd.type, animEnd); 540 | 541 | this.isAnimating = false; 542 | 543 | if (suppressEvents !== true) { 544 | this._triggerDOMEvent(animationEvent.addClassDone, element); 545 | } 546 | 547 | resolve(true); 548 | }, false); 549 | 550 | let prevAnimationNames = this._getElementAnimationNames(element); 551 | 552 | // Step 3: Add given className + -add suffix to kick off animation 553 | classList.add(className + '-add'); 554 | 555 | // Step 4: if no animations happened cleanup animation classes and add final class 556 | let animationNames = this._getElementAnimationNames(element); 557 | if (!this._animationChangeWithValidKeyframe(animationNames, prevAnimationNames)) { 558 | classList.remove(className + '-add'); 559 | classList.add(className); 560 | 561 | this._removeMultipleEventListener(element, 'webkitAnimationEnd animationend', animEnd); 562 | this._removeMultipleEventListener(element, 'webkitAnimationStart animationstart', animStart); 563 | 564 | if (suppressEvents !== true) { 565 | this._triggerDOMEvent(animationEvent.addClassTimeout, element); 566 | } 567 | 568 | resolve(false); 569 | } 570 | }); 571 | } 572 | 573 | /* Public API End */ 574 | } 575 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import {TemplatingEngine} from 'aurelia-templating'; 2 | import {CssAnimator} from './animator'; 3 | 4 | /** 5 | * Configuires the CssAnimator as the default animator for Aurelia. 6 | * @param config The FrameworkConfiguration instance. 7 | * @param callback A configuration callback provided by the plugin consumer. 8 | */ 9 | export function configure(config: Object, callback?:(animator:CssAnimator) => void): void { 10 | let animator = config.container.get(CssAnimator); 11 | config.container.get(TemplatingEngine).configureAnimator(animator); 12 | if (typeof callback === 'function') { callback(animator); } 13 | } 14 | -------------------------------------------------------------------------------- /test/animate.spec.js: -------------------------------------------------------------------------------- 1 | import {CssAnimator} from '../src/animator'; 2 | import {animationEvent} from 'aurelia-templating'; 3 | import {initialize} from 'aurelia-pal-browser'; 4 | 5 | jasmine.getFixtures().fixturesPath = 'base/test/fixtures/'; 6 | 7 | describe('animator-css', () => { 8 | let sut; 9 | beforeAll(() => initialize()); 10 | beforeEach(() => { 11 | sut = new CssAnimator(); 12 | }); 13 | describe('animate function', () => { 14 | let elem; 15 | let testClass; 16 | 17 | describe('with valid keyframes', () => { 18 | beforeEach(() => { 19 | loadFixtures('animate.html'); 20 | elem = $('#animateAddAndRemove').eq(0)[0]; 21 | testClass = 'animate-test'; 22 | }); 23 | 24 | it('should add and remove a class automatically', (done) => { 25 | sut.animate(elem, testClass).then(() => { 26 | expect(sut.isAnimating).toBe(false); 27 | expect($('#animateAddAndRemove').eq(0).css('opacity')).toBe('0'); 28 | done(); 29 | }); 30 | }); 31 | 32 | it('should animate multiple elements', (done) => { 33 | let elements = $('.sequenced-items li'); 34 | 35 | sut.animate([elements.eq(0)[0], elements.eq(1)[0], elements.eq(2)[0]], testClass).then(() => { 36 | expect(sut.isAnimating).toBe(false); 37 | expect(elements.eq(0).css('opacity')).toBe('1'); 38 | expect(elements.eq(1).css('opacity')).toBe('1'); 39 | expect(elements.eq(2).css('opacity')).toBe('1'); 40 | done(); 41 | }); 42 | }); 43 | 44 | it('should not fire add/remove events', (done) => { 45 | let eventCalled = false; 46 | const listenerAdd = document.addEventListener(animationEvent.addClassBegin, () => eventCalled = true); 47 | const listenerRemove = document.addEventListener(animationEvent.removeClassBegin, () => eventCalled = true); 48 | 49 | sut.animate(elem, testClass).then(() => { 50 | expect(eventCalled).toBe(false); 51 | 52 | document.removeEventListener(animationEvent.addClassBegin, listenerAdd); 53 | document.removeEventListener(animationEvent.removeClassBegin, listenerRemove); 54 | done(); 55 | }); 56 | }); 57 | }); 58 | 59 | // missing keyframes currently break the promise animator 60 | describe('without valid keyframes', () => { 61 | beforeEach(() => { 62 | loadFixtures('animate-missing-keyframes.html'); 63 | elem = $('#animateAddAndRemove').eq(0)[0]; 64 | testClass = 'animate-test'; 65 | }); 66 | 67 | it('should add and remove a class automatically', (done) => { 68 | sut.animate(elem, testClass).then(() => { 69 | expect(sut.isAnimating).toBe(false); 70 | expect($('#animateAddAndRemove').eq(0).css('opacity')).toBe('0'); 71 | done(); 72 | }); 73 | }); 74 | }); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /test/animator.spec.js: -------------------------------------------------------------------------------- 1 | import {configure} from '../src/index'; 2 | import {CssAnimator} from '../src/animator'; 3 | import {animationEvent} from 'aurelia-templating'; 4 | import {initialize} from 'aurelia-pal-browser'; 5 | 6 | jasmine.getFixtures().fixturesPath = 'base/test/fixtures/'; 7 | 8 | describe('animator-css', () => { 9 | let sut; 10 | beforeAll(() => initialize()); 11 | beforeEach( () => { 12 | sut = new CssAnimator(); 13 | }); 14 | 15 | describe('plugin initialization', () => { 16 | let aurelia = { 17 | globalResources: () => { 18 | 19 | }, 20 | container: { 21 | registerInstance: (type, instance) => { 22 | 23 | }, 24 | get: (type) => { 25 | if (type === CssAnimator) { 26 | return new CssAnimator(); 27 | } 28 | 29 | return { 30 | configureAnimator() { 31 | 32 | } 33 | }; 34 | } 35 | } 36 | }; 37 | 38 | it('should export configure function', () => { 39 | expect(typeof configure).toBe('function'); 40 | }); 41 | 42 | it('should accept a setup callback passing back the animator instance', (done) => { 43 | const cb = (instance) => { 44 | expect(typeof instance).toBe('object'); 45 | done(); 46 | }; 47 | 48 | configure(aurelia, cb); 49 | }); 50 | 51 | it('should have animation done classes disabled by default', () => { 52 | configure(aurelia, (instance) => { 53 | expect(instance.useAnimationDoneClasses).toBe(false); 54 | }); 55 | }); 56 | }); 57 | 58 | describe('enter animation', () => { 59 | let elem; 60 | 61 | beforeEach(() => { 62 | loadFixtures('animation.html'); 63 | elem = $('.au-animate').eq(0)[0]; 64 | }); 65 | 66 | it('should return a promise', () => { 67 | const result = sut.enter(elem).catch((error) => { throw error; }); 68 | expect(result.then).toBeDefined(); 69 | }); 70 | 71 | it('should remove animation from stack after done', (done) => { 72 | const elemAnimated = $('.animated-item').eq(0)[0]; 73 | 74 | sut.enter(elemAnimated).then( () => { 75 | expect(sut.isAnimating).toBe(false); 76 | done(); 77 | }); 78 | }); 79 | 80 | it('should respect animation-delay', (done) => { 81 | const elemAnimated = $('#delayedElement').eq(0)[0]; 82 | 83 | sut.enter(elemAnimated).then( () => { 84 | expect(sut.isAnimating).toBe(false); 85 | done(); 86 | }); 87 | }); 88 | 89 | it('should kick off animation', (done) => { 90 | const elemAnimated = $('.animated-item').eq(0)[0]; 91 | 92 | sut.enter(elemAnimated).then( (didRunAnimation) => { 93 | expect(didRunAnimation).toBe(true); 94 | done(); 95 | }); 96 | }); 97 | 98 | it('should not kick off animation on element without proper classes', (done) => { 99 | const elemAnimated = $('.boring-item').eq(0)[0]; 100 | sut.enter(elemAnimated).then( (didRunAnimation) => { 101 | expect(didRunAnimation).toBe(false); 102 | done(); 103 | }); 104 | }); 105 | 106 | it('should cleanup animation classes', (done) => { 107 | const elemAnimated = $('.animated-item').eq(0)[0]; 108 | 109 | sut.enter(elemAnimated).then( () => { 110 | expect(elemAnimated.classList.contains('au-enter')).toBe(false); 111 | expect(elemAnimated.classList.contains('au-enter-active')).toBe(false); 112 | done(); 113 | }); 114 | }); 115 | 116 | it('should not add left class by default', (done) => { 117 | const elemAnimated = $('.animated-item').eq(0)[0]; 118 | 119 | sut.enter(elemAnimated).then( () => { 120 | expect(elemAnimated.classList.contains('au-entered')).toBe(false); 121 | done(); 122 | }); 123 | }); 124 | 125 | it('should add configured entered class', (done) => { 126 | sut.useAnimationDoneClasses = true; 127 | sut.animationEnteredClass = 'custom-entered'; 128 | 129 | const elemAnimated = $('.animated-item').eq(0)[0]; 130 | 131 | sut.enter(elemAnimated).then( () => { 132 | expect(elemAnimated.classList.contains('custom-entered')).toBe(true); 133 | done(); 134 | }); 135 | }); 136 | 137 | it('should not add undefined done class', (done) => { 138 | sut.useAnimationDoneClasses = true; 139 | sut.animationEnteredClass = undefined; 140 | 141 | const elemAnimated = $('.animated-item').eq(0)[0]; 142 | 143 | sut.enter(elemAnimated).then( () => { 144 | expect(elemAnimated.classList.contains('undefined')).toBe(false); 145 | done(); 146 | }); 147 | }); 148 | 149 | it('should set isAnimating to active during animations', (done) => { 150 | sut.enter($('.animated-item').eq(0)[0]).then( () => { 151 | expect(sut.isAnimating).toBe(false); 152 | done(); 153 | }); 154 | 155 | setTimeout( () => { 156 | expect(sut.isAnimating).toBe(true); 157 | }, 70); 158 | }); 159 | 160 | it('should publish expected events', (done) => { 161 | let elemAnimated = $('.animated-item').eq(0)[0]; 162 | let enterBeginCalled = false; 163 | let enterActiveCalled = false; 164 | let enterDoneCalled = false; 165 | 166 | const l1 = document.addEventListener(animationEvent.enterBegin, (payload) => enterBeginCalled = true); 167 | const l2 = document.addEventListener(animationEvent.enterActive, (payload) => enterActiveCalled = true); 168 | const l3 = document.addEventListener(animationEvent.enterDone, () => enterDoneCalled = true); 169 | 170 | sut.enter(elemAnimated).then( () => { 171 | expect(enterBeginCalled).toBe(true); 172 | expect(enterActiveCalled).toBe(true); 173 | expect(enterDoneCalled).toBe(true); 174 | 175 | document.removeEventListener(animationEvent.enterBegin, l1, false); 176 | document.removeEventListener(animationEvent.enterActive, l2, false); 177 | document.removeEventListener(animationEvent.enterDone, l3, false); 178 | done(); 179 | }); 180 | }); 181 | 182 | it('should publish timeout event', (done) => { 183 | let elemAnimated = $('.boring-item').eq(0)[0]; 184 | let timeoutCalled = false; 185 | 186 | const listener = document.addEventListener(animationEvent.enterTimeout, () => { 187 | timeoutCalled = true; 188 | }); 189 | 190 | sut.enter(elemAnimated).then( () => { 191 | expect(timeoutCalled).toBe(true); 192 | document.removeEventListener(animationEvent.enterTimeout, listener, false); 193 | done(); 194 | }); 195 | }); 196 | }); 197 | 198 | describe('leave animation', () => { 199 | let elem; 200 | beforeEach(() => { 201 | loadFixtures('animation.html'); 202 | elem = $('.au-animate').eq(0)[0]; 203 | }); 204 | 205 | it('should return a promise', () => { 206 | const result = sut.leave(elem); 207 | expect(result.then).toBeDefined(); 208 | }); 209 | 210 | it('should remove animation from stack after done', (done) => { 211 | sut.leave(elem).then( () => { 212 | expect(sut.isAnimating).toBe(false); 213 | done(); 214 | }); 215 | }); 216 | 217 | it('should kick off animation', (done) => { 218 | const elemAnimated = $('.animated-item').eq(0)[0]; 219 | 220 | sut.leave(elemAnimated).then( (didRunAnimation) => { 221 | expect(didRunAnimation).toBe(true); 222 | done(); 223 | }); 224 | }); 225 | 226 | it('should not kick off animation on element without proper classes', (done) => { 227 | const elemAnimated = $('.boring-item').eq(0)[0]; 228 | sut.leave(elemAnimated).then( (didRunAnimation) => { 229 | expect(didRunAnimation).toBe(false); 230 | done(); 231 | }); 232 | }); 233 | 234 | it('should cleanup animation classes', (done) => { 235 | const elemAnimated = $('.animated-item').eq(0)[0]; 236 | 237 | sut.enter(elemAnimated).then( () => { 238 | expect(elemAnimated.classList.contains('au-leave')).toBe(false); 239 | expect(elemAnimated.classList.contains('au-leave-active')).toBe(false); 240 | done(); 241 | }); 242 | }); 243 | 244 | it('should not add left class by default', (done) => { 245 | const elemAnimated = $('.animated-item').eq(0)[0]; 246 | 247 | sut.leave(elemAnimated).then( () => { 248 | expect(elemAnimated.classList.contains('au-left')).toBe(false); 249 | done(); 250 | }); 251 | }); 252 | 253 | it('should add configured entered class', (done) => { 254 | sut.useAnimationDoneClasses = true; 255 | sut.animationLeftClass = 'custom-left'; 256 | 257 | const elemAnimated = $('.animated-item').eq(0)[0]; 258 | 259 | sut.leave(elemAnimated).then( () => { 260 | expect(elemAnimated.classList.contains('custom-left')).toBe(true); 261 | done(); 262 | }); 263 | }); 264 | 265 | it('should not add undefined done class', (done) => { 266 | sut.useAnimationDoneClasses = true; 267 | sut.animationLeftClass = undefined; 268 | 269 | const elemAnimated = $('.animated-item').eq(0)[0]; 270 | 271 | sut.leave(elemAnimated).then( () => { 272 | expect(elemAnimated.classList.contains('undefined')).toBe(false); 273 | done(); 274 | }); 275 | }); 276 | 277 | it('should set isAnimating to active during animations', (done) => { 278 | sut.leave($('.animated-item').eq(0)[0]).then( () => { 279 | expect(sut.isAnimating).toBe(false); 280 | done(); 281 | }); 282 | 283 | setTimeout( () => { 284 | expect(sut.isAnimating).toBe(true); 285 | }, 70); 286 | }); 287 | 288 | it('should publish expected events', (done) => { 289 | let elemPublish = $('.animated-item').eq(0)[0]; 290 | let leaveBeginCalled = false; 291 | let leaveActiveCalled = false; 292 | let leaveDoneCalled = false; 293 | 294 | const l1 = document.addEventListener(animationEvent.leaveBegin, (payload) => leaveBeginCalled = true); 295 | const l2 = document.addEventListener(animationEvent.leaveActive, (payload) => leaveActiveCalled = true); 296 | const l3 = document.addEventListener(animationEvent.leaveDone, () => leaveDoneCalled = true); 297 | 298 | 299 | sut.leave(elemPublish).then( () => { 300 | expect(leaveBeginCalled).toBe(true); 301 | expect(leaveActiveCalled).toBe(true); 302 | expect(leaveDoneCalled).toBe(true); 303 | 304 | document.removeEventListener(animationEvent.leaveBegin, l1, false); 305 | document.removeEventListener(animationEvent.leaveActive, l2, false); 306 | document.removeEventListener(animationEvent.leaveDone, l3, false); 307 | 308 | done(); 309 | }); 310 | }); 311 | 312 | it('should publish timeout event', (done) => { 313 | let elemTimeout = $('.boring-item').eq(0)[0]; 314 | let timeoutCalled = false; 315 | 316 | const listener = document.addEventListener(animationEvent.leaveTimeout, () => { 317 | timeoutCalled = true; 318 | }); 319 | 320 | sut.leave(elemTimeout).then( () => { 321 | expect(timeoutCalled).toBe(true); 322 | document.removeEventListener(animationEvent.leaveTimeout, listener, false); 323 | done(); 324 | }); 325 | }); 326 | }); 327 | 328 | describe('removeClass animation', () => { 329 | let elem; 330 | let testClass; 331 | 332 | beforeEach(() => { 333 | loadFixtures('animation.html'); 334 | elem = $('#goingToBeShown').eq(0)[0]; 335 | testClass = 'hidden'; 336 | }); 337 | 338 | it('should return a promise', () => { 339 | const result = sut.removeClass(elem, testClass); 340 | expect(result.then).toBeDefined(); 341 | }); 342 | 343 | it('should remove animation from stack after done', (done) => { 344 | sut.removeClass(elem, testClass).then( () => { 345 | expect(sut.isAnimating).toBe(false); 346 | done(); 347 | }); 348 | }); 349 | 350 | it('should kick off animation', (done) => { 351 | sut.removeClass(elem, testClass).then( (didRunAnimation) => { 352 | expect(didRunAnimation).toBe(true); 353 | done(); 354 | }); 355 | }); 356 | 357 | it('should not kick off animation on element without proper classes', (done) => { 358 | sut.removeClass($('#removeClassWrong').eq(0)[0], testClass).then( (didRunAnimation) => { 359 | expect(didRunAnimation).toBe(false); 360 | done(); 361 | }); 362 | }); 363 | 364 | it('should cleanup animation classes', (done) => { 365 | sut.removeClass(elem, testClass).then( () => { 366 | expect(elem.classList.contains(testClass)).toBe(false); 367 | expect(elem.classList.contains(testClass + '-remove')).toBe(false); 368 | done(); 369 | }); 370 | }); 371 | 372 | it('should set isAnimating to active during animations', (done) => { 373 | sut.removeClass(elem, testClass).then( () => { 374 | expect(sut.isAnimating).toBe(false); 375 | done(); 376 | }); 377 | 378 | setTimeout( () => { 379 | expect(sut.isAnimating).toBe(true); 380 | }, 70); 381 | }); 382 | 383 | it('should publish expected events', (done) => { 384 | let removeClassBeginCalled = false; 385 | let removeClassDoneCalled = false; 386 | 387 | let l1 = document.addEventListener(animationEvent.removeClassBegin, () => removeClassBeginCalled = true); 388 | let l2 = document.addEventListener(animationEvent.removeClassDone, () => removeClassDoneCalled = true); 389 | 390 | sut.removeClass(elem, testClass).then(() => { 391 | expect(removeClassBeginCalled).toBe(true); 392 | expect(removeClassDoneCalled).toBe(true); 393 | 394 | document.removeEventListener(animationEvent.removeClassBegin, l1, false); 395 | document.removeEventListener(animationEvent.removeClassDone, l2, false); 396 | done(); 397 | }); 398 | }); 399 | 400 | it('should publish timeout event', (done) => { 401 | let elemTimeout = $('#removeClassNoAnim').eq(0)[0]; 402 | let timeoutCalled = false; 403 | 404 | document.addEventListener(animationEvent.removeClassTimeout, () => { 405 | timeoutCalled = true; 406 | }); 407 | 408 | sut.removeClass(elemTimeout, 'remove-non-anim').then( (didRunAnimation) => { 409 | expect(didRunAnimation).toBe(false); 410 | expect(timeoutCalled).toBe(true); 411 | done(); 412 | }); 413 | }); 414 | }); 415 | 416 | describe('addClass animation', () => { 417 | let elem; 418 | let testClass; 419 | 420 | beforeEach(() => { 421 | loadFixtures('animation.html'); 422 | elem = $('#goingToBeIncreased').eq(0)[0]; 423 | testClass = 'addMoreWidth'; 424 | }); 425 | 426 | it('should return a promise', () => { 427 | const result = sut.addClass(elem, testClass); 428 | expect(result.then).toBeDefined(); 429 | }); 430 | 431 | it('should remove animation from stack after done', (done) => { 432 | sut.addClass(elem, testClass).then( () => { 433 | expect(sut.isAnimating).toBe(false); 434 | done(); 435 | }); 436 | }); 437 | 438 | it('should kick off animation', (done) => { 439 | sut.addClass(elem, testClass).then( (didRunAnimation) => { 440 | expect(didRunAnimation).toBe(true); 441 | expect($(elem).css('width')).toBe('40px'); 442 | done(); 443 | }); 444 | }); 445 | 446 | it('should not kick off animation on element without proper classes', (done) => { 447 | sut.addClass(elem, 'nonAnimatedWidth').then( (didRunAnimation) => { 448 | expect(didRunAnimation).toBe(false); 449 | done(); 450 | }); 451 | }); 452 | 453 | it('should cleanup animation classes', (done) => { 454 | sut.addClass(elem, testClass).then( (didRunAnimation) => { 455 | expect(elem.classList.contains(testClass)).toBe(true); 456 | expect(elem.classList.contains(testClass + '-add')).toBe(false); 457 | done(); 458 | }); 459 | }); 460 | 461 | it('should set isAnimating to active during animations', (done) => { 462 | sut.addClass(elem, testClass).then( () => { 463 | expect(sut.isAnimating).toBe(false); 464 | done(); 465 | }); 466 | 467 | setTimeout( () => { 468 | expect(sut.isAnimating).toBe(true); 469 | }, 70); 470 | }); 471 | 472 | it('should publish expected events', (done) => { 473 | let addClassBeginCalled = false; 474 | let addClassDoneCalled = false; 475 | 476 | let l1 = document.addEventListener(animationEvent.addClassBegin, () => addClassBeginCalled = true); 477 | let l2 = document.addEventListener(animationEvent.addClassDone, () => addClassDoneCalled = true); 478 | 479 | sut.addClass(elem, testClass).then( () => { 480 | expect(addClassBeginCalled).toBe(true); 481 | expect(addClassDoneCalled).toBe(true); 482 | 483 | document.removeEventListener(animationEvent.addClassBegin, l1, false); 484 | document.removeEventListener(animationEvent.addClassDone, l2, false); 485 | done(); 486 | }); 487 | }); 488 | 489 | it('should publish timeout event', (done) => { 490 | const elemTimeout = $('#addClassNoAnim').eq(0)[0]; 491 | let timeoutCalled = false; 492 | 493 | document.addEventListener(animationEvent.addClassTimeout, () => { 494 | timeoutCalled = true; 495 | }); 496 | 497 | sut.addClass(elemTimeout, 'add-non-anim').then( (didRunAnimation) => { 498 | expect(didRunAnimation).toBe(false); 499 | expect(timeoutCalled).toBe(true); 500 | done(); 501 | }); 502 | }); 503 | }); 504 | 505 | describe('add and removeClass', () => { 506 | let elem; 507 | let testClass; 508 | 509 | beforeEach(() => { 510 | loadFixtures('addremove.html'); 511 | elem = $('#elem').eq(0)[0]; 512 | testClass = 'aurelia-hide'; 513 | }); 514 | 515 | it('should handle quick add and remove cycle', (done) => { 516 | let ok = []; 517 | 518 | ok.push(sut.addClass(elem, testClass)); 519 | 520 | setTimeout( () => { 521 | ok.push(sut.removeClass(elem, testClass)); 522 | }, 49); 523 | 524 | setTimeout( () => { 525 | Promise.all(ok).then( () => { 526 | expect(elem.classList.contains(testClass)).toBe(false); 527 | done(); 528 | }); 529 | }, 500); 530 | }); 531 | }); 532 | 533 | describe('event listener cleanup', () => { 534 | let el; 535 | 536 | beforeEach(() => { 537 | loadFixtures('addremove.html'); 538 | el = $('#overlap').eq(0)[0]; 539 | sut.addClass(el, 'aurelia-hide'); 540 | }); 541 | 542 | it('should remove event listeners when no animation occurs', (done) => { 543 | expect(el.classList.contains('fade-in')).toBe(true); 544 | expect(el.classList.contains('aurelia-hide')).toBe(true); 545 | 546 | sut.removeClass(el, 'aurelia-hide'); 547 | setTimeout(function() { 548 | expect(el.classList.contains('fade-in')).toBe(true); 549 | expect(el.classList.contains('aurelia-hide')).toBe(false); 550 | done(); 551 | }, 2000); 552 | }); 553 | }); 554 | 555 | describe('staggering animations', () => { 556 | let elems; 557 | 558 | beforeEach(() => { 559 | sut.useAnimationDoneClasses = true; 560 | loadFixtures('animation.html'); 561 | elems = $('.stagger'); 562 | $(elems).removeClass('au-left'); 563 | }); 564 | 565 | it('should trigger stagger event', (done) => { 566 | let proms = []; 567 | let eventCalled = false; 568 | 569 | let listener = document.addEventListener(animationEvent.staggerNext, () => eventCalled = true); 570 | 571 | elems.each( (idx, elem) => { 572 | proms.push(sut.enter(elem)); 573 | }); 574 | 575 | Promise.all(proms).then( () => { 576 | expect(eventCalled).toBe(true); 577 | 578 | document.removeEventListener(animationEvent.staggerNext, listener); 579 | done(); 580 | }); 581 | }); 582 | 583 | it('should animate enter elements staggered', (done) => { 584 | let proms = []; 585 | elems.each( (idx, elem) => { 586 | proms.push(sut.enter(elem)); 587 | }); 588 | 589 | let time = Date.now(); 590 | Promise.all(proms).then( () => { 591 | let complete = (Date.now() - time) <= 1500 + 100 * elems.length; 592 | expect(complete).toBe(true); 593 | elems.each( (idx, elem) => { 594 | expect($(elem).css('opacity')).toBe('1'); 595 | }); 596 | done(); 597 | }); 598 | }); 599 | 600 | it('should animate leave elements staggered', (done) => { 601 | let proms = []; 602 | elems.each( (idx, elem) => { 603 | proms.push(sut.leave(elem)); 604 | }); 605 | 606 | let time = Date.now(); 607 | Promise.all(proms).then( () => { 608 | let complete = (Date.now() - time) <= 1500 + 100 * elems.length; 609 | expect(complete).toBe(true); 610 | elems.each( (idx, elem) => { 611 | expect($(elem).css('opacity')).toBe('0'); 612 | }); 613 | done(); 614 | }); 615 | }); 616 | 617 | it('should animate enter element using stagger-enter', (done) => { 618 | let eelems = $('.stagger-enter-only'); 619 | 620 | setTimeout( () => { 621 | expect($(eelems[0]).css('opacity')).not.toBe('0'); 622 | expect($(eelems[eelems.length - 1]).css('opacity')).toBe('0'); 623 | }, 100 ); 624 | 625 | let proms = []; 626 | eelems.each( (idx, elem) => { 627 | proms.push(sut.enter(elem)); 628 | }); 629 | 630 | Promise.all(proms).then( () => { 631 | eelems.each( (idx, elem) => { 632 | expect($(elem).css('opacity')).toBe('1'); 633 | }); 634 | done(); 635 | }); 636 | }); 637 | 638 | it('should animate leave element using stagger-leave', (done) => { 639 | let lelems = $('.stagger-leave-only'); 640 | 641 | setTimeout( () => { 642 | expect($(lelems[0]).css('opacity')).not.toBe('1'); 643 | expect($(lelems[lelems.length - 1]).css('opacity')).toBe('1'); 644 | }, 100 ); 645 | 646 | let proms = []; 647 | lelems.each( (idx, elem) => { 648 | proms.push(sut.leave(elem)); 649 | }); 650 | 651 | Promise.all(proms).then( () => { 652 | lelems.each( (idx, elem) => { 653 | expect($(elem).css('opacity')).toBe('0'); 654 | }); 655 | done(); 656 | }); 657 | }); 658 | 659 | it('should ignore exising elements when calculating stagger delay', (done) => { 660 | elems = $('.stagger'); 661 | 662 | let anim = (s, e, d) => { 663 | let proms = []; 664 | for ( let i = s; i < e; ++i ) { 665 | proms.push(sut[d](elems[i])); 666 | } 667 | 668 | return Promise.all(proms); 669 | }; 670 | 671 | anim(0, 2, 'enter').then( () => { 672 | let time = Date.now(); 673 | Promise.all([anim(3, 4, 'enter'), anim(0, 2, 'leave')]).then(() => { 674 | expect((Date.now() - time) <= 1500 + 200).toBe(true); 675 | done(); 676 | }); 677 | }); 678 | }); 679 | }); 680 | }); 681 | -------------------------------------------------------------------------------- /test/fixtures/addremove.html: -------------------------------------------------------------------------------- 1 | 38 | 39 |
40 | 41 | Quickly animated element 42 | 43 |
44 | 45 |
Clean up
46 | -------------------------------------------------------------------------------- /test/fixtures/animate-missing-keyframes.html: -------------------------------------------------------------------------------- 1 | 18 | 19 |
20 |
Animation - automatic add/remove
21 | 22 |
    23 |
  • Sequence 1
  • 24 |
  • Sequence 2
  • 25 |
  • Sequence 3
  • 26 |
27 |
28 | -------------------------------------------------------------------------------- /test/fixtures/animate.html: -------------------------------------------------------------------------------- 1 | 36 | 37 |
38 |
Animation - automatic add/remove
39 | 40 |
    41 |
  • Sequence 1
  • 42 |
  • Sequence 2
  • 43 |
  • Sequence 3
  • 44 |
45 |
46 | -------------------------------------------------------------------------------- /test/fixtures/animation.html: -------------------------------------------------------------------------------- 1 | 146 | 147 |
148 |
    149 |
  • Test 1
  • 150 |
  • Test 2
  • 151 |
152 | 153 | 154 |
Test
155 |
156 |
157 | 158 |
159 | I'm going to get bigger 160 |
161 | 162 |
Delayed element
163 | 164 | 165 |
    166 |
  • Item1
  • 167 |
  • Item2
  • 168 |
  • Item3
  • 169 |
  • Item4
  • 170 |
  • Item5
  • 171 |
172 | 173 |
    174 |
  • Item1
  • 175 |
  • Item2
  • 176 |
  • Item3
  • 177 |
178 | 179 |
    180 |
  • Item1
  • 181 |
  • Item2
  • 182 |
  • Item3
  • 183 |
184 |
185 | -------------------------------------------------------------------------------- /test/fixtures/run-sequence.html: -------------------------------------------------------------------------------- 1 | 33 | 34 |
35 |
    36 |
  • Sequence 1
  • 37 |
  • Sequence 2
  • 38 |
  • Sequence 3
  • 39 |
40 |
41 | -------------------------------------------------------------------------------- /test/run-sequence.spec.js: -------------------------------------------------------------------------------- 1 | import {CssAnimator} from '../src/animator'; 2 | import {animationEvent} from 'aurelia-templating'; 3 | import {initialize} from 'aurelia-pal-browser'; 4 | 5 | jasmine.getFixtures().fixturesPath = 'base/test/fixtures/'; 6 | 7 | describe('animator-css', () => { 8 | let sut; 9 | 10 | beforeAll(() => initialize()); 11 | beforeEach(() => { 12 | sut = new CssAnimator(); 13 | }); 14 | 15 | describe('runSequence function', () => { 16 | let elems; 17 | 18 | beforeEach(() => { 19 | loadFixtures('run-sequence.html'); 20 | elems = $('.sequenced-items li'); 21 | }); 22 | 23 | it('should run multiple animations one after another', (done) => { 24 | const testClass = 'animate-test'; 25 | 26 | sut.runSequence([ 27 | { element: elems.eq(0)[0], className: testClass }, 28 | { element: elems.eq(1)[0], className: testClass }, 29 | { element: elems.eq(2)[0], className: testClass } 30 | ]).then( () => { 31 | expect(elems.eq(0).css('opacity')).toBe('1'); 32 | expect(elems.eq(1).css('opacity')).toBe('1'); 33 | expect(elems.eq(2).css('opacity')).toBe('1'); 34 | done(); 35 | }); 36 | }); 37 | 38 | it('should fire sequence DOM events', () => { 39 | let beginFired = false; 40 | let doneFired = false; 41 | const listenerBegin = document.addEventListener(animationEvent.sequenceBegin, () => beginFired = true); 42 | const listenerDone = document.addEventListener(animationEvent.sequenceDone, () => doneFired = true); 43 | 44 | const testClass = 'animate-test'; 45 | 46 | sut.runSequence([ 47 | { element: elems.eq(0)[0], className: testClass }, 48 | { element: elems.eq(1)[0], className: testClass }, 49 | { element: elems.eq(2)[0], className: testClass } 50 | ]).then( () => { 51 | document.removeEventListener(animationEvent.sequenceBegin, listenerBegin); 52 | document.removeEventListener(animationEvent.sequenceDone, listenerDone); 53 | 54 | expect(beginFired).toBe(true); 55 | expect(doneFired).toBe(true); 56 | }); 57 | }); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "es2015", 5 | "experimentalDecorators": true, 6 | "emitDecoratorMetadata": false, 7 | "moduleResolution": "node", 8 | "stripInternal": true, 9 | "preserveConstEnums": true, 10 | "listFiles": true, 11 | "declaration": true, 12 | "removeComments": true, 13 | "lib": ["es2015", "dom"] 14 | }, 15 | "exclude": [ 16 | "node_modules", 17 | "dist", 18 | "build", 19 | "doc", 20 | "test", 21 | "config.js", 22 | "gulpfile.js", 23 | "karma.conf.js" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aurelia-animator-css", 3 | "main": "dist/aurelia-animator-css.d.ts" 4 | } 5 | --------------------------------------------------------------------------------