UNPKG

material-motion

Version:

Makes it easy to add rich, interactive motion to your application.

214 lines 9.01 kB
/** @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 { Spring as WobbleSpring, } from 'wobble'; import { createProperty, } from '../observables/createProperty'; import { MotionObservable, } from '../observables/MotionObservable'; import { State, } from '../enums'; export const DEFAULT_STIFFNESS = 342; export const DEFAULT_DAMPING = 30; export const DEFAULT_THRESHOLD = .001; // Springs are a core primitive in Material Motion, yet, the implementations we // have are all from 3rd party libraries. Thus, the common // getters/setters/properties live here, and each implementation can extend it // to implement its own `value$`. export class NumericSpring { constructor() { this.destination$ = createProperty({ initialValue: 0, }); this.initialValue$ = createProperty({ initialValue: 0, }); this.initialVelocity$ = createProperty({ initialValue: 0, }); this.stiffness$ = createProperty({ initialValue: DEFAULT_STIFFNESS, }); this.damping$ = createProperty({ initialValue: DEFAULT_DAMPING, }); this.threshold$ = createProperty({ initialValue: DEFAULT_THRESHOLD, }); this.enabled$ = createProperty({ initialValue: true, }); this.state$ = createProperty({ initialValue: State.AT_REST, }); this.value$ = new MotionObservable((observer) => { const spring = new WobbleSpring(); const updateSpringConfig = spring.updateConfig.bind(spring); let initialized = false; let initialValueChangedWhileDisabled = false; let initialVelocityChangedWhileDisabled = false; this.state$.write(State.AT_REST); spring.onStart(() => { this.state$.write(State.ACTIVE); }); spring.onUpdate(() => { observer.next(spring.currentValue); }); spring.onStop(() => { this.state$.write(State.AT_REST); }); // During initialization, the configuration values will be set in the // order they are subscribed to; hence, const subscriptions = [ // properties that configure the spring this.stiffness$._map(transformToValueWithKey('stiffness')).subscribe(updateSpringConfig), this.damping$._map(transformToValueWithKey('damping')).subscribe(updateSpringConfig), this.threshold$._map(transformToValueWithKey('restDisplacementThreshold')).subscribe(updateSpringConfig), // properties that initialize the spring this.initialVelocity$.subscribe((initialVelocity) => { if (this.enabled$.read()) { updateSpringConfig({ initialVelocity }); } else { initialVelocityChangedWhileDisabled = true; } }), this.initialValue$.subscribe((initialValue) => { // We need to figure out what the right interplay is between // `initialValue` and `value$`. It's probably more convenient for // an author if changes to `initialValue` are passed through to // `value$`. This means you can do something like // `spring.value$.subscribe(location$)` and trust `location$` is // always up-to-date. However, it's unclear how that would affect // `state$`. Should `state$` go at_rest -> active -> at_rest every // time something touches `initialValue`? Should `value$` be able // to emit `initialValue$` without touching `value$`? Should we // require authors to worry about connecting the two? // // Punting on this for now and forcing authors to deal with it. if (this.enabled$.read()) { updateSpringConfig({ fromValue: initialValue }); observer.next(initialValue); } else { initialValueChangedWhileDisabled = true; } }), // properties that can start/stop the spring this.enabled$.subscribe((enabled) => { if (initialized) { if (enabled) { // For simple cases, the spring can manage its own state; // however, we provide property APIs (like initialValue$) for // situations where it should be managed externally. // // The ability to manage this state externally shouldn't require // every author to do so; hence, we use dirty-checking to only // initialize the spring's values if the author has used these // features. // // There's probably a more elegant way to do this (queueing up // changes as they come through and then applying the most // recent one when enabled is set), but this is the simplest // quick solution. const springConfig = { toValue: this.destination$.read(), }; if (initialValueChangedWhileDisabled) { const initialValue = this.initialValue$.read(); springConfig.fromValue = initialValue; observer.next(initialValue); } if (initialVelocityChangedWhileDisabled) { springConfig.initialVelocity = this.initialVelocity$.read(); } updateSpringConfig(springConfig); spring.start(); initialValueChangedWhileDisabled = false; initialVelocityChangedWhileDisabled = false; } else { spring.stop(); } } }), this.destination$.subscribe((destination) => { if (this.enabled$.read()) { updateSpringConfig({ toValue: destination }); spring.start(); } ; }), ]; initialized = true; return function disconnect() { subscriptions.forEach(subscription => subscription.unsubscribe()); }; })._remember(); } get destination() { return this.destination$.read(); } set destination(value) { this.destination$.write(value); } get initialValue() { return this.initialValue$.read(); } set initialValue(value) { this.initialValue$.write(value); } get initialVelocity() { return this.initialVelocity$.read(); } set initialVelocity(value) { this.initialVelocity$.write(value); } get stiffness() { return this.stiffness$.read(); } set stiffness(value) { this.stiffness$.write(value); } get damping() { return this.damping$.read(); } set damping(value) { this.damping$.write(value); } get threshold() { return this.threshold$.read(); } set threshold(value) { this.threshold$.write(value); } get enabled() { return this.enabled$.read(); } set enabled(value) { this.enabled$.write(value); } get state() { return this.state$.read(); } } export default NumericSpring; function transformToValueWithKey(key) { return { transform(value) { return { [key]: value, }; }, }; } //# sourceMappingURL=NumericSpring.js.map