diff --git a/docs/animations-demo-inner/app.css b/docs/animations-demo-inner/app.css
new file mode 100644
index 000000000..911c08db8
--- /dev/null
+++ b/docs/animations-demo-inner/app.css
@@ -0,0 +1,175 @@
+/* Using CSS-vars for transitions so you can experiment easily */
+:root {
+ --infernoAnimationEnter: all 1.2s ease-out;
+ --infernoAnimationLeave: all .6s ease-out;
+}
+
+/*******************************************/
+/* Animate height and opacity of card
*/
+/*******************************************/
+.inner-leave {
+ /* Leave animation start state */
+ opacity: 1;
+ transform: translateX(0);
+}
+
+.inner-leave-active {
+ /* Leave animation transitions */
+ overflow: visible;
+ transition: var(--infernoAnimationLeave);
+ pointer-events: none; /* prevent hover to fire transition events */
+}
+
+.inner-leave-end {
+ /* Leave animation end state */
+ opacity: 0;
+ transform: translateX(-100%);
+}
+
+.inner-enter {
+ /* Enter animation start state */
+ opacity: 0.5 ;
+ transform: translateX(50%);
+}
+
+.inner-enter-active {
+ /* Enter animation transitions */
+ transition: var(--infernoAnimationEnter);
+ pointer-events: none; /* prevent hover to fire transition events */
+}
+
+.inner-enter-end {
+ /* Enter animation end state */
+ opacity: 1;
+ transform: translateX(0);
+}
+
+
+/* Some CSS for the list and cards */
+.page {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+
+.page img {
+ margin: 20vh auto 0;
+ display: block;
+ background-color: white;
+}
+
+.page h3 {
+ background-color: white;
+ margin: 1rem;
+ text-align: center;
+}
+
+.page p {
+ background-color: white;
+ margin: 1rem auto;
+ max-width: 20rem;
+ text-align: left;
+}
+
+.inner {
+ max-width: 30rem;
+ margin: 10vh auto 2rem;
+ padding: 4rem;
+ background-color: #eee;
+}
+
+.inner h2 {
+ font-size: 3em;
+ margin: 3rem auto;
+ text-align: center;
+}
+
+/* Some general CSS for the example */
+
+button {
+ padding: 0.5rem 1rem;
+ margin: 0.5rem auto 0;
+ display: block;
+ background: #35a748;
+ color: white;
+ border-style: none;
+ border-radius: 4px;
+ width: 10rem;
+} button:hover {
+ background: #3775bb;
+}
+
+body {
+ font-family: helvetica;
+ box-sizing: border-box;
+ margin: 0;
+ padding: 1rem;
+ display: flex;
+ gap: 2rem;
+ justify-content: center;
+ align-items: flex-end;
+ height: 100vh;
+ overflow-y: scroll;
+ overflow-x: hidden;
+}
+
+.App {
+ box-sizing: border-box;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: flex-end;
+ padding: 1rem;
+ width: calc(100vw - 2rem);
+ height: 100vh;
+}
+
+h1 {
+ position: absolute;
+ bottom: 0;
+ left: 0;
+ transform: rotate(-90deg);
+ font-size: 2rem;
+ font-weight: 800;
+ color: #ddd;
+ text-align: center;
+ margin: 0.5rem;
+ white-space: nowrap;
+ width: 2rem;
+ transform-origin: 50% 50%;
+}
+
+h1 small {
+ font-size: 0.5em;
+}
+h1 small a {
+ color: #4A90E2;
+ text-decoration: none;
+}
+h1 small a:hover {
+ color: #3775bb;
+}
+
+h2 {
+ font-size: 1rem;
+ font-weight: 100;
+ color: #4A90E2;
+ text-align: center;
+}
+
+h3 {
+ font-size: 0.85rem;
+ font-weight: 100;
+}
+
+p {
+ font-size: 0.85rem;
+ font-weight: 100;
+ color: #888;
+ text-align: center;
+ margin-top: -0.5em;
+ width: 40rem;
+ max-width: 100%;
+}
diff --git a/docs/animations-demo-inner/app.js b/docs/animations-demo-inner/app.js
new file mode 100644
index 000000000..cd4b14952
--- /dev/null
+++ b/docs/animations-demo-inner/app.js
@@ -0,0 +1,90 @@
+(function() {
+ "use strict";
+ var Component = Inferno.Component;
+ var createElement = Inferno.createElement;
+ var createRef = Inferno.createRef;
+ var InfernoAnimation = Inferno.Animation;
+
+ var {
+ AnimatedComponent,
+ componentDidAppear,
+ componentWillDisappear
+ } = InfernoAnimation;
+
+ var {
+ addClassName,
+ removeClassName,
+ forceReflow,
+ registerTransitionListener,
+ } = InfernoAnimation.utils;
+
+ class Page extends Component {
+ componentDidAppear(dom) {
+ // We need to store a reference to the animating child that
+ // isn't removed on unmount. Currently this requires passing
+ // a ref as property and referencing the .current property
+ // of that object.
+ this._innerEl = this.props.innerRef.current;
+ componentDidAppear(this._innerEl, { animation: "inner" });
+ }
+
+ componentWillDisappear(dom, callback) {
+ componentWillDisappear(this._innerEl, { animation: "inner" }, callback);
+ }
+
+ render() {
+ return (
+ createElement('div', {
+ className: "page",
+ }, createElement('div', { className: "random-wrapper" }, [
+ createElement('h3', null, "Page " + this.props.step),
+ createElement('img', { width: "120px", height: "120px", src: "avatar.png" }),
+ createElement('p', null, "The entire page is swapped, but we are only animating div.inner. This gives the apperance of only swapping the box below."),
+ createElement('p', null, "In order not to hide the incoming content we can't set background on div.page. The background needs to be provided by a backdrop in the wizard component."),
+ createElement('div', { ref: this.props.innerRef, className: "inner"}, [
+ createElement('h2', null, "Step " + this.props.step),
+ createElement('button', {onClick: (e) => { e.preventDefault(); this.props.onNext() }}, "Next")
+ ])
+ ])
+ )
+ );
+ }
+ }
+
+ const nrofSteps = 3;
+
+ class Wizard extends Component {
+ constructor() {
+ super();
+
+ // Ref objects used to reference the animating children of each page
+ this._innerAnimRefs = [];
+ for (let i = 0; i < nrofSteps; i++) {
+ this._innerAnimRefs.push(createRef());
+ }
+
+ this.state = {
+ showStepIndex: 0
+ };
+ }
+
+ doGoNext = () => {
+ this.setState({
+ showStepIndex: (this.state.showStepIndex + 1) % nrofSteps
+ })
+ }
+
+ render() {
+ const { showStepIndex } = this.state;
+
+ return createElement(Page, { key: "page_" + showStepIndex, step: showStepIndex + 1, innerRef: this._innerAnimRefs[showStepIndex], onNext: this.doGoNext });
+ }
+ }
+
+
+ document.addEventListener('DOMContentLoaded', function () {
+ var container_1 = document.querySelector('#App1');
+
+ Inferno.render(createElement(Wizard), container_1);
+ });
+})();
diff --git a/docs/animations-demo-inner/avatar.png b/docs/animations-demo-inner/avatar.png
new file mode 100644
index 000000000..c007023c5
Binary files /dev/null and b/docs/animations-demo-inner/avatar.png differ
diff --git a/docs/animations-demo-inner/dist/bundle.js b/docs/animations-demo-inner/dist/bundle.js
new file mode 100644
index 000000000..604d1bf18
--- /dev/null
+++ b/docs/animations-demo-inner/dist/bundle.js
@@ -0,0 +1 @@
+!function(){"use strict";function e(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,n(e,t)}function n(e,t){return n=Object.setPrototypeOf||function(e,n){return e.__proto__=n,e},n(e,t)}!function(){var n=Inferno.Component,t=Inferno.createElement,r=Inferno.createRef,o=Inferno.Animation;o.AnimatedComponent;var i=o.componentDidAppear,p=o.componentWillDisappear,s=o.utils;s.addClassName,s.removeClassName,s.forceReflow,s.registerTransitionListener;var a=function(n){function r(){return n.apply(this,arguments)||this}e(r,n);var o=r.prototype;return o.componentDidAppear=function(e){this._innerEl=this.props.innerRef.current,i(this._innerEl,{prefix:"inner"})},o.componentWillDisappear=function(e,n){p(this._innerEl,{prefix:"inner"},n)},o.render=function(){var e=this;return t("div",{className:"page"},t("div",{className:"random-wrapper"},[t("img",{width:"120px",height:"120px",src:"avatar.png"}),t("div",{ref:this.props.innerRef,className:"inner"},[t("h2",null,"Step "+this.props.step),t("button",{onClick:function(n){n.preventDefault(),e.props.onNext()}},"Next")])]))},r}(n),c=function(n){function o(){var e;(e=n.call(this)||this).doGoNext=function(){e.setState({showStepIndex:(e.state.showStepIndex+1)%3})},e._innerAnimRefs=[];for(var t=0;t<3;t++)e._innerAnimRefs.push(r());return e.state={showStepIndex:0},e}return e(o,n),o.prototype.render=function(){var e=this.state.showStepIndex;return t(a,{key:"page_"+e,step:e+1,innerRef:this._innerAnimRefs[e],onNext:this.doGoNext})},o}(n);document.addEventListener("DOMContentLoaded",(function(){var e=document.querySelector("#App1");Inferno.render(t(c),e)}))}()}();
diff --git a/docs/animations-demo-inner/index.html b/docs/animations-demo-inner/index.html
new file mode 100644
index 000000000..adb212e17
--- /dev/null
+++ b/docs/animations-demo-inner/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+ inferno-animation
+
+
+
+
+
+
+
This demo uses inferno-animation to animate multiple elements of a card when it is added and removed from the DOM. Example | View source... | View CSS...
+
Animations on mount and unmount
- This is an implementation of inferno-animation where components are animated when added and removed from the DOM.
+ This is an implementation of inferno-animation where components are animated when added, removed or moved in the DOM. Example | View source... | View CSS...
diff --git a/packages/inferno-animation/readme.md b/packages/inferno-animation/readme.md
index 2e31aedc0..d25360103 100644
--- a/packages/inferno-animation/readme.md
+++ b/packages/inferno-animation/readme.md
@@ -19,6 +19,8 @@ There are three base components you can extend from to get animations in a strai
- AnimatedMoveComponent -- animates on move (within the same parent)
- AnimatedAllComponent -- animates on add/remove and move (within the same parent)
+You can also animate functional components. There are a couple of examples of animations in the main repos in the `docs/animations` and `docs/animations-demo` folder.
+
If you don't want to extend from one of the pre-wired components, look att src/AnimatedAllComponent.ts to see
how to wire up the three animation hooks:
@@ -26,8 +28,6 @@ how to wire up the three animation hooks:
- componentWillDisappear
- componentWillMove
-There are a couple of examples of animations in the main repos in the `docs/animations` and `docs/animations-demo` folder.
-
Using AnimatedAllComponent is just like working with ordinary components. Don't forget to
add the CSS or you can get strange results:
@@ -93,4 +93,53 @@ import { componentDidAppear, componentWillDisappear, componentWillMove } from 'i
IMPORTANT! Always use the provided helper methods instead of implementing the hooks yourself. There
might be optimisations and/or changes to how the animation hooks are implemented in future versions
-of Inferno that you want to benefit from.
\ No newline at end of file
+of Inferno that you want to benefit from.
+
+### Bootstrap style modal animation
+This is an example of how you could implement a Bootstrap style Modal animation using inferno-animation. These two animations are used both for the backdrop and the modal and the purpose is to support the CSS-rules without modification.
+
+- always use the inferno-animation utility functions
+- implementation is straight forward
+- `callback` in animateModalOnWillDisappear triggers the dom-removal in Inferno and is crucial!
+
+Custom animations won't be coordinated with the standard animations to reduce reflow, but performance is not an issue with just a few animations running simultaneously. Use the standard animations for grid or list items.
+
+Call these helper methods from `componentDidAppear` and `componentWillDisapper` of your backdrop and content component when you build a Bootstrap style modal.
+
+```js
+import { utils } from 'inferno-animation'
+const {
+ addClassName,
+ removeClassName,
+ registerTransitionListener,
+ forceReflow,
+ setDisplay
+} = utils
+
+export function animateModalOnWillDisappear (dom, callback, onClosed) {
+ registerTransitionListener([dom], () => {
+ // Always call the dom removal callback first!
+ callback && callback()
+ onClosed && onClosed()
+ })
+
+ setTimeout(() => {
+ removeClassName(dom, 'show')
+ }, 5)
+}
+
+export function animateModalOnDidAppear (dom, onOpened) {
+ setDisplay(dom, 'none')
+ addClassName(dom, 'fade')
+ forceReflow(dom)
+ setDisplay(dom, undefined)
+
+ registerTransitionListener([dom, dom.children[0]], function () {
+ // *** Cleanup ***
+ setDisplay(dom, undefined)
+ onOpened && onOpened(dom)
+ })
+
+ addClassName(dom, 'show')
+}
+```
\ No newline at end of file
diff --git a/packages/inferno-animation/src/animations.ts b/packages/inferno-animation/src/animations.ts
index fdd3fdc59..b998264c0 100644
--- a/packages/inferno-animation/src/animations.ts
+++ b/packages/inferno-animation/src/animations.ts
@@ -11,7 +11,8 @@ import {
setDisplay,
resetDisplay,
setTransform,
- clearTransform
+ clearTransform,
+ forceReflow
} from './utils';
import { queueAnimation, AnimationPhase } from './animationCoordinator';
import { isNullOrUndef, isNull } from 'inferno-shared';
@@ -72,7 +73,13 @@ function _didAppear(phase: AnimationPhase, dom: HTMLElement, cls: AnimationClass
resetDisplay(dom, display);
return;
case AnimationPhase.MEASURE:
- getDimensions(dom);
+ // In case of img element that hasn't been loaded, just trigger reflow
+ if (dom.tagName !== 'IMG' || (dom as any).complete) {
+ dimensions = getDimensions(dom);
+ } else {
+ //
+ forceReflow();
+ }
return;
case AnimationPhase.SET_START_STATE:
// 1. Set start of animation
@@ -109,7 +116,7 @@ export function componentWillDisappear(dom: HTMLElement, props, callback: Functi
function _willDisappear(phase: AnimationPhase, dom: HTMLElement, callback: Function, cls: AnimationClass, dimensions) {
switch (phase) {
case AnimationPhase.MEASURE:
- // 1. Get dimensions and set animation start state
+ // 1. Set animation start state and dimensions
setDimensions(dom, dimensions.width, dimensions.height);
addClassName(dom, cls.start);
return;
diff --git a/packages/inferno/src/DOM/unmounting.ts b/packages/inferno/src/DOM/unmounting.ts
index c9c471341..adca9cc13 100644
--- a/packages/inferno/src/DOM/unmounting.ts
+++ b/packages/inferno/src/DOM/unmounting.ts
@@ -46,7 +46,7 @@ export function unmount(vNode, animations: AnimationQueues) {
children.componentWillUnmount();
}
- // If we have a componentWillDisappear on this component, block children
+ // If we have a componentWillDisappear on this component, block children from animating
let childAnimations = animations;
if (isFunction(children.componentWillDisappear)) {
childAnimations = new AnimationQueues();
@@ -57,7 +57,7 @@ export function unmount(vNode, animations: AnimationQueues) {
children.$UN = true;
unmount(children.$LI, childAnimations);
} else if (flags & VNodeFlags.ComponentFunction) {
- // If we have a onComponentWillDisappear on this component, block children
+ // If we have a onComponentWillDisappear on this component, block children from animating
let childAnimations = animations;
ref = vNode.ref;
if (!isNullOrUndef(ref)) {