Skip to content
This repository was archived by the owner on Apr 8, 2020. It is now read-only.

Commit 1412c9f

Browse files
committed
Support class based transitions
1 parent 3f41255 commit 1412c9f

21 files changed

+928
-434
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/**
2+
* @license
3+
* Copyright (C) 2016-present Chi Vinh Le and contributors.
4+
*
5+
* This software may be modified and distributed under the terms
6+
* of the MIT license. See the LICENSE file for details.
7+
*/
8+
9+
import * as React from "react";
10+
import { assert } from "chai";
11+
import { shallow } from "enzyme";
12+
import { assemble } from "react-assemble";
13+
14+
import { mergeWithBaseStyle } from "./mergeWithBaseStyle";
15+
import { Component } from "../../test/component";
16+
17+
describe("mergeWithBaseStyle", () => {
18+
const composable = mergeWithBaseStyle;
19+
const Assembly = assemble<any, any>(composable)(Component);
20+
21+
it("should merge base style with transition style", () => {
22+
const input = {
23+
style: { top: 0, left: 2 },
24+
transitionState: { style: { left: 10 } },
25+
};
26+
const output = {
27+
...input,
28+
style: { top: 0, left: 10 },
29+
className: "",
30+
};
31+
const wrapper = shallow(<Assembly {...input} />);
32+
assert.deepEqual(wrapper.props(), output);
33+
});
34+
35+
it("should merge base className with transition className", () => {
36+
const input = {
37+
className: "baseClass",
38+
transitionState: { className: "transitionClass" },
39+
};
40+
const output = {
41+
...input,
42+
style: {},
43+
className: "baseClass transitionClass",
44+
};
45+
const wrapper = shallow(<Assembly {...input} />);
46+
assert.deepEqual(wrapper.props(), output);
47+
});
48+
49+
it("should merge both", () => {
50+
const input = {
51+
style: { top: 0, left: 2 },
52+
className: "baseClass",
53+
transitionState: { style: { left: 10 }, className: "transitionClass" },
54+
};
55+
const output = {
56+
...input,
57+
style: { top: 0, left: 10 },
58+
className: "baseClass transitionClass",
59+
};
60+
const wrapper = shallow(<Assembly {...input} />);
61+
assert.deepEqual(wrapper.props(), output);
62+
});
63+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* @license
3+
* Copyright (C) 2016-present Chi Vinh Le and contributors.
4+
*
5+
* This software may be modified and distributed under the terms
6+
* of the MIT license. See the LICENSE file for details.
7+
*/
8+
9+
import { combine, withProps } from "react-assemble";
10+
11+
const mergeClasses = (...classes: string[]) => classes.filter((s) => s).join(" ");
12+
13+
export const mergeWithBaseStyle = combine(
14+
withProps<any, any>(({ transitionState, style, className }: any) => ({
15+
style: { ...style, ...transitionState.style },
16+
className: mergeClasses(className, transitionState.className),
17+
})),
18+
);

src/composables/mergeWithStyle.spec.tsx

Lines changed: 0 additions & 31 deletions
This file was deleted.

src/composables/mergeWithStyle.tsx

Lines changed: 0 additions & 14 deletions
This file was deleted.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* @license
3+
* Copyright (C) 2016-present Chi Vinh Le and contributors.
4+
*
5+
* This software may be modified and distributed under the terms
6+
* of the MIT license. See the LICENSE file for details.
7+
*/
8+
9+
import * as React from "react";
10+
import { assert } from "chai";
11+
import { mount } from "enzyme";
12+
import { assemble } from "react-assemble";
13+
14+
import { withDOMNodeCallback } from "./withDOMNodeCallback";
15+
16+
class Component extends React.Component<any, any> {
17+
public render(): any {
18+
return <span ref={this.props.onDOMNodeRef} />;
19+
}
20+
}
21+
22+
describe("withDOMNodeCallback", () => {
23+
const composable = withDOMNodeCallback;
24+
const Assembly = assemble<any, any>(composable)(Component);
25+
26+
it("should provide callback", () => {
27+
const wrapper = mount(<Assembly />);
28+
assert.strictEqual(wrapper.find(Component).props().getDOMNode().nodeName, "SPAN");
29+
});
30+
});
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* @license
3+
* Copyright (C) 2016-present Chi Vinh Le and contributors.
4+
*
5+
* This software may be modified and distributed under the terms
6+
* of the MIT license. See the LICENSE file for details.
7+
*/
8+
9+
import { withHandlers } from "react-assemble";
10+
11+
export const withDOMNodeCallback =
12+
withHandlers<any, any>(() => {
13+
let ref: Element;
14+
return {
15+
onDOMNodeRef: () => (e: Element) => {
16+
ref = e;
17+
},
18+
getDOMNode: () => () => ref,
19+
};
20+
});

src/composables/withTransitionInfo.spec.tsx

Lines changed: 85 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,46 +8,105 @@
88

99
import * as React from "react";
1010
import { assert } from "chai";
11-
import { shallow } from "enzyme";
11+
import { shallow, mount } from "enzyme";
1212
import { assemble } from "react-assemble";
1313

1414
import { withTransitionInfo } from "./withTransitionInfo";
1515
import { Component } from "../../test/component";
1616

17+
function addCSS(css: string): HTMLElement {
18+
const head = document.getElementsByTagName("head")[0];
19+
const s = document.createElement("style");
20+
s.setAttribute("type", "text/css");
21+
s.appendChild(document.createTextNode(css));
22+
head.appendChild(s);
23+
return s;
24+
}
25+
26+
const css =
27+
`
28+
.transition {
29+
transition-property: height, width;
30+
transition-duration: 2ms, 10s;
31+
transition-delay: 2s, 10ms;
32+
transition-timing-function: linear, ease;
33+
}
34+
`;
35+
1736
describe("withTransitionInfo", () => {
18-
it("should pass transitionInfo for ongoing transition", () => {
19-
const input = {
20-
style: {
21-
transition: "height 2ms 2s linear, width ease 10s 10ms",
22-
},
23-
};
24-
const output = {
25-
...input,
26-
transitionInfo: {
27-
firstProperty: "width",
28-
firstPropertyDelay: 10,
29-
lastProperty: "width",
30-
inTransition: true,
31-
},
32-
};
33-
const composable = withTransitionInfo;
34-
const Assembly = assemble<any, any>(composable)(Component);
35-
const wrapper = shallow(<Assembly {...input} />);
36-
assert.deepEqual(wrapper.props(), output);
37+
const composable = withTransitionInfo;
38+
const Assembly = assemble<any, any>(composable)(Component);
39+
40+
describe("style based", () => {
41+
it("should pass transitionInfo for ongoing transition", () => {
42+
const input = {
43+
style: {
44+
transition: "height 2ms 2s linear, width ease 10s 10ms",
45+
},
46+
transitionState: {
47+
inTransition: true,
48+
},
49+
};
50+
const output = {
51+
...input,
52+
transitionInfo: {
53+
firstProperty: "width",
54+
firstPropertyDelay: 10,
55+
lastProperty: "width",
56+
},
57+
};
58+
const wrapper = shallow(<Assembly {...input} />);
59+
assert.deepEqual(wrapper.props(), output);
60+
});
61+
});
62+
63+
describe("class based", () => {
64+
let div: HTMLElement;
65+
let style: HTMLElement;
66+
67+
before(() => {
68+
style = addCSS(css);
69+
div = document.createElement("div");
70+
document.body.appendChild(div);
71+
});
72+
73+
after(() => {
74+
div.parentNode.removeChild(div);
75+
style.parentNode.removeChild(style);
76+
});
77+
78+
it("should pass transitionInfo for ongoing transition", () => {
79+
const input = {
80+
className: "transition",
81+
getDOMNode: () => div,
82+
transitionState: {
83+
inTransition: true,
84+
},
85+
};
86+
const output = {
87+
...input,
88+
transitionInfo: {
89+
firstProperty: "width",
90+
firstPropertyDelay: 10,
91+
lastProperty: "width",
92+
},
93+
};
94+
const wrapper = mount(<Assembly {...input} />);
95+
assert.deepEqual(wrapper.find(Component).props(), output);
96+
});
3797
});
3898

39-
it("should pass transitionInfo for absent transition", () => {
99+
it("should pass empty transitionInfo for absent transition", () => {
40100
const input = {
41101
style: {},
102+
transitionState: {
103+
inTransition: false,
104+
},
42105
};
43106
const output = {
44107
...input,
45-
transitionInfo: {
46-
inTransition: false,
47-
},
108+
transitionInfo: {},
48109
};
49-
const composable = withTransitionInfo;
50-
const Assembly = assemble<any, any>(composable)(Component);
51110
const wrapper = shallow(<Assembly {...input} />);
52111
assert.deepEqual(wrapper.props(), output);
53112
});

src/composables/withTransitionInfo.ts

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,43 @@
66
* of the MIT license. See the LICENSE file for details.
77
*/
88

9-
import { withProps } from "react-assemble";
9+
import { withProps, withHandlers, isolate, integrate } from "react-assemble";
1010

11-
import parseTransition from "../utils/parseTransition";
11+
import { parseTransition, TransitionEntry } from "../utils/parseTransition";
12+
import { parseComputedTransition } from "../utils/parseComputedTransition";
13+
import { memoize } from "../utils/memoize";
1214

1315
export const withTransitionInfo =
14-
withProps<any, any>(({style}: any) => {
15-
if (style.transition) {
16-
const [{delay: firstPropertyDelay, property: firstProperty}, {property: lastProperty}] =
17-
parseTransition(style.transition);
16+
isolate(
17+
withHandlers<any, any>(() => {
18+
const memoized = memoize(
19+
(node: Element) => parseComputedTransition(getComputedStyle(node)),
20+
(node: Element) => node.className,
21+
);
1822
return {
19-
transitionInfo: {
20-
firstPropertyDelay,
21-
firstProperty,
22-
lastProperty,
23-
inTransition: true,
24-
},
23+
parseComputedTransitionMemoized: () => memoized,
2524
};
26-
}
27-
return { transitionInfo: { inTransition: false } };
28-
});
25+
}),
26+
withProps<any, any>(({style, className, transitionState, getDOMNode, parseComputedTransitionMemoized}: any) => {
27+
if (transitionState.inTransition) {
28+
let parsed: [TransitionEntry, TransitionEntry];
29+
if (style && style.transition) {
30+
parsed = parseTransition(style.transition);
31+
} else {
32+
const node = getDOMNode();
33+
node.className = className;
34+
parsed = parseComputedTransitionMemoized(node);
35+
}
36+
const [{delay: firstPropertyDelay, property: firstProperty}, {property: lastProperty}] = parsed;
37+
return {
38+
transitionInfo: {
39+
firstPropertyDelay,
40+
firstProperty,
41+
lastProperty,
42+
},
43+
};
44+
}
45+
return { transitionInfo: {} };
46+
}),
47+
integrate<any>("transitionInfo"),
48+
);

0 commit comments

Comments
 (0)