/
flip.ts
151 lines (126 loc) · 3.89 KB
/
flip.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
import { value, Action, ColdSubscription } from 'popmotion';
import {
BoundingBox,
Dimensions,
Value,
Pose,
PoserState,
DomPopmotionPoser
} from '../types';
import { resolveProp, measureWithoutTransform } from './utils';
import { Poser } from 'pose-core';
const ORIGIN_START = 0;
const ORIGIN_CENTER = '50%';
const ORIGIN_END = '100%';
type StyleMap = { [key: string]: any };
const findCenter = ({ top, right, bottom, left }: BoundingBox) => ({
x: (left + right) / 2,
y: (top + bottom) / 2
});
const positionalProps = ['width', 'height', 'top', 'left', 'bottom', 'right'];
const positionalPropsDict = new Set(positionalProps);
const checkPositionalProp = (key: string) => positionalPropsDict.has(key);
const hasPositionalProps = (pose: Pose) =>
Object.keys(pose).some(checkPositionalProp);
export const isFlipPose = (flip: boolean, key: string, state: PoserState) =>
state.props.element instanceof HTMLElement &&
(flip === true || key === 'flip');
export const setValue = (
{ values, props }: PoserState,
key: string,
to: any
) => {
if (values.has(key)) {
// Here, if we already have the value, we update it twice.
// Because of stylefire's render batching, this isn't going
// to actually render twice, but because we're making
// the value jump a great distance, we want to reset the velocity
// to 0, rather than something arbitrarily high
// A more explicit API would be nicer
const { raw } = values.get(key);
raw.update(to);
raw.update(to);
} else {
values.set(key, {
raw: value(to, (v: any) => props.elementStyler.set(key, v))
});
}
};
const explicitlyFlipPose = (state: PoserState, nextPose: Pose) => {
const { dimensions, elementStyler } = state.props;
dimensions.measure();
const {
width,
height,
top,
left,
bottom,
right,
position,
...remainingPose
} = nextPose;
const propsToSet = positionalProps.concat('position').reduce(
(acc, key) => {
if (nextPose[key] !== undefined) {
acc[key] = resolveProp(nextPose[key], state.props);
}
return acc;
},
{} as StyleMap
);
elementStyler.set(propsToSet).render();
return implicitlyFlipPose(state, remainingPose);
};
const implicitlyFlipPose = (state: PoserState, nextPose: Pose) => {
const { dimensions, element, elementStyler } = state.props;
if (!dimensions.has()) return {};
const prev = dimensions.get() as BoundingBox;
const next = measureWithoutTransform(element);
// Find transform origin based on x/y delta
const originX =
prev.left === next.left
? ORIGIN_START
: prev.right === next.right ? ORIGIN_END : ORIGIN_CENTER;
const originY =
prev.top === next.top
? ORIGIN_START
: prev.bottom === next.bottom ? ORIGIN_END : ORIGIN_CENTER;
// Set transform origins
elementStyler.set({ originX, originY });
// Set initial offsets to replicate previous position with transforms
if (prev.width !== next.width) {
setValue(state, 'scaleX', prev.width / next.width);
nextPose.scaleX = 1;
}
if (prev.height !== next.height) {
setValue(state, 'scaleY', prev.height / next.height);
nextPose.scaleY = 1;
}
const prevCenter = findCenter(prev);
const nextCenter = findCenter(next);
if (originX === ORIGIN_CENTER) {
setValue(state, 'x', prevCenter.x - nextCenter.x);
nextPose.x = 0;
}
if (originY === ORIGIN_CENTER) {
setValue(state, 'y', prevCenter.y - nextCenter.y);
nextPose.y = 0;
}
// Render the set values
elementStyler.render();
return nextPose;
};
export const flipPose = (props: PoserState, nextPose: Pose) =>
hasPositionalProps(nextPose)
? explicitlyFlipPose(props, nextPose)
: implicitlyFlipPose(props, nextPose);
// Prevents the bug where TS errors between "export cannot be named"
// and import is "declared but unused".
export {
Action,
Dimensions,
ColdSubscription,
DomPopmotionPoser,
Poser,
Value
};