uikit
Version:
UIkit is a lightweight and modular front-end framework for developing fast and powerful web interfaces.
132 lines (101 loc) • 3.96 kB
JavaScript
import {assign, children, css, fastdom, includes, index, isVisible, noop, offset, parent, position, Promise, Transition} from 'uikit-util';
export default function (action, target, duration) {
return new Promise(resolve =>
requestAnimationFrame(() => {
let nodes = children(target);
// Get current state
const currentProps = nodes.map(el => getProps(el, true));
const targetProps = css(target, ['height', 'padding']);
// Cancel previous animations
Transition.cancel(target);
nodes.forEach(Transition.cancel);
reset(target);
// Adding, sorting, removing nodes
action();
// Find new nodes
nodes = nodes.concat(children(target).filter(el => !includes(nodes, el)));
// Wait for update to propagate
Promise.resolve().then(() => {
// Force update
fastdom.flush();
// Get new state
const targetPropsTo = css(target, ['height', 'padding']);
const [propsTo, propsFrom] = getTransitionProps(target, nodes, currentProps);
// Reset to previous state
nodes.forEach((el, i) => propsFrom[i] && css(el, propsFrom[i]));
css(target, assign({display: 'block'}, targetProps));
// Start transitions on next frame
requestAnimationFrame(() => {
const transitions = nodes.map((el, i) =>
parent(el) === target && Transition.start(el, propsTo[i], duration, 'ease')
).concat(Transition.start(target, targetPropsTo, duration, 'ease'));
Promise.all(transitions).then(() => {
nodes.forEach((el, i) => parent(el) === target && css(el, 'display', propsTo[i].opacity === 0 ? 'none' : ''));
reset(target);
}, noop).then(resolve);
});
});
}));
}
function getProps(el, opacity) {
const zIndex = css(el, 'zIndex');
return isVisible(el)
? assign({
display: '',
opacity: opacity ? css(el, 'opacity') : '0',
pointerEvents: 'none',
position: 'absolute',
zIndex: zIndex === 'auto' ? index(el) : zIndex
}, getPositionWithMargin(el))
: false;
}
function getTransitionProps(target, nodes, currentProps) {
const propsTo = nodes.map((el, i) =>
parent(el) && i in currentProps
? currentProps[i]
? isVisible(el)
? getPositionWithMargin(el)
: {opacity: 0}
: {opacity: isVisible(el) ? 1 : 0}
: false);
const propsFrom = propsTo.map((props, i) => {
const from = parent(nodes[i]) === target && (currentProps[i] || getProps(nodes[i]));
if (!from) {
return false;
}
if (!props) {
delete from.opacity;
} else if (!('opacity' in props)) {
const {opacity} = from;
if (opacity % 1) {
props.opacity = 1;
} else {
delete from.opacity;
}
}
return from;
});
return [propsTo, propsFrom];
}
function reset(el) {
css(el.children, {
height: '',
left: '',
opacity: '',
pointerEvents: '',
position: '',
top: '',
marginTop: '',
marginLeft: '',
transform: '',
width: '',
zIndex: ''
});
css(el, {height: '', display: '', padding: ''});
}
function getPositionWithMargin(el) {
const {height, width} = offset(el);
const {top, left} = position(el);
const {marginLeft, marginTop} = css(el, ['marginTop', 'marginLeft']);
return {top, left, height, width, marginLeft, marginTop, transform: ''};
}