material-motion
Version:
Makes it easy to add rich, interactive motion to your application.
132 lines • 5.11 kB
JavaScript
/** @license
* Copyright 2016 - present The Material Motion Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
import { when, } from '../aggregators';
import { combineLatest, } from '../combineLatest';
import { subscribe, } from '../subscribe';
import { Axis, Direction, State, ThresholdRegion, } from '../enums';
import { createProperty, } from '../observables';
import { NumericSpring, } from './NumericSpring';
export var SwipeState;
(function (SwipeState) {
SwipeState["NONE"] = "none";
SwipeState["LEFT"] = "left";
SwipeState["RIGHT"] = "right";
})(SwipeState || (SwipeState = {}));
;
export class Swipeable {
constructor({ tossable, width$ }) {
this.iconSpring = new NumericSpring();
this.backgroundSpring = new NumericSpring();
// Should `State` be called `MotionState` so `state$` can be reserved for interactions?
this.swipeState$ = createProperty({ initialValue: SwipeState.NONE });
/**
* If an item is swiped past the threshold, it will animate by its own width
* + destinationMargin, in the direction of the swipe.
*
* This ensures that decoration that might overflow an item's bounds (like a
* shadow) isn't visible when it's been swiped away.
*/
this.destinationMargin$ = createProperty({
initialValue: 0,
});
this.tossable = tossable;
this.width$ = width$;
this.state$ = this.tossable.state$;
tossable.draggable.axis = Axis.X;
const ICON_SPRING_INITIAL_VALUE = 0.67;
const draggable = tossable.draggable;
const spring = tossable.spring;
const draggedX$ = tossable.draggedLocation$.pluck({ path: 'x' });
this.iconSpring.initialValue = ICON_SPRING_INITIAL_VALUE;
this.direction$ = draggedX$.threshold(0).isAnyOf([ThresholdRegion.ABOVE]).rewrite({
mapping: {
true: Direction.RIGHT,
false: Direction.LEFT,
}
});
this.isThresholdMet$ = draggedX$.distanceFrom(0).threshold(Swipeable.VISUAL_THRESHOLD).isAnyOf([
ThresholdRegion.ABOVE,
ThresholdRegion.WITHIN,
]);
this.whenThresholdCrossed$ = when(this.isThresholdMet$.dedupe());
subscribe({
sink: this.backgroundSpring.destination$,
source: this.isThresholdMet$.rewrite({
mapping: {
true: 1,
false: 0,
}
}),
});
subscribe({
sink: this.iconSpring.destination$,
source: this.isThresholdMet$.rewrite({
mapping: {
true: 1,
false: ICON_SPRING_INITIAL_VALUE,
}
}),
});
// This needs to also take velocity into consideration; right now, it only
// cares about final position.
subscribe({
sink: this.swipeState$,
source: when(draggable.state$.isAnyOf([State.AT_REST])).rewriteTo({
value$: this.isThresholdMet$.rewrite({
mapping: {
true: this.direction$,
false: SwipeState.NONE,
},
}),
onlyEmitWithUpstream: true,
}),
});
const destinationDistance$ = width$.addedBy(this.destinationMargin$);
subscribe({
sink: spring.destination$,
source: combineLatest({
x: this.swipeState$.rewrite({
mapping: {
[SwipeState.NONE]: 0,
[SwipeState.LEFT]: destinationDistance$.multipliedBy(-1),
[SwipeState.RIGHT]: destinationDistance$,
}
}),
y: 0,
})
});
this.styleStreamsByTargetName = {
item: tossable.styleStreams,
icon: {
scale$: this.iconSpring.value$,
willChange$: tossable.styleStreams.willChange$,
},
background: {
scale$: this.backgroundSpring.value$,
willChange$: tossable.styleStreams.willChange$,
},
};
}
get destinationMargin() {
return this.destinationMargin$.read();
}
set destinationMargin(value) {
this.destinationMargin$.write(value);
}
}
Swipeable.VISUAL_THRESHOLD = 72;
export default Swipeable;
//# sourceMappingURL=Swipeable.js.map