├── .babelrc
├── .editorconfig
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── .travis.yml
├── LICENSE
├── README.md
├── package.json
├── rollup.config.js
├── src
├── __tests__
│ └── vdom.js
├── dom
│ ├── client
│ │ ├── NevinhaComponent.js
│ │ ├── client.js
│ │ ├── events.js
│ │ ├── props.js
│ │ └── render.js
│ └── vdom.js
├── isomorphic
│ ├── component.js
│ ├── diff.js
│ ├── isomorphic.js
│ ├── nevinha-is.js
│ └── render.js
├── motions
│ ├── dom
│ │ ├── animations
│ │ │ ├── agreccives
│ │ │ │ ├── bomb.js
│ │ │ │ └── flash-bang.js
│ │ │ ├── animations.js
│ │ │ ├── bounce
│ │ │ │ ├── enter-down-bounce.js
│ │ │ │ ├── enter-left-bounce.js
│ │ │ │ ├── enter-right-bounce.js
│ │ │ │ ├── enter-up-bounce.js
│ │ │ │ ├── jump-bounce.js
│ │ │ │ └── scale-bounce.js
│ │ │ ├── perspective
│ │ │ │ ├── flip-left-bounce.js
│ │ │ │ └── flip-right-bounce.js
│ │ │ └── specials
│ │ │ │ ├── dance.js
│ │ │ │ ├── journal.js
│ │ │ │ ├── pulse-slow.js
│ │ │ │ ├── pulse.js
│ │ │ │ └── rotate-scale.js
│ │ └── motions-props.js
│ └── providers
│ │ ├── CSSProvider.js
│ │ ├── animations.js
│ │ ├── parallaxProvider.js
│ │ └── perspectiveProvider.js
└── nevinha.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 |
2 | {
3 | "env": {
4 | "test": {
5 | "presets": ["env"]
6 | },
7 | "dev": {
8 | "presets": [
9 | ["env", {"modules": false}]
10 | ],
11 | "plugins": ["external-helpers",
12 | "transform-object-rest-spread"]
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain consistent
2 | # coding styles between different editors and IDEs
3 | # http://editorconfig.org
4 |
5 | root = true
6 |
7 | [*]
8 | indent_style = spaces
9 | indent_size = 2
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 | insert_final_newline = false
13 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "jest": true,
6 | "node": true
7 | },
8 | "globals": {
9 | "AUI": true,
10 | "FARO_ENV": true,
11 | "Liferay": true
12 | },
13 | "parser": "babel-eslint",
14 | "parserOptions": {
15 | "ecmaFeatures": {
16 | "allowImportExportEverywhere": true,
17 | "experimentalObjectRestSpread": true,
18 | "jsx": true
19 | },
20 | "ecmaVersion": 2017,
21 | "sourceType": "module"
22 | },
23 | "plugins": ["react"],
24 | "rules": {
25 | "comma-dangle": 0,
26 | "eol-last": [2, "always"],
27 | "indent": [2, 2],
28 | "max-len": 0,
29 | "new-cap": [
30 | 2,
31 | {
32 | "capIsNew": false
33 | }
34 | ],
35 | "no-console": 0,
36 | "no-undef": 2,
37 | "no-underscore-dangle": [
38 | 2,
39 | {
40 | "allowAfterThis": true,
41 | "enforceInMethodNames": true
42 | }
43 | ],
44 | "no-unused-vars": [
45 | 2,
46 | {
47 | "args": "after-used",
48 | "ignoreRestSiblings": false,
49 | "vars": "all"
50 | }
51 | ],
52 | "object-shorthand": 2,
53 | "prefer-const": 2,
54 | "prefer-template": 2,
55 | "quote-props": [2, "as-needed"],
56 | "quotes": [2, "single", "avoid-escape"],
57 | "react/jsx-boolean-value": 2,
58 | "react/jsx-handler-names": 0,
59 | "react/jsx-key": 2,
60 | "react/jsx-no-bind": 2,
61 | "react/jsx-sort-props": 2,
62 | "react/jsx-uses-vars": 2,
63 | "require-jsdoc": 0,
64 | "sort-keys": [
65 | 2,
66 | "asc",
67 | {
68 | "caseSensitive": false
69 | }
70 | ],
71 | "sort-vars": 2,
72 | "valid-jsdoc": 0
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules/
3 | npm-debug.log
4 | .idea
5 | package-lock.json
6 | .vscode
7 | dist/
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "bracketSpacing": false,
3 | "singleQuote": true,
4 | "tabWidth": 2,
5 | "useTabs": false
6 | }
7 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | branches:
2 | - master
3 |
4 | cache:
5 | yarn: true
6 |
7 | install:
8 | - yarn
9 |
10 | language: node_js
11 | node_js:
12 | - "9.10.0"
13 |
14 | script:
15 | - npm run test
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 By Clóvis da Silva Neto
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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NevinhaJS
2 |
3 | ## What's NevinhaJS?
4 | A component render as [React](https://reactjs.org/) that goes beyond static methods and component lifecycles, including state, props and improving the way you make meaningful animations, providing an amazing experience.
5 |
6 |
7 | > 🚨 Importants Notes
8 | > - **NevinhaJS uses the virtual DOM as base of their diff.**
9 | > - **NevinhaJS uses CSS Typed OM API, which improves arround 30% the performance of your browser's css manipulations. You can learn more in this [google article](https://developers.google.com/web/updates/2018/03/cssom#customprops)**
10 |
11 | Actually, we're implementing new features and animations to make this framework more powerful and sexy for your projects.
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Quickly create components for motion animation.
20 |
21 |
22 | > 🤔 **What's Missing?**
23 | > - Web Animations API
24 | > - Parallax animations
25 | > - Include [All Animations CSS3](http://all-animation.github.io)
26 | > - Unit tests
27 | > - Pre-commit tasks
28 | > - ...
29 |
30 | ## Table of Contents
31 |
32 | - [Install](#install)
33 | - [Usage](#usage)
34 | - [Contributing](#contributing)
35 | - [Examples](https://github.com/NevinhaJS/nevinha-js-examples)
36 | - [License](#license)
37 |
38 | ## Install
39 | We'll put NevinhaJS in [NPM](https://www.npmjs.com/) as soon as possible.
40 |
41 | ## Usage
42 |
43 | With a bundler like [rollup](https://rollupjs.org/) or [webpack](https://webpack.js.org/), use:
44 |
45 | ```javascript
46 | // using ES6 modules
47 | import {NevinhaComponent, render} from 'nevinha-js';
48 |
49 | class App extends NevinhaComponent {
50 | constructor(){
51 | super();
52 | this.state.name = "NevinhaJS"
53 | }
54 |
55 | customEvent(){
56 | console.log('this is a custom event')
57 | }
58 |
59 | render() {
60 | const {name} = this.state;
61 | const {customEvent} = this.props;
62 |
63 | return (
64 |
65 |
66 |
67 |
68 | Don't forget to declare this component, because it wasn't declared yet
69 |
70 |
71 | // Yeah it has some effects inside our architecture,
72 | // you just need to call the effect name 😉
73 |
74 |
Hello! This is the new: {name}
75 | {name}
76 |
77 |
78 |
79 |
80 | );
81 | }
82 | }
83 | ```
84 |
85 | You can learn more about how to use NevinhaJs in the [NevinhaJs Examples repo](https://github.com/NevinhaJS/nevinha-js-examples)
86 |
87 | **We're taking special care with browser animation performance. We're always following google developer's documentation and developers group to ensure performance compatibility**
88 |
89 | ## Contributing
90 |
91 | First of all, thank you for your contribution. If you want to contribute, feel free to search for [open issues](../../issues) or our [project roadmap](../..//projects/1), we have a lot of work to do, and of course we'll need you.
92 |
93 | ### Reporting Issues
94 | Did you find a problem? Do you want a new feature? First check if your problem or idea [has been reported](../../issues).
95 | If there is a [new question](../../issues/new), please be clear and descriptive.
96 |
97 | > 🚨 Check issue open and closed.
98 |
99 | ### Submitting pull requests
100 |
101 | Make sure your pull requests are within scope and that you follow the project assumptions.
102 |
103 | > 🚨 Submit your pull solipsies with tests if necessary.
104 |
105 | - Fork it!
106 | - Clone your fork: `git clone https://github.com//NevinhaJS`
107 | - Navigate to the newly cloned directory: `cd NevinhaJS`
108 | - Create a new branch for the new feature: `git checkout -b new-feature`
109 | - Install the tools necessary for development: `yarn`
110 | - Make your changes.
111 | - `yarn run build` to verify your change doesn't increase output size.
112 | - Commit your changes: `git commit -am 'Add new feature'`
113 | - Push to the branch: `git push origin new-feature`
114 | - Submit a pull request with full remarks documenting your changes.
115 |
116 | ## License
117 |
118 | [MIT License](LICENSE.md) © NevinhaJS
119 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nevinha-js",
3 | "version": "0.6.2",
4 | "description": "Create static components with sexy animations",
5 | "main": "dist/nevinha.cjs.js",
6 | "module": "dist/nevinha.esm.js",
7 | "license": "MIT",
8 | "repository": "nevinhajs/nevinha-js",
9 | "keywords": [
10 | "nevinhajs",
11 | "nevinha-js"
12 | ],
13 | "scripts": {
14 | "build": "BABEL_ENV=dev rollup -c",
15 | "dev": "BABEL_ENV=dev rollup -c -w",
16 | "format": "prettier-eslint --write 'src/**/*.js'",
17 | "lint": "eslint src/",
18 | "open:coverage": "opn dist/coverage/lcov-report/index.html",
19 | "test": "jest --coverage"
20 | },
21 | "devDependencies": {
22 | "babel-core": "^6.26.3",
23 | "babel-eslint": "^8.2.6",
24 | "babel-jest": "^23.4.2",
25 | "eslint": "^5.1.0",
26 | "eslint-plugin-react": "^7.10.0",
27 | "jest": "^23.4.2",
28 | "opn-cli": "^3.1.0",
29 | "prettier-eslint-cli": "^4.7.1",
30 | "regenerator-runtime": "^0.12.1",
31 | "rollup": "^0.45.2",
32 | "rollup-plugin-babel": "^2.7.1",
33 | "rollup-plugin-eslint": "^4.0.0",
34 | "rollup-plugin-filesize": "^1.4.2",
35 | "rollup-plugin-uglify": "^2.0.1",
36 | "rollup-watch": "^4.3.1"
37 | },
38 | "dependencies": {
39 | "babel-plugin-external-helpers": "^6.22.0",
40 | "babel-plugin-transform-object-rest-spread": "^6.26.0",
41 | "babel-preset-env": "^1.7.0",
42 | "uglify-es": "^3.3.9"
43 | },
44 | "transform": {
45 | "^.+\\.jsx?$": "babel-jest"
46 | },
47 | "jest": {
48 | "collectCoverageFrom": [
49 | "src/dom/**/*.js",
50 | "src/isomorphic/**/*.js",
51 | "src/motions/**/*.js"
52 | ],
53 | "coverageDirectory": "dist/coverage",
54 | "coverageThreshold": {
55 | "global": {
56 | "branches": 1,
57 | "functions": 1,
58 | "lines": 25
59 | }
60 | },
61 | "verbose": true,
62 | "testURL": "http://localhost/"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import uglify from 'rollup-plugin-uglify';
2 | import babel from 'rollup-plugin-babel';
3 | import eslint from 'rollup-plugin-eslint';
4 | import filesize from 'rollup-plugin-filesize';
5 | import { minify } from 'uglify-es';
6 | import pkg from './package.json';
7 |
8 | export default {
9 | entry: 'src/nevinha.js',
10 | plugins: [
11 | eslint(),
12 | babel({
13 | exclude: 'node_modules/**',
14 | }),
15 | uglify({}, minify),
16 | filesize()
17 | ],
18 | sourceMap: true,
19 | targets: [
20 | { dest: pkg.main, format: 'cjs' },
21 | { dest: pkg.module, format: 'es' }
22 | ]
23 | };
--------------------------------------------------------------------------------
/src/__tests__/vdom.js:
--------------------------------------------------------------------------------
1 | import {NevinhaDOM} from '../nevinha';
2 |
3 | describe('NevinhaDOM', () => {
4 | it('should return a virutal DOM', () => {
5 | const type = 'string';
6 | const attributes = {description: 'description', title: 'title'};
7 | const args = ['arg1', 'arg2', 'arg3'];
8 | const nevinhaDom = NevinhaDOM(type, attributes, args);
9 |
10 | expect(nevinhaDom).toEqual({
11 | attributes: {
12 | description: 'description',
13 | title: 'title'
14 | },
15 | children: ['arg1', 'arg2', 'arg3'],
16 | type: 'string'
17 | });
18 | });
19 | });
20 |
21 | describe('NevinhaDOM Empty State', () => {
22 | const {attributes, children, type} = NevinhaDOM();
23 |
24 | it('should return an empty object for the attributes parameter', () => {
25 | expect(attributes).toEqual({});
26 | });
27 |
28 | it('should return an empty array for the children parameter', () => {
29 | expect(children).toEqual([]);
30 | });
31 |
32 | it('should return undefined for the type parameter', () => {
33 | expect(type).toEqual(undefined);
34 | });
35 | });
--------------------------------------------------------------------------------
/src/dom/client/NevinhaComponent.js:
--------------------------------------------------------------------------------
1 | import {
2 | updateElement,
3 | IsomorphicNevinhaComponent
4 | } from '../../isomorphic/isomorphic';
5 | import {diffDOM} from './client';
6 | import {definedMotionsProps} from '../../motions/dom/motions-props';
7 |
8 | class NevinhaComponent extends IsomorphicNevinhaComponent {
9 | constructor(props, context) {
10 | super(props, context);
11 | }
12 |
13 | getElementIndex() {
14 | const parentNode = this.getParentNode();
15 |
16 | return Array.prototype.slice
17 | .call(parentNode.childNodes)
18 | .indexOf(this.element);
19 | }
20 |
21 | getParentNode() {
22 | let parentNode = this.element.parentNode;
23 |
24 | if (!parentNode) parentNode = this.parentNode;
25 |
26 | return parentNode;
27 | }
28 |
29 | removeAnimation(ref) {
30 | if (!ref.animation) return;
31 |
32 | if (!ref.animation.infinite) {
33 | ref.animation.config.finish();
34 | }
35 |
36 | ref.animation.config.cancel();
37 |
38 | ref.animation = null;
39 | }
40 |
41 | setAnimation(ref, config) {
42 | const {name, values} = config;
43 |
44 | if (ref.animation) {
45 | this.removeAnimation(ref);
46 | }
47 |
48 | definedMotionsProps[name].callFn(ref.element, values, ref);
49 | }
50 |
51 | setState(states) {
52 | const currentElement = this.render();
53 |
54 | Object.keys(states).map(key => (this.state[key] = states[key]));
55 |
56 | const index = this.getElementIndex();
57 |
58 | if (index === -1) return;
59 |
60 | updateElement(
61 | this,
62 | currentElement,
63 | diffDOM,
64 | this.getParentNode(),
65 | this.render(),
66 | index
67 | );
68 | }
69 | }
70 |
71 | export {NevinhaComponent};
72 |
--------------------------------------------------------------------------------
/src/dom/client/client.js:
--------------------------------------------------------------------------------
1 | import {isEvent} from './events';
2 | import {NevinhaComponent} from './NevinhaComponent';
3 | import {render, createTextNode, createInstance, diffDOM} from './render';
4 | import {
5 | setBooleanProp,
6 | setProp,
7 | setProps,
8 | removeBooleanProp,
9 | removeProp,
10 | updateProp,
11 | updateProps
12 | } from './props';
13 |
14 | export {
15 | createTextNode,
16 | createInstance,
17 | diffDOM,
18 | isEvent,
19 | NevinhaComponent,
20 | setBooleanProp,
21 | setProp,
22 | setProps,
23 | removeBooleanProp,
24 | removeProp,
25 | updateProp,
26 | updateProps,
27 | render
28 | };
29 |
--------------------------------------------------------------------------------
/src/dom/client/events.js:
--------------------------------------------------------------------------------
1 | export const isEvent = attr => {
2 | return /^on/.test(attr);
3 | };
4 |
5 | export const formatEventName = eventName =>
6 | eventName.split('on')[1].toLowerCase();
7 |
--------------------------------------------------------------------------------
/src/dom/client/props.js:
--------------------------------------------------------------------------------
1 | import {isEvent, formatEventName} from './events';
2 | import {isCustomProp} from '../../isomorphic/nevinha-is';
3 | import {removeContextRef, addContextRef} from '../../isomorphic/diff';
4 | import {definedMotionsProps} from '../../motions/dom/motions-props';
5 | import {setTypedStyle} from '../../motions/providers/CSSProvider';
6 |
7 | export const setProps = ($el, props, parentComponent) => {
8 | Object.keys(props).forEach(prop => {
9 | if (isEvent(prop)) {
10 | $el[`on${formatEventName(prop)}`] = props[prop];
11 | } else {
12 | setProp($el, prop, props[prop], parentComponent);
13 | }
14 | });
15 | };
16 |
17 | export const setProp = ($el, name, value, parentComponent) => {
18 | if (isCustomProp(name) && value) {
19 | return setCustomProp($el, name, value, parentComponent);
20 | }
21 |
22 | if (name === 'className') {
23 | return $el.setAttribute('class', value);
24 | }
25 |
26 | if (typeof value === 'boolean') {
27 | return setBooleanProp($el, name, value);
28 | }
29 |
30 | if (name === 'value') {
31 | return ($el.value = value);
32 | }
33 |
34 | if (name === 'ref') {
35 | return addContextRef(parentComponent, value, {
36 | element: $el
37 | });
38 | }
39 |
40 | return $el.setAttribute(name, value);
41 | };
42 |
43 | export const setBooleanProp = ($el, name, value) => {
44 | if (value) {
45 | $el.setAttribute(name, value);
46 | $el[name] = true;
47 | } else {
48 | $el[name] = false;
49 | }
50 | };
51 |
52 | export const removeBooleanProp = ($el, name) => {
53 | $el.removeAttribute(name);
54 | $el[name] = false;
55 | };
56 |
57 | export const removeProp = ($el, name, value) => {
58 | if (isCustomProp(name)) {
59 | return;
60 | } else if (name === 'className') {
61 | $el.removeAttribute('class');
62 | } else if (typeof value === 'boolean') {
63 | removeBooleanProp($el, name);
64 | } else {
65 | $el.removeAttribute(name);
66 | }
67 | };
68 |
69 | export const updateProp = ($el, name, newVal, oldVal, parentComponent) => {
70 | if (isEvent(name)) {
71 | const eventName = formatEventName(name);
72 | $el[`on${eventName}`] = newVal;
73 | return;
74 | }
75 |
76 | if (typeof newVal == 'undefined' || newVal == null) {
77 | return removeProp($el, name, oldVal);
78 | }
79 |
80 | if (!oldVal || newVal !== oldVal) {
81 | return setProp($el, name, newVal, parentComponent);
82 | }
83 | };
84 |
85 | export const updateProps = (
86 | $target,
87 | newProps = {},
88 | oldProps = {},
89 | parentComponent
90 | ) => {
91 | const props = Object.assign({}, newProps, oldProps);
92 |
93 | if (!newProps.ref && oldProps.ref) {
94 | removeContextRef(parentComponent, oldProps.ref);
95 | }
96 |
97 | Object.keys(props).forEach(name => {
98 | updateProp($target, name, newProps[name], oldProps[name], parentComponent);
99 | });
100 | };
101 |
102 | export const setStyleProps = ($el, value) => {
103 | const values = value.split(';');
104 |
105 | if (!values[values.length - 1].trim()) {
106 | values.splice(-1, 1);
107 | }
108 |
109 | const stylesMap = values.map(cssValue => {
110 | const splitedValue = cssValue.split(':');
111 |
112 | return {
113 | property: splitedValue[0].trim(),
114 | value: splitedValue[1].trim()
115 | };
116 | });
117 |
118 | stylesMap.forEach(style => setTypedStyle($el, style.property, style.value));
119 | };
120 |
121 | export const setCustomProp = ($el, name, value) => {
122 | if (name == 'style') return setStyleProps($el, value);
123 |
124 | return definedMotionsProps[name].callFn($el, value);
125 | };
126 |
--------------------------------------------------------------------------------
/src/dom/client/render.js:
--------------------------------------------------------------------------------
1 | import {setProps, updateProps} from './props';
2 | import {createVirtualElement} from '../../isomorphic/isomorphic';
3 | /**
4 | * @param {NevinhaClass} component The instance of the NevinhaJS application wich should rerender.
5 | * @param {htmlNode} $node The node where the component will be attached
6 | */
7 | export const render = (Component, $node) => {
8 | const instance = new Component();
9 | instance.element = createVirtualElement(instance.render(), {
10 | createInstance,
11 | createTextNode,
12 | parentComponent: instance
13 | });
14 | $node.appendChild(instance.element);
15 | };
16 |
17 | export const createTextNode = nodeText => {
18 | return document.createTextNode(nodeText);
19 | };
20 |
21 | export const createInstance = (nodeType, props, children, parentComponent) => {
22 | const $el = document.createElement(nodeType);
23 |
24 | children
25 | .map(node =>
26 | createVirtualElement(node, {
27 | createInstance,
28 | createTextNode,
29 | parentComponent
30 | })
31 | )
32 | .forEach(newNode => {
33 | if (newNode) {
34 | $el.appendChild(newNode);
35 | }
36 | });
37 |
38 | setProps($el, props, parentComponent);
39 |
40 | return $el;
41 | };
42 |
43 | export const diffDOM = {
44 | addToDiff: ($parent, newNode, parentComponent, index) => {
45 | $parent.insertBefore(
46 | createVirtualElement(newNode, {
47 | createInstance,
48 | createTextNode,
49 | parentComponent
50 | }),
51 | $parent.childNodes[index]
52 | );
53 | },
54 |
55 | removeFromDiff: ($parent, _, __, index) => {
56 | $parent.removeChild($parent.childNodes[index]);
57 | return index;
58 | },
59 |
60 | replaceFromDiff: ($parent, newNode, oldNode, parentComponent, index) => {
61 | $parent.replaceChild(
62 | createVirtualElement(newNode, {
63 | createInstance,
64 | createTextNode,
65 | parentComponent
66 | }),
67 | $parent.childNodes[index]
68 | );
69 | },
70 |
71 | updatePropsFromDiff: ($parent, newNode, oldNode, parentComponent, index) => {
72 | updateProps(
73 | $parent.childNodes[index],
74 | newNode.attributes,
75 | oldNode.attributes,
76 | parentComponent
77 | );
78 | }
79 | };
80 |
--------------------------------------------------------------------------------
/src/dom/vdom.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Generates a virtual DOM representation as a plane Object
3 | *
4 | * @param {(string|function)} type - Element type
5 | * @param {object} attributes - All Props and values
6 | * @param {...(string|vdom|array)} ...args - Child elements
7 | *
8 | * @returns {object} Virtual DOM representation as a plane object
9 | *
10 | */
11 | /*eslint-disable */
12 | /** @jsx NevinhaDOM */
13 | const NevinhaDOM = (type, attributes, ...args) => {
14 | const children = args.length ? [].concat(...args) : [];
15 |
16 | console.log({type, attributes: attributes || {}, children});
17 |
18 | return {type, attributes: attributes || {}, children};
19 | };
20 | /*eslint-enable */
21 |
22 | export default NevinhaDOM;
23 |
--------------------------------------------------------------------------------
/src/isomorphic/component.js:
--------------------------------------------------------------------------------
1 | class IsomorphicNevinhaComponent {
2 | constructor(props, context) {
3 | this.props = props || {};
4 | this.state = this.state || {};
5 | this.context = context || {};
6 |
7 | this.componentWillMount();
8 | }
9 |
10 | created() {}
11 |
12 | componentWillMount() {}
13 |
14 | componentDidMount() {}
15 |
16 | componentUnmount() {}
17 |
18 | setState() {}
19 | }
20 |
21 | export {IsomorphicNevinhaComponent};
22 |
--------------------------------------------------------------------------------
/src/isomorphic/diff.js:
--------------------------------------------------------------------------------
1 | import {isClass} from './nevinha-is';
2 | import {createVirtualElement} from './render';
3 | import {createTextNode, createInstance} from '../dom/client/render';
4 |
5 | export const updateElement = (
6 | parentComponent,
7 | oldNode,
8 | {addToDiff, removeFromDiff, replaceFromDiff, updatePropsFromDiff},
9 | $parent,
10 | newNode,
11 | index = 0
12 | ) => {
13 | let newParentCompeonent = parentComponent;
14 | if (!oldNode && !newNode) return;
15 |
16 | if (oldNode && typeof oldNode.type == 'function') {
17 | const {NodeComponent} = updateComponentDiff(
18 | oldNode,
19 | parentComponent,
20 | $parent.childNodes[index],
21 | true
22 | );
23 | oldNode = NodeComponent;
24 | }
25 |
26 | if (newNode && typeof newNode.type == 'function') {
27 | const {NodeComponent, NodeComponentInstance} = updateComponentDiff(
28 | newNode,
29 | parentComponent,
30 | $parent.childNodes[index]
31 | );
32 |
33 | newNode = NodeComponent;
34 |
35 | if (NodeComponentInstance) {
36 | newParentCompeonent = NodeComponentInstance;
37 | }
38 | }
39 |
40 | if (!oldNode) {
41 | addToDiff($parent, newNode, newParentCompeonent, index);
42 | } else if (!newNode) {
43 | updateContext(newParentCompeonent, oldNode);
44 | removeFromDiff($parent, newNode, oldNode, index);
45 | return true;
46 | } else if (changed(newNode, oldNode)) {
47 | updateContext(newParentCompeonent, oldNode);
48 | replaceFromDiff($parent, newNode, oldNode, newParentCompeonent, index);
49 | } else if (newNode.type) {
50 | const newLength = newNode.children.length;
51 | const oldLength = oldNode.children.length;
52 |
53 | updatePropsFromDiff($parent, newNode, oldNode, newParentCompeonent, index);
54 |
55 | for (let i = 0; i < newLength || i < oldLength; i++) {
56 | //It's to check if the parentNode is will put back or not the old node
57 | //Its to avoid null pointer in case of the newNode is smaller than the oldNode
58 |
59 | if (
60 | !$parent.childNodes[index] ||
61 | (!$parent.childNodes[index].childNodes[i] && oldLength > newLength)
62 | )
63 | return;
64 |
65 | const removedElment = updateElement(
66 | newParentCompeonent,
67 | oldNode.children[i],
68 | {
69 | addToDiff,
70 | removeFromDiff,
71 | replaceFromDiff,
72 | updatePropsFromDiff
73 | },
74 | $parent.childNodes[index],
75 | newNode.children[i],
76 | i
77 | );
78 |
79 | if (removedElment && oldLength > newLength) {
80 | i--;
81 | }
82 | }
83 | }
84 | };
85 |
86 | const updateComponentDiff = (NodeComponent, parentComponent, $node, isOld) => {
87 | let NodeComponentInstance;
88 |
89 | if (isClass(NodeComponent.type)) {
90 | let context = {};
91 |
92 | if (parentComponent && parentComponent.context.store) {
93 | context = parentComponent.context;
94 | }
95 |
96 | context.name = NodeComponent.type.name;
97 |
98 | if ($node && $node.data && $node.data[NodeComponent.type.name]) {
99 | $node.data[NodeComponent.type.name].componentUnmount();
100 | $node.data[NodeComponent.type.name] = null;
101 | }
102 |
103 | NodeComponentInstance = new NodeComponent.type( // eslint-disable-line
104 | NodeComponent.attributes,
105 | context
106 | );
107 |
108 | NodeComponentInstance.children = NodeComponent.children;
109 |
110 | if (NodeComponentInstance.getChildContext) {
111 | NodeComponentInstance.context = {
112 | ...NodeComponentInstance.getChildContext(),
113 | ...NodeComponentInstance.context
114 | };
115 | }
116 |
117 | NodeComponentInstance.element = createVirtualElement(NodeComponent, {
118 | $node,
119 | createInstance,
120 | createTextNode,
121 | parentComponent: NodeComponentInstance
122 | });
123 |
124 | if ($node) {
125 | NodeComponentInstance.parentNode = $node.parentNode;
126 |
127 | if ($node.data) {
128 | $node.data[NodeComponent.type.name] = NodeComponentInstance;
129 | }
130 | }
131 |
132 | NodeComponent = NodeComponentInstance.render();
133 | } else {
134 | NodeComponent = Object.assign(
135 | NodeComponent.type(NodeComponent.attributes, NodeComponent.children)
136 | );
137 | }
138 |
139 | if (typeof NodeComponent.type != 'string') {
140 | const updatedComponent = updateComponentDiff(
141 | NodeComponent,
142 | parentComponent,
143 | $node,
144 | isOld
145 | );
146 |
147 | NodeComponent = updatedComponent.NodeComponent;
148 | NodeComponentInstance = updatedComponent.NodeComponentInstance;
149 | }
150 |
151 | return {NodeComponent, NodeComponentInstance};
152 | };
153 |
154 | export const updateContext = (parentComponent, {attributes, children}) => {
155 | if (!attributes) return;
156 |
157 | const {ref} = attributes;
158 |
159 | if (ref) {
160 | removeContextRef(parentComponent, ref);
161 | }
162 |
163 | if (children.length) {
164 | children.forEach(child => updateContext(parentComponent, child));
165 | }
166 | };
167 |
168 | export const removeContextRef = (parentComponent, ref) => {
169 | delete parentComponent.context[ref];
170 | };
171 |
172 | export const addContextRef = (parentComponent, ref, value) => {
173 | return (parentComponent.context[ref] = value);
174 | };
175 |
176 | /**
177 | * @param {object} node1 An jsx node to compare changes
178 | * @param {object} node2 Another jsx node to compare changes
179 | */
180 | export const changed = (node1, node2) => {
181 | if (
182 | typeof node1 !== typeof node2 ||
183 | (typeof node1 === 'string' && node1 !== node2) ||
184 | node1.type !== node2.type
185 | ) {
186 | return true;
187 | }
188 | };
189 |
--------------------------------------------------------------------------------
/src/isomorphic/isomorphic.js:
--------------------------------------------------------------------------------
1 | import {IsomorphicNevinhaComponent} from './component';
2 | import {updateElement, changed} from './diff';
3 | import {createVirtualElement} from './render';
4 | import {isClass, isCustomProp, hasCustomProp} from './nevinha-is';
5 |
6 | export {
7 | changed,
8 | createVirtualElement,
9 | hasCustomProp,
10 | IsomorphicNevinhaComponent,
11 | isClass,
12 | isCustomProp,
13 | updateElement
14 | };
15 |
--------------------------------------------------------------------------------
/src/isomorphic/nevinha-is.js:
--------------------------------------------------------------------------------
1 | import {definedMotionsProps} from '../motions/dom/motions-props';
2 |
3 | export const isClass = func => {
4 | return func.prototype.render;
5 | };
6 |
7 | export const isPXUnit = value => {
8 | return /px$/.test(value);
9 | };
10 |
11 | export const isNumber = value => typeof value === 'number';
12 |
13 | export const isCustomProp = propName => {
14 | if (definedMotionsProps[propName]) return true;
15 |
16 | // if (propName == 'style') return true;
17 | };
18 |
19 | export const isSafari = () =>
20 | /constructor/i.test(window.HTMLElement) ||
21 | (function(p) {
22 | return p.toString() === '[object SafariRemoteNotification]';
23 | })(
24 | !window['safari'] ||
25 | (typeof safari !== 'undefined' && safari.pushNotification) //eslint-disable-line
26 | );
27 |
28 | export const isFirefox = typeof InstallTrigger !== 'undefined';
29 |
30 | export const hasCustomProp = (props = {}) => {
31 | return Object.keys(definedMotionsProps).filter(key => props[key]).length;
32 | };
33 |
--------------------------------------------------------------------------------
/src/isomorphic/render.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @param {object} node The object that will be parsed to htmlNode with it respective children
3 | */
4 | export const createVirtualElement = (
5 | node,
6 | {createTextNode, createInstance, parentComponent, $node}
7 | ) => {
8 | if (!node) return;
9 |
10 | const {type, attributes, children} = node;
11 |
12 | if (typeof node == 'string' || typeof node == 'number') {
13 | return createTextNode(node);
14 | }
15 |
16 | if (typeof type == 'function' && type.prototype.render) {
17 | let context = {};
18 |
19 | if (parentComponent && parentComponent.context.store) {
20 | context = parentComponent.context;
21 | }
22 |
23 | const instance = new type(attributes, context); // eslint-disable-line
24 | instance.children = children;
25 |
26 | if (instance.getChildContext) {
27 | instance.context = {
28 | ...instance.getChildContext(),
29 | ...instance.context
30 | };
31 | }
32 |
33 | const createdElement = createVirtualElement(instance.render(), {
34 | $node,
35 | createInstance,
36 | createTextNode,
37 | parentComponent: instance
38 | });
39 |
40 | instance.element = createdElement;
41 | instance.parentNode = instance.element.parentNode;
42 |
43 | if (!instance.element.data) {
44 | instance.element.data = {};
45 | }
46 |
47 | if ($node) instance.componentUnmount();
48 |
49 | instance.element.data[type.name] = instance;
50 |
51 | if (instance.created) {
52 | instance.created();
53 | }
54 |
55 | return createdElement;
56 | }
57 |
58 | if (typeof type == 'function') {
59 | return createVirtualElement(type(attributes, children), {
60 | createInstance,
61 | createTextNode,
62 | parentComponent
63 | });
64 | }
65 |
66 | if (!children) {
67 | return;
68 | }
69 |
70 | return createInstance(type, attributes, children, parentComponent);
71 | };
72 |
--------------------------------------------------------------------------------
/src/motions/dom/animations/agreccives/bomb.js:
--------------------------------------------------------------------------------
1 | import {getValue} from '../../motions-props';
2 | import {addComponentContextAnimation} from '../../../providers/animations';
3 | import {setVisibleState} from '../../../providers/CSSProvider';
4 |
5 | const BOMB_MOTION = 'bomb';
6 | const bombKeyFrames = [
7 | {
8 | opacity: 1,
9 | transform: 'rotateZ(0)',
10 | transformOrigin: '-50% 0%',
11 | visibility: 'visible'
12 | },
13 | {
14 | opacity: 0,
15 | transform: 'rotateZ(-100deg)',
16 | transformOrigin: '-10% 0%',
17 | visibility: 'hidden'
18 | }
19 | ];
20 |
21 | const setBombAnimation = ($el, values, contextRef) => {
22 | setVisibleState($el);
23 |
24 | const speed = getValue(values, 'speed', BOMB_MOTION) * 1000;
25 | const animation = $el.animate(bombKeyFrames, {
26 | duration: speed,
27 | easing: 'cubic-bezier(0, 0.93, 1, 1)',
28 | fill: 'forwards'
29 | });
30 |
31 | addComponentContextAnimation(animation, contextRef);
32 | };
33 |
34 | export const bomb = {
35 | callFn: setBombAnimation,
36 | values: {
37 | speed: {
38 | defaultValue: 0.4
39 | }
40 | }
41 | };
42 |
--------------------------------------------------------------------------------
/src/motions/dom/animations/agreccives/flash-bang.js:
--------------------------------------------------------------------------------
1 | import {getValue} from '../../motions-props';
2 | import {addComponentContextAnimation} from '../../../providers/animations';
3 | import {setVisibleState} from '../../../providers/CSSProvider';
4 |
5 | const FLASH_MOTION = 'flash';
6 | const flashKeyFrames = {
7 | offset: [0, 0.1, 0.2, 0.3, 1],
8 | opacity: [0, 1, 0, 1, 1]
9 | };
10 |
11 | const setFlashAnimation = ($el, values, contextRef) => {
12 | setVisibleState($el);
13 |
14 | const speed = getValue(values, 'speed', FLASH_MOTION) * 1000;
15 | const animation = $el.animate(flashKeyFrames, {
16 | duration: speed,
17 | easing: 'linear'
18 | });
19 |
20 | addComponentContextAnimation(animation, contextRef);
21 | };
22 |
23 | export const flash = {
24 | callFn: setFlashAnimation,
25 | values: {
26 | speed: {
27 | defaultValue: 2
28 | }
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/src/motions/dom/animations/animations.js:
--------------------------------------------------------------------------------
1 | import {bomb} from './agreccives/bomb';
2 | import {enterDownBounce} from './bounce/enter-down-bounce';
3 | import {enterLeftBounce} from './bounce/enter-left-bounce';
4 | import {enterRightBounce} from './bounce/enter-right-bounce';
5 | import {enterUpBounce} from './bounce/enter-up-bounce';
6 | import {jumpBounce} from './bounce/jump-bounce';
7 | import {flash} from './agreccives/flash-bang';
8 | import {scaleBounce} from './bounce/scale-bounce';
9 | import {dance} from './specials/dance';
10 | import {pulse} from './specials/pulse';
11 | import {rotateScale} from './specials/rotate-scale';
12 | import {pulseSlow} from './specials/pulse-slow';
13 | import {journal} from './specials/journal';
14 | import {flipLeftBounce} from './perspective/flip-left-bounce';
15 | import {flipRightBounce} from './perspective/flip-right-bounce';
16 |
17 | export {
18 | bomb,
19 | dance,
20 | enterDownBounce,
21 | enterLeftBounce,
22 | enterRightBounce,
23 | enterUpBounce,
24 | flash,
25 | flipLeftBounce,
26 | flipRightBounce,
27 | journal,
28 | jumpBounce,
29 | pulse,
30 | pulseSlow,
31 | rotateScale,
32 | scaleBounce
33 | };
34 |
--------------------------------------------------------------------------------
/src/motions/dom/animations/bounce/enter-down-bounce.js:
--------------------------------------------------------------------------------
1 | import {getValue} from '../../motions-props';
2 | import {hideElementToAnimate} from '../../../providers/animations';
3 |
4 | const ENTER_DOWN_MOTION = 'enterDownBounce';
5 | const enterDownKeyFrames = [
6 | {
7 | opacity: 0,
8 | transform: 'translateY(-120px)',
9 | visibility: 'hidden'
10 | },
11 | {
12 | offset: 0.5,
13 | opacity: 0.5,
14 | transform: 'translateY(0)',
15 | visibility: 'visible'
16 | },
17 | {
18 | offset: 0.7,
19 | opacity: 0.7,
20 | transform: 'translateY(-20px)',
21 | visibility: 'visible'
22 | },
23 | {
24 | opacity: 1,
25 | transform: 'translateY(0)',
26 | visibility: 'visible'
27 | }
28 | ];
29 |
30 | const setEnterDownBounce = ($el, values, contextRef) => {
31 | const speed = getValue(values, 'speed', ENTER_DOWN_MOTION) * 1000;
32 |
33 | hideElementToAnimate(
34 | $el,
35 | enterDownKeyFrames,
36 | {
37 | duration: speed,
38 | easing: 'linear',
39 | fill: 'forwards'
40 | },
41 | contextRef
42 | );
43 | };
44 |
45 | export const enterDownBounce = {
46 | callFn: setEnterDownBounce,
47 | values: {
48 | speed: {
49 | defaultValue: 0.7
50 | }
51 | }
52 | };
53 |
--------------------------------------------------------------------------------
/src/motions/dom/animations/bounce/enter-left-bounce.js:
--------------------------------------------------------------------------------
1 | import {getValue} from '../../motions-props';
2 | import {hideElementToAnimate} from '../../../providers/animations';
3 |
4 | const ENTER_LEFT_MOTION = 'enterLeftBounce';
5 | const enterLeftKeyFrames = [
6 | {
7 | opacity: 0,
8 | transform: 'translateX(-120px)',
9 | visibility: 'hidden'
10 | },
11 | {
12 | offset: 0.5,
13 | opacity: 0.5,
14 | transform: 'translateX(0px)',
15 | visibility: 'visible'
16 | },
17 | {
18 | offset: 0.7,
19 | opacity: 0.7,
20 | transform: 'translateX(-8px)',
21 | visibility: 'visible'
22 | },
23 | {
24 | opacity: 1,
25 | transform: 'translateY(0)',
26 | visibility: 'visible'
27 | }
28 | ];
29 |
30 | const setEnterLeftBounce = ($el, values, contextRef) => {
31 | const speed = getValue(values, 'speed', ENTER_LEFT_MOTION) * 1000;
32 |
33 | hideElementToAnimate(
34 | $el,
35 | enterLeftKeyFrames,
36 | {
37 | duration: speed,
38 | easing: 'linear',
39 | fill: 'forwards'
40 | },
41 | contextRef
42 | );
43 | };
44 |
45 | export const enterLeftBounce = {
46 | callFn: setEnterLeftBounce,
47 | values: {
48 | speed: {
49 | defaultValue: 0.7
50 | }
51 | }
52 | };
53 |
--------------------------------------------------------------------------------
/src/motions/dom/animations/bounce/enter-right-bounce.js:
--------------------------------------------------------------------------------
1 | import {getValue} from '../../motions-props';
2 | import {hideElementToAnimate} from '../../../providers/animations';
3 |
4 | const ENTER_RIGHT_MOTION = 'enterRightBounce';
5 | const enterRightKeyFrames = [
6 | {
7 | opacity: 0,
8 | transform: 'translateX(120px)',
9 | visibility: 'hidden'
10 | },
11 | {
12 | offset: 0.5,
13 | opacity: 0.5,
14 | transform: 'translateX(0px)',
15 | visibility: 'visible'
16 | },
17 | {
18 | offset: 0.7,
19 | opacity: 0.7,
20 | transform: 'translateX(8px)',
21 | visibility: 'visible'
22 | },
23 | {
24 | opacity: 1,
25 | transform: 'translateY(0)',
26 | visibility: 'visible'
27 | }
28 | ];
29 |
30 | const setEnterRightBounce = ($el, values, contextRef) => {
31 | const speed = getValue(values, 'speed', ENTER_RIGHT_MOTION) * 1000;
32 |
33 | hideElementToAnimate(
34 | $el,
35 | enterRightKeyFrames,
36 | {
37 | duration: speed,
38 | easing: 'linear',
39 | fill: 'forwards'
40 | },
41 | contextRef
42 | );
43 | };
44 |
45 | export const enterRightBounce = {
46 | callFn: setEnterRightBounce,
47 | values: {
48 | speed: {
49 | defaultValue: 0.7
50 | }
51 | }
52 | };
53 |
--------------------------------------------------------------------------------
/src/motions/dom/animations/bounce/enter-up-bounce.js:
--------------------------------------------------------------------------------
1 | import {getValue} from '../../motions-props';
2 | import {hideElementToAnimate} from '../../../providers/animations';
3 |
4 | const ENTER_UP_MOTION = 'enterUpBounce';
5 | const enterUpKeyFrames = [
6 | {
7 | opacity: 0,
8 | transform: 'translateY(120px)',
9 | visibility: 'hidden'
10 | },
11 | {
12 | offset: 0.5,
13 | opacity: 0.5,
14 | transform: 'translateY(0)',
15 | visibility: 'visible'
16 | },
17 | {
18 | offset: 0.7,
19 | opacity: 0.7,
20 | transform: 'translateY(20px)',
21 | visibility: 'visible'
22 | },
23 | {
24 | opacity: 1,
25 | transform: 'translateY(0)',
26 | visibility: 'visible'
27 | }
28 | ];
29 |
30 | const setEnterUpBounce = ($el, values, contextRef) => {
31 | const speed = getValue(values, 'speed', ENTER_UP_MOTION) * 1000;
32 |
33 | hideElementToAnimate(
34 | $el,
35 | enterUpKeyFrames,
36 | {
37 | duration: speed,
38 | easing: 'linear',
39 | fill: 'forwards'
40 | },
41 | contextRef
42 | );
43 | };
44 |
45 | export const enterUpBounce = {
46 | callFn: setEnterUpBounce,
47 | values: {
48 | speed: {
49 | defaultValue: 0.7
50 | }
51 | }
52 | };
53 |
--------------------------------------------------------------------------------
/src/motions/dom/animations/bounce/jump-bounce.js:
--------------------------------------------------------------------------------
1 | import {getValue} from '../../motions-props';
2 | import {addComponentContextAnimation} from '../../../providers/animations';
3 | import {setVisibleState} from '../../../providers/CSSProvider';
4 |
5 | const JUMP_BOUNCE_MOTION = 'jumpBounce';
6 | const jumpBounceKeyFrames = [
7 | {
8 | transform: 'translateY(0)'
9 | },
10 | {
11 | offset: 0.1,
12 | transform: 'translateY(-100px)'
13 | },
14 | {
15 | offset: 0.4,
16 | transform: 'translateY(20px)'
17 | },
18 | {
19 | offset: 0.6,
20 | transform: 'translateY(-60px)'
21 | },
22 | {
23 | offset: 0.9,
24 | transform: 'translateY(30px)'
25 | },
26 | {
27 | transform: 'translateY(0)'
28 | }
29 | ];
30 |
31 | const setJumpBounce = ($el, values, contextRef) => {
32 | setVisibleState($el);
33 |
34 | const speed = getValue(values, 'speed', JUMP_BOUNCE_MOTION) * 1000;
35 | const animation = $el.animate(jumpBounceKeyFrames, {
36 | duration: speed,
37 | easing: 'cubic-bezier(0.42, 0, 0.44, 1.21)',
38 | fill: 'forwards'
39 | });
40 |
41 | addComponentContextAnimation(animation, contextRef);
42 | };
43 |
44 | export const jumpBounce = {
45 | callFn: setJumpBounce,
46 | values: {
47 | speed: {
48 | defaultValue: 1
49 | }
50 | }
51 | };
52 |
--------------------------------------------------------------------------------
/src/motions/dom/animations/bounce/scale-bounce.js:
--------------------------------------------------------------------------------
1 | import {getValue} from '../../motions-props';
2 | import {addComponentContextAnimation} from '../../../providers/animations';
3 | import {setVisibleState} from '../../../providers/CSSProvider';
4 |
5 | const SCALE_BOUNCE_MOTION = 'scaleBounce';
6 | const scaleBounceKeyFrames = [
7 | {
8 | opacity: 0,
9 | transform: 'scale(1.5)'
10 | },
11 | {
12 | offset: 0.5,
13 | opacity: 0.5,
14 | transform: 'scale(1)'
15 | },
16 | {
17 | offset: 0.7,
18 | opacity: 0.7,
19 | transform: 'scale(1.1)'
20 | },
21 | {
22 | opacity: 1,
23 | transform: 'scale(1)'
24 | }
25 | ];
26 |
27 | const setScaleBounce = ($el, values, contextRef) => {
28 | setVisibleState($el);
29 |
30 | const speed = getValue(values, 'speed', SCALE_BOUNCE_MOTION) * 1000;
31 | const animation = $el.animate(scaleBounceKeyFrames, {
32 | duration: speed,
33 | easing: 'linear',
34 | fill: 'forwards'
35 | });
36 |
37 | addComponentContextAnimation(animation, contextRef);
38 | };
39 |
40 | export const scaleBounce = {
41 | callFn: setScaleBounce,
42 | values: {
43 | speed: {
44 | defaultValue: 0.7
45 | }
46 | }
47 | };
48 |
--------------------------------------------------------------------------------
/src/motions/dom/animations/perspective/flip-left-bounce.js:
--------------------------------------------------------------------------------
1 | import {getValue} from '../../motions-props';
2 | import {addComponentContextAnimation} from '../../../providers/animations';
3 | import {setVisibleState} from '../../../providers/CSSProvider';
4 |
5 | const MOTION = 'flipLeftBounce';
6 | const keyframes = [
7 | {
8 | opacity: 0,
9 | transform: 'rotateY(70deg)'
10 | },
11 | {
12 | opacity: 0.5,
13 | transform: 'rotateY(-5deg)'
14 | },
15 | {
16 | opacity: 0.8,
17 | transform: 'rotateY(20deg)'
18 | },
19 | {
20 | opacity: 1,
21 | transform: 'rotateY(0deg)'
22 | }
23 | ];
24 |
25 | const setflipLeftBounce = ($el, values, contextRef) => {
26 | setVisibleState($el);
27 |
28 | const speed = getValue(values, 'speed', MOTION) * 1000;
29 | const animation = $el.animate(keyframes, {
30 | duration: speed,
31 | easing: 'ease-in-out',
32 | fill: 'forwards'
33 | });
34 |
35 | addComponentContextAnimation(animation, contextRef);
36 | };
37 |
38 | export const flipLeftBounce = {
39 | callFn: setflipLeftBounce,
40 | values: {
41 | speed: {
42 | defaultValue: 1
43 | }
44 | }
45 | };
46 |
--------------------------------------------------------------------------------
/src/motions/dom/animations/perspective/flip-right-bounce.js:
--------------------------------------------------------------------------------
1 | import {getValue} from '../../motions-props';
2 | import {addComponentContextAnimation} from '../../../providers/animations';
3 | import {setVisibleState} from '../../../providers/CSSProvider';
4 |
5 | const MOTION = 'flipRightBounce';
6 | const keyframes = [
7 | {
8 | opacity: 0,
9 | transform: 'rotateY(-70deg)'
10 | },
11 | {
12 | opacity: 0.5,
13 | transform: 'rotateY(5deg)'
14 | },
15 | {
16 | opacity: 0.8,
17 | transform: 'rotateY(-20deg)'
18 | },
19 | {
20 | opacity: 1,
21 | transform: 'rotateY(0deg)'
22 | }
23 | ];
24 |
25 | const setflipRightBounce = ($el, values, contextRef) => {
26 | setVisibleState($el);
27 |
28 | const speed = getValue(values, 'speed', MOTION) * 1000;
29 | const animation = $el.animate(keyframes, {
30 | duration: speed,
31 | easing: 'ease-in-out',
32 | fill: 'forwards'
33 | });
34 |
35 | addComponentContextAnimation(animation, contextRef);
36 | };
37 |
38 | export const flipRightBounce = {
39 | callFn: setflipRightBounce,
40 | values: {
41 | speed: {
42 | defaultValue: 1
43 | }
44 | }
45 | };
46 |
--------------------------------------------------------------------------------
/src/motions/dom/animations/specials/dance.js:
--------------------------------------------------------------------------------
1 | import {getValue} from '../../motions-props';
2 | import {addComponentContextAnimation} from '../../../providers/animations';
3 | import {setVisibleState} from '../../../providers/CSSProvider';
4 |
5 | const DANCE_MOTION = 'dance';
6 | const danceKeyFrames = [
7 | {
8 | boxShadow: 'inset 0 0 0 0 transparent',
9 | transform: 'scale(1) rotateZ(10deg)'
10 | },
11 | {
12 | boxShadow: 'inset 0 0 0 0 transparent',
13 | offset: 0.05,
14 | transform: 'scale(1) rotateZ(-10deg)'
15 | },
16 | {
17 | boxShadow: 'inset 0 0 0 0 transparent',
18 | offset: 0.1,
19 | transform: 'scale(1) rotateZ(10deg)'
20 | },
21 | {
22 | boxShadow: 'inset 0 0 0 0 transparent',
23 | offset: 0.15,
24 | transform: 'scale(1) rotateZ(-10deg)'
25 | },
26 | {
27 | boxShadow: 'inset 0 0 0 0 transparent',
28 | offset: 0.2,
29 | transform: 'scale(1) rotateZ(10deg)'
30 | },
31 | {
32 | boxShadow: 'inset 0 0 0 0 transparent',
33 | offset: 0.25,
34 | transform: 'scale(1) rotateZ(-10deg)'
35 | },
36 | {
37 | boxShadow: 'inset 0 0 0 0 transparent',
38 | offset: 0.35,
39 | transform: 'scale(1) rotateZ(0deg)'
40 | },
41 | {
42 | boxShadow: '0 0 10px rgba(0,0,0,0.2)',
43 | offset: 0.4,
44 | transform: 'scale(1.3) rotateZ(0deg)'
45 | },
46 | {
47 | boxShadow: 'inset 0 0 0 0 transparent',
48 | offset: 0.6,
49 | transform: 'scale(1) rotateZ(0deg)'
50 | },
51 | {
52 | boxShadow: 'inset 0 0 0 0 transparent',
53 | transform: 'scale(1) rotateZ(0deg)'
54 | }
55 | ];
56 |
57 | const setDanceAnimation = ($el, values, contextRef) => {
58 | setVisibleState($el);
59 |
60 | const speed = getValue(values, 'speed', DANCE_MOTION) * 1000;
61 | const animation = $el.animate(danceKeyFrames, {
62 | duration: speed,
63 | easing: 'linear',
64 | fill: 'forwards'
65 | });
66 |
67 | addComponentContextAnimation(animation, contextRef);
68 | };
69 |
70 | export const dance = {
71 | callFn: setDanceAnimation,
72 | values: {
73 | speed: {
74 | defaultValue: 2
75 | }
76 | }
77 | };
78 |
--------------------------------------------------------------------------------
/src/motions/dom/animations/specials/journal.js:
--------------------------------------------------------------------------------
1 | import {getValue} from '../../motions-props';
2 | import {addComponentContextAnimation} from '../../../providers/animations';
3 | import {setVisibleState} from '../../../providers/CSSProvider';
4 |
5 | const JOURNAL_MOTION = 'journal';
6 | const journalKeyFrames = [
7 | {
8 | transform: 'scale(0) rotate(-360deg)'
9 | },
10 | {
11 | offset: 0.8,
12 | transform: 'scale(0.5) rotate(-180deg)'
13 | },
14 | {
15 | transform: 'scale(1) rotate(0)'
16 | }
17 | ];
18 |
19 | const setJournalAnimation = ($el, values, contextRef) => {
20 | setVisibleState($el);
21 |
22 | const speed = getValue(values, 'speed', JOURNAL_MOTION) * 1000;
23 | const animation = $el.animate(journalKeyFrames, {
24 | duration: speed,
25 | easing: 'linear',
26 | fill: 'forwards'
27 | });
28 |
29 | addComponentContextAnimation(animation, contextRef);
30 | };
31 |
32 | export const journal = {
33 | callFn: setJournalAnimation,
34 | values: {
35 | speed: {
36 | defaultValue: 2
37 | }
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/src/motions/dom/animations/specials/pulse-slow.js:
--------------------------------------------------------------------------------
1 | import {addComponentContextAnimation} from '../../../providers/animations';
2 | import {getValue} from '../../motions-props';
3 | import {setVisibleState} from '../../../providers/CSSProvider';
4 |
5 | const PULSE_SLOW_MOTION = 'pulseSlow';
6 | const pulseSlowKeyFrames = [
7 | {
8 | opacity: 1,
9 | transform: 'scale(1)'
10 | },
11 | {
12 | opacity: 0.5,
13 | transform: 'scale(0.85)'
14 | }
15 | ];
16 |
17 | const setPulseSlowAnimation = ($el, values, contextRef) => {
18 | setVisibleState($el);
19 |
20 | const speed = getValue(values, 'speed', PULSE_SLOW_MOTION) * 1000;
21 | const animation = $el.animate(pulseSlowKeyFrames, {
22 | direction: 'alternate',
23 | duration: speed,
24 | easing: 'ease-in-out',
25 | iterations: Infinity
26 | });
27 |
28 | addComponentContextAnimation(animation, contextRef, true);
29 | };
30 |
31 | export const pulseSlow = {
32 | callFn: setPulseSlowAnimation,
33 | values: {
34 | speed: {
35 | defaultValue: 1
36 | }
37 | }
38 | };
39 |
--------------------------------------------------------------------------------
/src/motions/dom/animations/specials/pulse.js:
--------------------------------------------------------------------------------
1 | import {getValue} from '../../motions-props';
2 | import {addComponentContextAnimation} from '../../../providers/animations';
3 | import {setVisibleState} from '../../../providers/CSSProvider';
4 |
5 | const PULSE_MOTION = 'pulse';
6 | const pulseKeyFrames = [
7 | {
8 | transform: 'scale(1)'
9 | },
10 | {
11 | transform: 'scale(1.5)'
12 | }
13 | ];
14 |
15 | const setPulseAnimation = ($el, values, contextRef) => {
16 | setVisibleState($el);
17 |
18 | const speed = getValue(values, 'speed', PULSE_MOTION) * 1000;
19 | const animation = $el.animate(pulseKeyFrames, {
20 | direction: 'alternate',
21 | duration: speed,
22 | easing: 'linear',
23 | iterations: Infinity
24 | });
25 |
26 | addComponentContextAnimation(animation, contextRef, true);
27 | };
28 |
29 | export const pulse = {
30 | callFn: setPulseAnimation,
31 | values: {
32 | speed: {
33 | defaultValue: 0.5
34 | }
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/src/motions/dom/animations/specials/rotate-scale.js:
--------------------------------------------------------------------------------
1 | import {getValue} from '../../motions-props';
2 | import {addComponentContextAnimation} from '../../../providers/animations';
3 | import {setVisibleState} from '../../../providers/CSSProvider';
4 |
5 | const ROTATE_SCALE_MOTION = 'rotateScale';
6 | const rotateScaleKeyFrames = [
7 | {
8 | transform: 'rotateZ(0) scale(1)'
9 | },
10 | {
11 | offset: 0.25,
12 | transform: 'rotateZ(90deg) scale(0.4)'
13 | },
14 | {
15 | offset: 0.5,
16 | transform: 'rotateZ(180deg) scale(1)'
17 | },
18 | {
19 | offset: 0.75,
20 | transform: 'rotateZ(270deg) scale(0.4)'
21 | },
22 | {
23 | transform: 'rotateZ(360deg) scale(1)'
24 | }
25 | ];
26 |
27 | const setRotateScaleAnimation = ($el, values, contextRef) => {
28 | setVisibleState($el);
29 |
30 | const speed = getValue(values, 'speed', ROTATE_SCALE_MOTION) * 1000;
31 | const animation = $el.animate(rotateScaleKeyFrames, {
32 | duration: speed,
33 | easing: 'linear',
34 | iterations: Infinity
35 | });
36 |
37 | addComponentContextAnimation(animation, contextRef, true);
38 | };
39 |
40 | export const rotateScale = {
41 | callFn: setRotateScaleAnimation,
42 | values: {
43 | speed: {
44 | defaultValue: 3
45 | }
46 | }
47 | };
48 |
--------------------------------------------------------------------------------
/src/motions/dom/motions-props.js:
--------------------------------------------------------------------------------
1 | import {setTypedStyle, setTransition} from '../providers/CSSProvider';
2 | import {requestTimeout} from '../providers/animations';
3 | import {isNumber} from '../../isomorphic/nevinha-is';
4 | import {
5 | bomb,
6 | dance,
7 | enterDownBounce,
8 | enterLeftBounce,
9 | enterRightBounce,
10 | enterUpBounce,
11 | flash,
12 | flipLeftBounce,
13 | flipRightBounce,
14 | journal,
15 | jumpBounce,
16 | pulse,
17 | pulseSlow,
18 | rotateScale,
19 | scaleBounce
20 | } from './animations/animations';
21 |
22 | const STICKY_MOTION = 'sticky';
23 | const FADE_OUT_MOTION = 'fadeOut';
24 | const FADE_IN_MOTION = 'fadeOut';
25 | const PARALLAX_MOTION = 'parallax';
26 |
27 | export const setStickyPosition = ($el, values) => {
28 | setTypedStyle($el, 'position', STICKY_MOTION);
29 | setTypedStyle($el, 'top', getValue(values, 'top', STICKY_MOTION));
30 | };
31 |
32 | export const setFadeOut = ($el, values) => {
33 | const speed = getValue(values, 'speed', FADE_OUT_MOTION);
34 | const timingFn = getValue(values, 'timingFn', FADE_IN_MOTION);
35 |
36 | setTypedStyle($el, 'opacity', 1);
37 | setTypedStyle($el, 'visibility', 'visible');
38 | setTransition($el, 'opacity', speed, timingFn);
39 |
40 | requestTimeout(() => setTypedStyle($el, 'opacity', 0), 100);
41 | };
42 |
43 | export const setFadeIn = ($el, values) => {
44 | const speed = getValue(values, 'speed', FADE_IN_MOTION);
45 | const timingFn = getValue(values, 'timingFn', FADE_IN_MOTION);
46 |
47 | setTypedStyle($el, 'opacity', 0);
48 | setTypedStyle($el, 'visibility', 'visible');
49 | setTransition($el, 'opacity', speed, timingFn);
50 |
51 | requestTimeout(() => setTypedStyle($el, 'opacity', 1), 100);
52 | };
53 |
54 | export const setHide = $el => {
55 | setTypedStyle($el, 'display', 'none');
56 | };
57 |
58 | export const setShow = $el => {
59 | setTypedStyle($el, 'display', 'inherit');
60 | };
61 |
62 | export const setParallax = ($el, values) => {
63 | const size = getValue(values, 'size', PARALLAX_MOTION);
64 | setTypedStyle($el, 'transform', `translateZ(${size * -1}px)`);
65 | };
66 |
67 | export const definedMotionsProps = {
68 | bomb,
69 | dance,
70 | enterDownBounce,
71 | enterLeftBounce,
72 | enterRightBounce,
73 | enterUpBounce,
74 | fadeIn: {
75 | callFn: setFadeIn,
76 | values: {
77 | speed: {
78 | defaultValue: 0.5,
79 | rulesFn: () => [isNumber]
80 | },
81 | timingFn: {
82 | defaultValue: 'ease-out'
83 | }
84 | }
85 | },
86 | fadeOut: {
87 | callFn: setFadeOut,
88 | values: {
89 | speed: {
90 | defaultValue: 0.5,
91 | rulesFn: () => [isNumber]
92 | },
93 | timingFn: {
94 | defaultValue: 'ease-out'
95 | }
96 | }
97 | },
98 | flash,
99 | flipLeftBounce,
100 | flipRightBounce,
101 | hide: {
102 | callFn: setHide
103 | },
104 | journal,
105 | jumpBounce,
106 | parallax: {
107 | callFn: setParallax,
108 | values: {
109 | size: {
110 | defaultValue: 1,
111 | rulesFn: () => [isNumber]
112 | }
113 | }
114 | },
115 | pulse,
116 | pulseSlow,
117 | rotateScale,
118 | scaleBounce,
119 | show: {
120 | callFn: setShow
121 | },
122 | sticky: {
123 | callFn: setStickyPosition,
124 | values: {
125 | top: {
126 | defaultValue: '0px'
127 | }
128 | }
129 | }
130 | };
131 |
132 | export const getDefaultValues = (values, motionName) => {
133 | if (typeof values !== 'object') return definedMotionsProps[motionName].values;
134 |
135 | return values;
136 | };
137 |
138 | export const getValue = (valueConfig = {}, property, motionName) => {
139 | const propertyConfig = getDefaultValues(true, motionName)[property];
140 | const userValue = valueConfig[property];
141 |
142 | if (!userValue) return propertyConfig.defaultValue;
143 |
144 | if (propertyConfig.rulesFn) {
145 | propertyConfig.rulesFn().forEach(rule => {
146 | const isValid = rule(userValue);
147 |
148 | if (!isValid)
149 | throw new Error(
150 | `Invalid CSS value parsed to ${motionName} API. For more details, please read more about '${
151 | rule.name
152 | }' function validation in NevinhaJS API`,
153 | rule
154 | );
155 | });
156 | }
157 |
158 | return userValue;
159 | };
160 |
--------------------------------------------------------------------------------
/src/motions/providers/CSSProvider.js:
--------------------------------------------------------------------------------
1 | import {isPXUnit} from '../../isomorphic/nevinha-is';
2 |
3 | const supportTypedOM = () => window.CSS && CSS.number;
4 |
5 | const fromPixelToFloat = value => parseFloat(value.replace('px', ''));
6 |
7 | const formatCSSValue = value => {
8 | if (typeof value == 'number') return CSS.number(value);
9 |
10 | if (typeof value == 'string' && isPXUnit(value)) {
11 | return CSS.px(fromPixelToFloat(value));
12 | }
13 |
14 | return value;
15 | };
16 |
17 | const blackPropsList = value => {
18 | if (value == 'sticky') return true;
19 | };
20 |
21 | /**
22 | * @param {HTMLNode} $el The element target
23 | * @param {string} prop the CSS attribute name
24 | * @param {any} value the value that will be attached on the elment
25 | */
26 | export const setTypedStyle = ($el, prop, value) => {
27 | if (supportTypedOM()) {
28 | $el.attributeStyleMap.set(prop, formatCSSValue(value));
29 |
30 | return;
31 | }
32 |
33 | if (blackPropsList(value)) {
34 | $el.style[prop] = `-webkit-${value}`;
35 | }
36 |
37 | return ($el.style[prop] = value);
38 | };
39 |
40 | /**
41 | * @param {HTMLNode} $el The element target
42 | * @param {string} prop the CSS attribute name
43 | * @param {any} value the value that will be attached on the elment
44 | */
45 | export const removeTypedStyle = ($el, prop) => {
46 | if (supportTypedOM()) {
47 | return $el.attributeStyleMap.delete(prop);
48 | }
49 |
50 | $el.style.removeProperty(prop);
51 | };
52 |
53 | export const setVisibleState = $el => {
54 | setTypedStyle($el, 'opacity', 1);
55 | setTypedStyle($el, 'visibility', 'visible');
56 | };
57 |
58 | /**
59 | * @param {HTMLNode} $el The element target
60 | * @param {string} prop the CSS attribute name
61 | * @param {float} speed the speed of the transition
62 | * @param {string} timingFn the transition timing function
63 | * wich can be ease-in, ease-out, ease-in-out, linear, ease or a cubic-bezier function
64 | */
65 | export const setTransition = ($el, prop, speed, timingFn) => {
66 | return setTypedStyle($el, 'transition', `${prop} ${speed}s ${timingFn}`);
67 | };
68 |
--------------------------------------------------------------------------------
/src/motions/providers/animations.js:
--------------------------------------------------------------------------------
1 | import {setTypedStyle} from './CSSProvider';
2 |
3 | /**
4 | * Behaves the same as setTimeout except uses requestAnimationFrame() where possible for better performance
5 | * @param {function} fn The callback function
6 | * @param {int} delay The delay in milliseconds
7 | */
8 | export const requestTimeout = function(fn, delay = 1) {
9 | if (
10 | !window.requestAnimationFrame &&
11 | !window.webkitRequestAnimationFrame &&
12 | !(
13 | window.mozRequestAnimationFrame && window.mozCancelRequestAnimationFrame
14 | ) && // Firefox 5 ships without cancel support
15 | !window.oRequestAnimationFrame &&
16 | !window.msRequestAnimationFrame
17 | )
18 | return window.setTimeout(fn, delay);
19 |
20 | const start = new Date().getTime();
21 |
22 | const animationLoop = () => {
23 | const current = new Date().getTime();
24 | const delta = current - start;
25 |
26 | delta >= delay ? fn() : requestAnimationFrame(animationLoop);
27 | };
28 |
29 | requestAnimationFrame(animationLoop);
30 | };
31 |
32 | export const hideElementToAnimate = (
33 | $el,
34 | animationFn,
35 | animationConfig,
36 | contextRef
37 | ) => {
38 | setTypedStyle($el, 'opacity', 0);
39 | setTypedStyle($el, 'visibility', 'hidden');
40 |
41 | requestTimeout(() => {
42 | const animation = $el.animate(animationFn, animationConfig);
43 | addComponentContextAnimation(animation, contextRef);
44 | }, 0);
45 | };
46 |
47 | export const addComponentContextAnimation = (
48 | animation,
49 | contextRef,
50 | infinite
51 | ) => {
52 | if (contextRef) {
53 | contextRef.animation = {
54 | config: animation,
55 | infinite
56 | };
57 | }
58 | };
59 |
--------------------------------------------------------------------------------
/src/motions/providers/parallaxProvider.js:
--------------------------------------------------------------------------------
1 | import {NevinhaComponent} from '../../dom/client/client';
2 |
3 | class ParallaxProvider extends NevinhaComponent {
4 | render() {
5 | const {children, props} = this;
6 |
7 | const layer = {
8 | attributes: {},
9 | children,
10 | type: 'div'
11 | };
12 |
13 | const node = {
14 | attributes: {
15 | style: 'perspective: 1px; overflow: auto;',
16 | ...props
17 | },
18 | children: [layer],
19 | type: 'div'
20 | };
21 |
22 | return node;
23 | }
24 | }
25 |
26 | export {ParallaxProvider};
27 |
--------------------------------------------------------------------------------
/src/motions/providers/perspectiveProvider.js:
--------------------------------------------------------------------------------
1 | import {NevinhaComponent} from '../../dom/client/client';
2 |
3 | class PerspectiveProvider extends NevinhaComponent {
4 | render() {
5 | const {children, props} = this;
6 | const {perspective = '600px'} = props;
7 |
8 | const node = {
9 | attributes: {
10 | style: `perspective: ${perspective};`,
11 | ...props
12 | },
13 | children,
14 | type: 'div'
15 | };
16 |
17 | return node;
18 | }
19 | }
20 |
21 | export {PerspectiveProvider};
22 |
--------------------------------------------------------------------------------
/src/nevinha.js:
--------------------------------------------------------------------------------
1 | import {NevinhaComponent} from './dom/client/client';
2 | import {render} from './dom/client/render';
3 | import NevinhaDOM from './dom/vdom';
4 | import {ParallaxProvider} from './motions/providers/parallaxProvider';
5 | import {PerspectiveProvider} from './motions/providers/perspectiveProvider';
6 |
7 | export {
8 | NevinhaComponent,
9 | render,
10 | ParallaxProvider,
11 | PerspectiveProvider,
12 | NevinhaDOM
13 | };
14 |
--------------------------------------------------------------------------------