Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/core/animations/cssTransitions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { VNodeProperties } from './../interfaces';
import { SupportedClassName } from './../interfaces';

let browserSpecificTransitionEndEventName = '';
let browserSpecificAnimationEndEventName = '';
Expand Down Expand Up @@ -42,8 +42,8 @@ function runAndCleanUp(element: HTMLElement, startAnimation: () => void, finishA
element.addEventListener(browserSpecificTransitionEndEventName, transitionEnd);
}

function exit(node: HTMLElement, properties: VNodeProperties, exitAnimation: string, removeNode: () => void) {
const activeClass = properties.exitAnimationActive || `${exitAnimation}-active`;
function exit(node: HTMLElement, exitAnimation: string, active?: SupportedClassName) {
const activeClass = active && active !== true ? active : `${exitAnimation}-active`;

runAndCleanUp(
node,
Expand All @@ -55,13 +55,13 @@ function exit(node: HTMLElement, properties: VNodeProperties, exitAnimation: str
});
},
() => {
removeNode();
node && node.parentNode && node.parentNode.removeChild(node);
}
);
}

function enter(node: HTMLElement, properties: VNodeProperties, enterAnimation: string) {
const activeClass = properties.enterAnimationActive || `${enterAnimation}-active`;
function enter(node: HTMLElement, enterAnimation: string, active?: SupportedClassName) {
const activeClass = active && active !== true ? active : `${enterAnimation}-active`;

runAndCleanUp(
node,
Expand Down
40 changes: 10 additions & 30 deletions src/core/interfaces.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ export type ScrollEventHandler = (event?: UIEvent) => EventHandlerResult;
export type SubmitEventHandler = EventHandler;

export interface TransitionStrategy {
enter(element: Element, properties: VNodeProperties, enterAnimation: string): void;
exit(element: Element, properties: VNodeProperties, exitAnimation: string, removeElement: () => void): void;
enter(element: Element, enterAnimation: string, enterAnimationActive?: SupportedClassName): void;
exit(element: Element, exitAnimation: string, exitAnimationActive?: SupportedClassName): void;
}

export interface ProjectorOptions {
Expand Down Expand Up @@ -109,34 +109,14 @@ export interface VDomOptions {
}

export interface VNodeProperties {
/**
* The animation to perform when this node is added to an already existing parent.
* When this value is a string, you must pass a `projectionOptions.transitions` object when creating the
* projector using [[createProjector]].
* @param element - Element that was just added to the DOM.
* @param properties - The properties object that was supplied to the [[h]] method
*/
enterAnimation?: ((element: Element, properties?: VNodeProperties) => void) | SupportedClassName;
/**
* The animation to perform when this node is removed while its parent remains.
* When this value is a string, you must pass a `projectionOptions.transitions` object when creating the projector using [[createProjector]].
* @param element - Element that ought to be removed from the DOM.
* @param removeElement - Function that removes the element from the DOM.
* This argument is provided purely for convenience.
* You may use this function to remove the element when the animation is done.
* @param properties - The properties object that was supplied to the [[v]] method that rendered this [[VNode]] the previous time.
*/
exitAnimation?:
| ((element: Element, removeElement: () => void, properties?: VNodeProperties) => void)
| SupportedClassName;
/**
* The animation to perform when the properties of this node change.
* This also includes attributes, styles, css classes. This callback is also invoked when node contains only text and that text changes.
* @param element - Element that was modified in the DOM.
* @param properties - The last properties object that was supplied to the [[h]] method
* @param previousProperties - The previous properties object that was supplied to the [[h]] method
*/
updateAnimation?: (element: Element, properties?: VNodeProperties, previousProperties?: VNodeProperties) => void;
enterAnimation?: SupportedClassName;

exitAnimation?: SupportedClassName;

enterAnimationActive?: SupportedClassName;

exitAnimationActive?: SupportedClassName;

/**
* Used to uniquely identify a DOM node among siblings.
* A key is required when there are more children with the same selector and these children are added or removed dynamically.
Expand Down
47 changes: 9 additions & 38 deletions src/core/vdom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import {
DeferredVirtualProperties,
DomOptions
} from './interfaces';
import transitionStrategy from './animations/cssTransitions';
import { Registry, isWidget, isWidgetBaseConstructor, isWidgetFunction, isWNodeFactory } from './Registry';
import { auto } from './diff';
import RegistryHandler from './RegistryHandler';
Expand Down Expand Up @@ -105,7 +104,7 @@ export type DNodeWrapper = VNodeWrapper | WNodeWrapper;
export interface MountOptions {
sync: boolean;
merge: boolean;
transition: TransitionStrategy;
transition?: TransitionStrategy;
domNode: HTMLElement;
registry: Registry;
}
Expand Down Expand Up @@ -598,37 +597,6 @@ function updateAttribute(domNode: Element, attrName: string, attrValue: string |
}
}

function runEnterAnimation(next: VNodeWrapper, transitions: TransitionStrategy) {
const {
domNode,
node: { properties },
node: {
properties: { enterAnimation }
}
} = next;
if (enterAnimation && enterAnimation !== true) {
if (typeof enterAnimation === 'function') {
return enterAnimation(domNode as Element, properties);
}
transitions.enter(domNode as Element, properties, enterAnimation);
}
}

function runExitAnimation(current: VNodeWrapper, transitions: TransitionStrategy, exitAnimation: string | Function) {
const {
domNode,
node: { properties }
} = current;
const removeDomNode = () => {
domNode && domNode.parentNode && domNode.parentNode.removeChild(domNode);
current.domNode = undefined;
};
if (typeof exitAnimation === 'function') {
return exitAnimation(domNode as Element, removeDomNode, properties);
}
transitions.exit(domNode as Element, properties, exitAnimation, removeDomNode);
}

function arrayFrom(arr: any) {
return Array.prototype.slice.call(arr);
}
Expand Down Expand Up @@ -816,7 +784,7 @@ export function renderer(renderer: () => RenderResult): Renderer {
let _mountOptions: MountOptions = {
sync: false,
merge: true,
transition: transitionStrategy,
transition: undefined,
domNode: global.document.body,
registry: new Registry()
};
Expand Down Expand Up @@ -1414,7 +1382,10 @@ export function renderer(renderer: () => RenderResult): Renderer {
if ((domNode as HTMLElement).tagName === 'OPTION' && domNode!.parentElement) {
setValue(domNode!.parentElement);
}
runEnterAnimation(next, _mountOptions.transition);
const { enterAnimation, enterAnimationActive } = node.properties;
if (_mountOptions.transition && enterAnimation && enterAnimation !== true) {
_mountOptions.transition.enter(domNode as HTMLElement, enterAnimation, enterAnimationActive);
}
const owningWrapper = _nodeToWrapperMap.get(next.node);
if (owningWrapper && node.properties.key != null) {
if (owningWrapper.instance) {
Expand Down Expand Up @@ -1450,9 +1421,9 @@ export function renderer(renderer: () => RenderResult): Renderer {
}
} else if (item.type === 'delete') {
const { current } = item;
const { exitAnimation } = current.node.properties;
if (exitAnimation && exitAnimation !== true) {
runExitAnimation(current, _mountOptions.transition, exitAnimation);
const { exitAnimation, exitAnimationActive } = current.node.properties;
if (_mountOptions.transition && exitAnimation && exitAnimation !== true) {
_mountOptions.transition.exit(current.domNode as HTMLElement, exitAnimation, exitAnimationActive);
} else {
current.domNode!.parentNode!.removeChild(current.domNode!);
current.domNode = undefined;
Expand Down
44 changes: 9 additions & 35 deletions tests/core/unit/vdom.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const { afterEach, beforeEach, describe, it } = intern.getInterface('bdd');
const { assert } = intern.getPlugin('chai');
const { describe: jsdomDescribe } = intern.getPlugin('jsdom');
import { match, spy, stub, SinonSpy, SinonStub } from 'sinon';
import { spy, stub, SinonSpy, SinonStub } from 'sinon';
import { add } from '../../../src/core/has';
import { createResolvers } from './../support/util';
import sendEvent from '../support/sendEvent';
Expand Down Expand Up @@ -5588,15 +5588,6 @@ jsdomDescribe('vdom', () => {

describe('animations', () => {
describe('enterAnimation', () => {
it('is invoked when a new node is added to an existing parent node', () => {
const enterAnimation = stub();
const [Widget, meta] = getWidget(v('div', []));
const r = renderer(() => w(Widget, {}));
const div = document.createElement('div');
r.mount({ domNode: div, sync: true });
meta.setRenderResult(v('div', [v('span', { enterAnimation })]));
assert.isTrue(enterAnimation.calledWith((div.childNodes[0] as Element).childNodes[0], match({})));
});
it('Does not invoke transition when null passed as enterAnimation', () => {
const transition = {
enter: stub(),
Expand Down Expand Up @@ -5643,20 +5634,6 @@ jsdomDescribe('vdom', () => {
});
});
describe('exitAnimation', () => {
it('is invoked when a node is removed from an existing parent node', () => {
const exitAnimation = stub();
const [Widget, meta] = getWidget(v('div', [v('span', { exitAnimation })]));
const r = renderer(() => w(Widget, {}));
const div = document.createElement('div');
r.mount({ domNode: div, sync: true });
meta.setRenderResult(v('div', []));
assert.isTrue(
exitAnimation.calledWithExactly((div.childNodes[0] as Element).childNodes[0], match({}), match({}))
);
assert.lengthOf((div.childNodes[0] as Element).childNodes, 1);
exitAnimation.lastCall.callArg(1); // arg1: removeElement
assert.lengthOf((div.childNodes[0] as Element).childNodes, 0);
});
it('Does not invoke transition when null passed as exitAnimation', () => {
const transition = {
enter: stub(),
Expand Down Expand Up @@ -5717,8 +5694,8 @@ jsdomDescribe('vdom', () => {
assert.isTrue(
transitionStrategy.enter.calledWithExactly(
(div.childNodes[0] as Element).firstChild,
match({}),
'fadeIn'
'fadeIn',
undefined
)
);
});
Expand All @@ -5732,13 +5709,10 @@ jsdomDescribe('vdom', () => {
assert.isTrue(
transitionStrategy.exit.calledWithExactly(
(div.childNodes[0] as Element).firstChild,
match({}),
'fadeOut',
match({})
undefined
)
);
transitionStrategy.exit.lastCall.callArg(3);
assert.lengthOf((div.childNodes[0] as Element).childNodes, 0);
});
it('Should run enter animations when a widget is added', () => {
const transitionStrategy = { enter: stub(), exit: stub() };
Expand Down Expand Up @@ -5769,16 +5743,16 @@ jsdomDescribe('vdom', () => {
assert.isTrue(
transitionStrategy.enter.calledWithExactly(
(div.childNodes[0] as Element).children[0],
match({}),
'enter'
'enter',
undefined
)
);
addItem();
assert.isTrue(
transitionStrategy.enter.calledWithExactly(
(div.childNodes[0] as Element).children[1],
match({}),
'enter'
'enter',
undefined
)
);
});
Expand Down Expand Up @@ -5810,7 +5784,7 @@ jsdomDescribe('vdom', () => {
r.mount({ domNode: div, sync: true, transition: transitionStrategy });
const node = (div.childNodes[0] as Element).children[1];
removeItem();
assert.isTrue(transitionStrategy.exit.calledWithExactly(node, match({}), 'exit', match({})));
assert.isTrue(transitionStrategy.exit.calledWithExactly(node, 'exit', undefined));
});
});
});
Expand Down