├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── dist
├── transitions.js
└── transitions.js.map
├── package-lock.json
├── package.json
├── src
└── index.js
└── test
└── test.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "9"
4 |
5 | env:
6 | - NODE_ENV=development
7 |
8 | install:
9 | - npm install
10 |
11 | script:
12 | - npm test
13 |
14 | notifications:
15 | email: false
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Zacharias Enochsson
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 | This project is only compatible with hyperapp v1, and I do not intend to maintain it any longer. Hence, I am archiving it.
2 |
3 | #
Hyperapp Transitions
4 |
5 | [](https://travis-ci.org/hyperapp/transitions) [](https://www.npmjs.org/package/@hyperapp/transitions) [](https://hyperappjs.herokuapp.com "Join us")
6 |
7 | Hyperapp Transitions lets you animate your [Hyperapp](https://github.com/hyperapp/hyperapp) components as they are appear, dissapear and move around on the page. Use it to provide your user with important cues and a smoother experience.
8 |
9 | * **CSS Based** — Anything you can `transition` with CSS can be animated, including background-color, opacity, position and scale.
10 | * **Animate Layouts** — Reorder nodes in a list or flexbox-layout, and watch them gracefully *slide* into place
11 | * **Composable** — Stack multiple transitions with different delay and duration for complex animation effects.
12 |
13 | ## Getting Started
14 |
15 | Simply wrap your component in one of the decorator components exported from Hyperapp Transitions:
16 |
17 | ```jsx
18 | import {Enter} from "@hyperapp/transitions"
19 |
20 | const Notification = ({key, message}) => (
21 |
22 |
23 | {message}
24 |
25 |
26 | )
27 | ```
28 |
29 | Use it as you would any component. The difference is, now your newly added `Notifications` will not just suddenly appear, but rather fade and slide in from the right.
30 |
31 | ## Installation
32 |
33 | Install with npm or Yarn.
34 |
35 |
36 | npm i @hyperapp/transitions
37 |
38 |
39 | Then with a module bundler like [Rollup](https://rollupjs.org) or [Webpack](https://webpack.js.org), use as you would anything else.
40 |
41 | ```js
42 | import {Enter, Exit, Move} from "@hyperapp/transitions"
43 | ```
44 |
45 | If you don't want to set up a build environment, you can download Hyperapp Transitions from a CDN like [unpkg.com](https://unpkg.com/@hyperapp/transitions) and it will be globally available through the window.transitions object.
46 |
47 | ```html
48 |
49 | ```
50 |
51 | ## Overview
52 |
53 | Hyperapp Transitions exports three components: `Enter`, `Exit` and `Move` described below. Each take a number of attributes for defining the animation.
54 |
55 | The components are *decorator-components* (a.k.a "higher-order components"), which means that they do not add anything to the virtual DOM tree, but rather modify and return their children.
56 |
57 | ## Enter
58 |
59 | Use the Enter component to make elements appear in an animated fashion, when they are added to the DOM.
60 |
61 | ### Attributes
62 |
63 | The Enter component takes the following attributes:
64 |
65 | #### `css`
66 |
67 | The css overrides the element should have *before* it begins to appear. This can also be a function, allowing you to defer the decision until right before the animation starts.
68 |
69 | Default: `{}`
70 |
71 | #### `time`
72 |
73 | The duration of the transition in milliseconds.
74 |
75 | Default: `300`.
76 |
77 | #### `easing`
78 |
79 | A string with the [timing function](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function) of the transition.
80 |
81 | Default: `"linear"`.
82 |
83 | #### `delay`
84 |
85 | Wait this amount of milliseconds before starting the transition.
86 |
87 | Default: `0`.
88 |
89 | ### Example:
90 |
91 | ```jsx
92 | //make messages pop and fade in:
93 |
97 | some message
98 |
99 | ```
100 |
101 | ## Exit
102 |
103 | Use the Exit component to animate out elements before they are removed from the DOM.
104 |
105 | ### Attributes:
106 |
107 | The Exit component takes the following attributes:
108 |
109 | #### `css`
110 |
111 | The css overrides the element should have *after* it has left. This can also be a function, allowing you to defer the decision until right before the animation starts.
112 |
113 | Default: `{}`
114 |
115 | #### `time`
116 |
117 | The duration of the transition in milliseconds.
118 |
119 | Default: `300`.
120 |
121 | #### `easing`
122 |
123 | A string with the [timing function](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function) of the transition.
124 |
125 | Default: `"linear"`
126 |
127 | #### `delay`
128 |
129 | Wait this amount of milliseconds before beginning the transition.
130 |
131 | Default: `0`.
132 |
133 | #### `keep`
134 |
135 | When composing multiple Exit-transition components, set this to `true` on all but the last one, in order to prevent previous ones from removing the element when complete.
136 |
137 | Default: `false`.
138 |
139 | ### Example:
140 |
141 | ```jsx
142 | //make messages slide and fade out, and change backgroundcolor
143 |
147 | some message
148 |
149 | ```
150 |
151 | ## Move
152 |
153 | When the order of sibling nodes (items in a list, for example) changes, their elements are laid out in new positions on the page. When the sibling nodes are wrapped with the `Move` transition-component, they will glide smoothly to their new positions.
154 |
155 | Remember to key the nodes you wish to apply the `Move` transition to, so that the vdom engine is able to detct that it is the *order* that has changed, and not just all the attributes.
156 |
157 | ### Attributes:
158 |
159 | The `Move` component takes the following attributes:
160 |
161 | #### `time`
162 |
163 | The duration of the transition in milliseconds.
164 |
165 | Default: `300`.
166 |
167 | #### `easing`
168 |
169 | A string with the [timing function](https://developer.mozilla.org/en-US/docs/Web/CSS/transition-timing-function) of the transition.
170 |
171 | Default: `"linear"`.
172 |
173 | ### Example:
174 |
175 | ```jsx
176 |
177 |
178 |
179 | {state.todos.map(todo => (
180 | -
181 | ...
182 |
183 | ))}
184 |
185 |
186 | ```
187 |
188 | ## Composing Transitions
189 |
190 | The transition components work by adding handlers for the `oncreate`, `onremove` and/or `onupdate` lifecycle events to their children. If a child already has a handler for those lifecycle events, it is not overwritten. Rather, we compose the transition-handlers with existing lifecycle event handlers.
191 |
192 | This makes it possible to compose multiple transition components on top of eachother.
193 |
194 | ### Example:
195 |
196 | ```jsx
197 | const FadeInPopOut = (props, children) => (
198 |
199 |
200 | {children}
201 |
202 |
203 | )
204 | ```
205 |
206 | ## Keys
207 |
208 | As a general rule: make sure to have keys on all nodes you apply transitions to.
209 |
210 | The lifecycle events which trigger the transitions, are based on *elements* - not virtual nodes. Without keys, Hyperapp's virtual DOM engine will often associate the a nodes with an unintended element in the real DOM, with unexpected transition-behavior as a consequence.
211 |
212 |
213 | ## Examples
214 |
215 | Please have a look at these live, editable examples, for some ideas of what is possible:
216 |
217 | - **[Toasts](https://codepen.io/zaceno/pen/QOYOZd)** - Combining Enter, Exit and Move transitions for an elegant notification display.
218 | - **[15-puzzle](https://codepen.io/zaceno/pen/XzOwPd)** - Slide squares around a grid using the Move transition
219 | - **[Carousel](https://codepen.io/zaceno/pen/ZawNmb)** - A situation you'll need deferred css for the Exit transition.
220 |
221 | ## Bugs & Questions
222 |
223 | Do you have questions? Or think you've found a bug? Please file an issue att https://github.com/hyperapp/transitions
224 |
225 | Or come join the us on the [Hyperapp Slack](https://hyperappjs.herokuapp.com)!
226 |
227 | ## License
228 |
229 | Hyperapp Transitions is MIT licensed. See [LICENSE](LICENSE.md).
230 |
--------------------------------------------------------------------------------
/dist/transitions.js:
--------------------------------------------------------------------------------
1 | !function(n,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t(n.transitions={})}(this,function(n){"use strict";function t(n,t){Object.keys(t).forEach(function(e){n.style[e]=t[e]})}function e(){a.forEach(r)}function o(n){var t=n._x,e=n._y;if(!t)return"translate(0, 0)";var o=r(n);return"translate("+Math.floor(t-o.x)+"px, "+Math.floor(e-o.y)+"px)"}function r(n){var t=n.getBoundingClientRect();return n._x=t.left,n._y=t.top,{x:t.left,y:t.top}}function u(n,e,o,r,u){var i=e.easing||"linear",f=e.time||300,c=e.delay||0;t(n,o),setTimeout(function(){requestAnimationFrame(function(){t(n,r),n.style.transition="all "+i+" "+f+"ms",setTimeout(function(){n.style.transition=null,u&&u()},f)})},c)}function i(n,t,e,r){"function"==typeof e&&(e=e()),function(n){var t=a.indexOf(n);-1!==t&&a.splice(t,1)}(n);var i=o(n);e.transform=i+(e.transform?" "+e.transform:""),u(n,t,{transform:i},e,r)}function f(){}function c(n){return function(t,e){var o=n(t||{});return e.filter(function(n){return!!n.attributes}).map(function(n){return["oncreate","onupdate","onremove"].forEach(function(t){n.attributes[t]=function(n,t){return n?t?function(e,o){return n&&n(e,o),t&&t(e,o),f}:n:t}(n.attributes[t],o[t])}),n})}}addEventListener("resize",e),addEventListener("scroll",e);var a=[],s=c(function(n){return{oncreate:function(n){!function(n){a.indexOf(n)>-1||(a.push(n),setTimeout(function(){r(n)},0))}(n)}}}),l=c(function(n){return{onupdate:function(t){!function(n,t){u(n,t,{transform:o(n)},{transform:null})}(t,n)}}}),d=c(function(n){return{onremove:function(t,e){e=e||function(){!function(n){n.parentNode.removeChild(n)}(t)},i(t,n,n.css||{},!n.keep&&e)}}}),p=c(function(n){return{oncreate:function(t){!function(n,t,e){"function"==typeof e&&(e=e()),u(n,t,e,Object.keys(e).reduce(function(n,t){return n[t]=null,n},{}),function(){r(n)})}(t,n,n.css||{})}}});n.Enter=p,n.Move=function(n,t){return l(n,s(null,t))},n.Exit=function(n,t){return d(n,s(null,t))},Object.defineProperty(n,"__esModule",{value:!0})});
2 | //# sourceMappingURL=transitions.js.map
--------------------------------------------------------------------------------
/dist/transitions.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["dist/transitions.js"],"names":["global","factory","exports","module","define","amd","transitions","this","setStyle","el","attr","Object","keys","forEach","name","style","updateAllTracked","trackingRegistry","updateTracking","invertLastMove","x","_x","y","_y","n","Math","floor","rect","getBoundingClientRect","left","top","runTransition","before","after","ondone","easing","time","delay","setTimeout","requestAnimationFrame","transition","runExit","css","done","i","indexOf","splice","unregisterTracking","translation","transform","noop","transitionComponent","handlersFn","children","handlers","filter","child","attributes","map","f1","f2","composeHandlers","addEventListener","_track","oncreate","push","registerTracking","_move","onupdate","runMove","_exit","onremove","parentNode","removeChild","removeElement","keep","Enter","reduce","o","runEnter","Move","Exit","defineProperty","value"],"mappings":"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","sourcesContent":["(function (global, factory) {\n\ttypeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :\n\ttypeof define === 'function' && define.amd ? define(['exports'], factory) :\n\t(factory((global.transitions = {})));\n}(this, (function (exports) { 'use strict';\n\naddEventListener('resize', updateAllTracked);\naddEventListener('scroll', updateAllTracked);\n\nvar trackingRegistry = [];\n\nfunction removeElement (el) {\n el.parentNode.removeChild(el);\n}\n\nfunction setStyle (el, attr) {\n Object.keys(attr)\n .forEach(function (name) {\n el.style[name] = attr[name];\n });\n}\n\nfunction registerTracking (el) {\n if (trackingRegistry.indexOf(el) > -1 ) return\n trackingRegistry.push(el);\n setTimeout(function () {\n updateTracking(el);\n }, 0);\n}\n\nfunction unregisterTracking(el) {\n var i = trackingRegistry.indexOf(el);\n if (i === -1) return\n trackingRegistry.splice(i, 1);\n}\n\nfunction updateAllTracked () {\n trackingRegistry.forEach(updateTracking); \n} \n\nfunction invertLastMove (el) {\n var x = el._x;\n var y = el._y;\n if (!x) return 'translate(0, 0)'\n var n = updateTracking(el);\n var dx = Math.floor(x - n.x);\n var dy = Math.floor(y - n.y);\n return 'translate(' + dx + 'px, ' + dy + 'px)'\n}\n\nfunction updateTracking (el) {\n var rect = el.getBoundingClientRect();\n el._x = rect.left;\n el._y = rect.top;\n return {x: rect.left, y: rect.top}\n}\n\nfunction runTransition (el, attr, before, after, ondone) {\n var easing = attr.easing || 'linear';\n var time = attr.time || 300;\n var delay = attr.delay || 0;\n setStyle(el, before);\n setTimeout(function () {\n requestAnimationFrame(function () {\n setStyle(el, after);\n el.style.transition = 'all ' + easing + ' ' + time + 'ms';\n setTimeout(function () {\n el.style.transition = null;\n ondone && ondone();\n }, time);\n });\n }, delay);\n}\n\nfunction runEnter (el, attr, css) {\n if (typeof css === 'function') css = css();\n runTransition(\n el, attr,\n css,\n Object.keys(css).reduce(function (o, n) {\n o[n] = null;\n return o\n }, {}),\n function () { updateTracking(el); }\n );\n}\n\nfunction runMove (el, attr) {\n runTransition(\n el, attr,\n {transform: invertLastMove(el)},\n {transform: null}\n );\n}\n\nfunction runExit (el, attr, css, done) {\n if (typeof css === 'function') css = css();\n unregisterTracking(el);\n var translation = invertLastMove(el);\n css.transform = translation + (css.transform ? (' ' + css.transform) : '');\n runTransition(\n el, attr,\n {transform: translation},\n css,\n done\n );\n}\n \nfunction noop () {}\n \nfunction composeHandlers (f1, f2) {\n if (!f1) return f2\n if (!f2) return f1\n return function (el, done) {\n f1 && f1(el, done);\n f2 && f2(el, done);\n return noop\n }\n}\n\nfunction transitionComponent (handlersFn) {\n return function (attr, children) {\n var handlers = handlersFn(attr || {});\n return children\n .filter(function (child) { return !!child.attributes})\n .map(function (child) {\n ['oncreate', 'onupdate', 'onremove'].forEach(function (n) {\n child.attributes[n] = composeHandlers(child.attributes[n], handlers[n]);\n }); \n return child\n })\n }\n}\n\nvar _track = transitionComponent(function (attr) { \n return {oncreate: function (el) { registerTracking(el);} }\n});\n\nvar _move = transitionComponent(function (attr) { \n return { onupdate: function (el) { runMove(el, attr); } }\n});\n\nvar _exit = transitionComponent(function (attr) {\n return {\n onremove: function (el, done) {\n done = done || function () { removeElement(el); };\n runExit(el, attr, attr.css || {}, !attr.keep && done);\n }\n }\n});\n\nvar Enter = transitionComponent(function (attr) {\n return { oncreate: function (el) { runEnter(el, attr, attr.css || {}); } }\n});\n\nvar Move = function (attr, children) {\n return _move(attr, _track(null, children))\n};\n\nvar Exit = function (attr, children) {\n return _exit(attr, _track(null, children))\n};\n\nexports.Enter = Enter;\nexports.Move = Move;\nexports.Exit = Exit;\n\nObject.defineProperty(exports, '__esModule', { value: true });\n\n})));\n//# sourceMappingURL=transitions.js.map\n"]}
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hyperapp-transitions",
3 | "version": "1.0.2",
4 | "description": "Transitions for Hyperapp",
5 | "main": "dist/transitions.js",
6 | "module": "src/index.js",
7 | "scripts": {
8 | "umd": "rollup src/index.js -f umd -n transitions -m -o dist/transitions.js",
9 | "minify": "uglifyjs dist/transitions.js -o dist/transitions.js -mc --source-map includeSources,url=transitions.js.map",
10 | "build": "npm run umd && npm run minify",
11 | "test": "npm run build && ava test/*.js"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/HyperappCommunity/transitions.git"
16 | },
17 | "author": "Zacharias Enochsson",
18 | "license": "MIT",
19 | "bugs": {
20 | "url": "https://github.com/HyperappCommunity/transitions/issues"
21 | },
22 | "homepage": "https://github.com/HyperappCommunity/transitions#readme",
23 | "devDependencies": {
24 | "ava": "^1.0.0-beta.3",
25 | "hyperapp": "^1.1.2",
26 | "jsdom": "^11.6.2",
27 | "rollup": "^0.52.2",
28 | "uglify-js": "^3.2.2"
29 | },
30 | "dependencies": {}
31 | }
32 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | addEventListener('resize', updateAllTracked)
2 | addEventListener('scroll', updateAllTracked)
3 |
4 | var trackingRegistry = []
5 |
6 | function removeElement (el) {
7 | el.parentNode.removeChild(el)
8 | }
9 |
10 | function setStyle (el, attr) {
11 | Object.keys(attr)
12 | .forEach(function (name) {
13 | el.style[name] = attr[name]
14 | })
15 | }
16 |
17 | function registerTracking (el) {
18 | if (trackingRegistry.indexOf(el) > -1 ) return
19 | trackingRegistry.push(el)
20 | setTimeout(function () {
21 | updateTracking(el)
22 | }, 0)
23 | }
24 |
25 | function unregisterTracking(el) {
26 | var i = trackingRegistry.indexOf(el)
27 | if (i === -1) return
28 | trackingRegistry.splice(i, 1)
29 | }
30 |
31 | function updateAllTracked () {
32 | trackingRegistry.forEach(updateTracking)
33 | }
34 |
35 | function invertLastMove (el) {
36 | var x = el._x
37 | var y = el._y
38 | if (!x) return 'translate(0, 0)'
39 | var n = updateTracking(el)
40 | var dx = Math.floor(x - n.x)
41 | var dy = Math.floor(y - n.y)
42 | return 'translate(' + dx + 'px, ' + dy + 'px)'
43 | }
44 |
45 | function updateTracking (el) {
46 | var rect = el.getBoundingClientRect()
47 | el._x = rect.left
48 | el._y = rect.top
49 | return {x: rect.left, y: rect.top}
50 | }
51 |
52 | function runTransition (el, attr, before, after, ondone) {
53 | var easing = attr.easing || 'linear'
54 | var time = attr.time || 300
55 | var delay = attr.delay || 0
56 | setStyle(el, before)
57 | setTimeout(function () {
58 | requestAnimationFrame(function () {
59 | setStyle(el, after)
60 | el.style.transition = 'all ' + easing + ' ' + time + 'ms'
61 | setTimeout(function () {
62 | el.style.transition = null
63 | ondone && ondone()
64 | }, time)
65 | })
66 | }, delay)
67 | }
68 |
69 | function runEnter (el, attr, css) {
70 | if (typeof css === 'function') css = css()
71 | runTransition(
72 | el, attr,
73 | css,
74 | Object.keys(css).reduce(function (o, n) {
75 | o[n] = null
76 | return o
77 | }, {}),
78 | function () { updateTracking(el) }
79 | )
80 | }
81 |
82 | function runMove (el, attr) {
83 | runTransition(
84 | el, attr,
85 | {transform: invertLastMove(el)},
86 | {transform: null}
87 | )
88 | }
89 |
90 | function runExit (el, attr, css, done) {
91 | if (typeof css === 'function') css = css()
92 | unregisterTracking(el)
93 | var translation = invertLastMove(el)
94 | css.transform = translation + (css.transform ? (' ' + css.transform) : '')
95 | runTransition(
96 | el, attr,
97 | {transform: translation},
98 | css,
99 | done
100 | )
101 | }
102 |
103 | function noop () {}
104 |
105 | function composeHandlers (f1, f2) {
106 | if (!f1) return f2
107 | if (!f2) return f1
108 | return function (el, done) {
109 | f1 && f1(el, done)
110 | f2 && f2(el, done)
111 | return noop
112 | }
113 | }
114 |
115 | function transitionComponent (handlersFn) {
116 | return function (attr, children) {
117 | var handlers = handlersFn(attr || {})
118 | return children
119 | .filter(function (child) { return !!child.attributes})
120 | .map(function (child) {
121 | ['oncreate', 'onupdate', 'onremove'].forEach(function (n) {
122 | child.attributes[n] = composeHandlers(child.attributes[n], handlers[n])
123 | })
124 | return child
125 | })
126 | }
127 | }
128 |
129 | var _track = transitionComponent(function (attr) {
130 | return {oncreate: function (el) { registerTracking(el)} }
131 | })
132 |
133 | var _move = transitionComponent(function (attr) {
134 | return { onupdate: function (el) { runMove(el, attr) } }
135 | })
136 |
137 | var _exit = transitionComponent(function (attr) {
138 | return {
139 | onremove: function (el, done) {
140 | done = done || function () { removeElement(el) }
141 | runExit(el, attr, attr.css || {}, !attr.keep && done)
142 | }
143 | }
144 | })
145 |
146 | var Enter = transitionComponent(function (attr) {
147 | return { oncreate: function (el) { runEnter(el, attr, attr.css || {}) } }
148 | })
149 |
150 | var Move = function (attr, children) {
151 | return _move(attr, _track(null, children))
152 | }
153 |
154 | var Exit = function (attr, children) {
155 | return _exit(attr, _track(null, children))
156 | }
157 |
158 | export {Enter, Move, Exit}
--------------------------------------------------------------------------------
/test/test.js:
--------------------------------------------------------------------------------
1 | import {JSDOM} from 'jsdom'
2 | const dom = new JSDOM('')
3 | global.window = dom.window
4 | global.document = dom.window.document
5 | global.addEventListener = dom.window.addEventListener
6 | global.requestAnimationFrame = fn => setTimeout(fn, 0)
7 |
8 | import test from 'ava'
9 | import {h, app} from 'hyperapp'
10 | const {Enter, Exit, Move} = require('../dist/transitions.js')
11 |
12 | function createContainer () {
13 | const el = document.createElement('div')
14 | document.body.appendChild(el)
15 | return el
16 | }
17 |
18 | test.cb('Enter', t => {
19 | const el = createContainer()
20 | app({}, {}, _ => h('main', {}, [
21 | Enter({css:{prop:'foo'}, time: 100, easing: 'bar', delay: 100}, [
22 | h('div', {id: 'target'}, ['foo'])
23 | ])
24 | ]), el)
25 | setTimeout(_ => {
26 | setTimeout(_ => {
27 | t.is(el.querySelector('#target').style.prop, 'foo')
28 | t.falsy(el.querySelector('#target').style.transition)
29 | setTimeout(_ => {
30 | t.falsy(el.querySelector('#target').style.prop)
31 | t.is(el.querySelector('#target').style.transition, 'all bar 100ms')
32 | setTimeout(_ => {
33 | t.falsy(el.querySelector('#target').style.transition)
34 | t.falsy(el.querySelector('#target').style.prop)
35 | t.end()
36 | }, 100)
37 | }, 100)
38 | }, 50)
39 | }, 0)
40 | })
41 |
42 | test.cb('Enter - deferred css', t => {
43 | const el = createContainer()
44 | app({}, {}, _ => h('main', {}, [
45 | Enter({css:_ => ({prop: 'foo'})}, [
46 | h('div', {id: 'target'}, ['foo'])
47 | ])
48 | ]), el)
49 | setTimeout(_ => {
50 | setTimeout(_ => {
51 | t.is(el.querySelector('#target').style.prop, 'foo')
52 | t.end()
53 | }, 0)
54 | }, 0)
55 | })
56 |
57 |
58 |
59 | test.cb('Exit', t => {
60 | const el = createContainer()
61 | const {toggle} = app(
62 | {show: true},
63 | {toggle: _ => ({show: false})},
64 | (state, actions) => h('main', {}, [
65 | state.show && Exit({
66 | css: {prop: 'foo'},
67 | easing: 'bar',
68 | time: 100,
69 | delay: 100
70 | }, [
71 | h('div', {id: 'target'}, ['foo'])
72 | ])
73 | ]),
74 | el
75 | )
76 | //render once
77 | setTimeout(_ => {
78 | toggle() //make element go away
79 | //render again
80 | setTimeout(_ => {
81 | t.falsy(el.querySelector('#target').style.prop)
82 | t.falsy(el.querySelector('#target').style.transition)
83 | setTimeout(_ => {
84 | t.is(el.querySelector('#target').style.prop, 'foo')
85 | t.is(el.querySelector('#target').style.transition, 'all bar 100ms')
86 | setTimeout(_ => {
87 | t.falsy(el.querySelector('#target'))
88 | t.end()
89 | }, 150)
90 | }, 100)
91 | }, 50)
92 | }, 0)
93 | })
94 |
95 |
96 |
97 | test.cb('Exit - deferred css', t => {
98 | const el = createContainer()
99 | const {toggle} = app(
100 | {show: true},
101 | {toggle: _ => ({show: false})},
102 | (state, actions) => h('main', {}, [
103 | state.show && Exit({
104 | css: _ => ({prop: 'foo'}),
105 | easing: 'bar',
106 | time: 100,
107 | delay: 100
108 | }, [
109 | h('div', {id: 'target'}, ['foo'])
110 | ])
111 | ]),
112 | el
113 | )
114 | //render once
115 | setTimeout(_ => {
116 | toggle() //make element go away
117 | setTimeout(_ => {
118 | t.is(el.querySelector('#target').style.prop, 'foo')
119 | t.end()
120 | }, 200)
121 | }, 0)
122 | })
123 |
124 |
125 | test.cb('Exit - keep: true', t => {
126 | const el = createContainer()
127 | const {toggle} = app(
128 | {show: true},
129 | {toggle: _ => ({show: false})},
130 | (state, actions) => h('main', {}, [
131 | state.show && Exit({
132 | css: {prop: 'foo'},
133 | time: 100,
134 | keep: true,
135 | }, [
136 | h('div', {id: 'target'}, ['foo'])
137 | ])
138 | ]),
139 | el
140 | )
141 | //render once
142 | setTimeout(_ => {
143 | toggle() //make element go away
144 | //render again
145 | setTimeout(_ => {
146 | t.truthy(el.querySelector('#target'))
147 | t.end()
148 | }, 150)
149 | }, 0)
150 | })
151 |
152 | test.cb('Move', t => {
153 | const el = createContainer()
154 | const {toggle} = app(
155 | {order: true},
156 | {toggle: _ => ({order: false})},
157 | (state, actions) => h('main', {}, Move({
158 | time: 200,
159 | easing: 'bar'
160 | }, (state.order
161 | ? [
162 | h('p', {key: 'first', id: 'first'}, []),
163 | h('p', {key: 'second', id: 'second'}, []),
164 | ]
165 | : [
166 | h('p', {key: 'second', id: 'second'}, []),
167 | h('p', {key: 'first', id: 'first'}, []),
168 | ]
169 | ))),
170 | el
171 | )
172 | setTimeout(_ => {
173 | toggle()
174 | setTimeout(_ => {
175 | t.is(el.querySelector('#first').style.transition, 'all bar 200ms')
176 | t.is(el.querySelector('#second').style.transition, 'all bar 200ms')
177 | setTimeout(_ => {
178 | t.falsy(el.querySelector('#first').style.transition)
179 | t.falsy(el.querySelector('#second').style.transition)
180 | t.end()
181 | }, 150)
182 | }, 100)
183 | }, 0)
184 | })
--------------------------------------------------------------------------------