UNPKG

wilderness-core

Version:
795 lines (664 loc) 25.1 kB
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); /* globals __DEV__ */ import clone from './clone'; import config from './config'; import { input } from './middleware'; import { event } from './events'; /** * The position of an object on a Timeline * where 0 is Timeline start and 1 is Timeline finish. * * @typedef {Object} TimelinePosition * * @property {Position} start * @property {Position} finish */ /** * A Shape positioned on a Timeline. * * @typedef {Object} TimelineShape * * @property {Shape} shape * @property {TimelinePosition} timelinePosition */ /** * The position of an object on a Timeline in milliseconds. * * @typedef {Object} MsTimelinePosition * * @property {number} start. * @property {number} finish. */ /** * A Shape positioned on a Timeline (position set in milliseconds). * * @typedef {Object} MsTimelineShape * * @property {Shape} shape * @property {MsTimelinePosition} timelinePosition */ /** * A TimelineShape array and their total duration. * * @typedef {Object} TimelineShapesAndDuration * * @property {TimelineShape[]} timelineShapes * @property {number} duration */ /** * The options required to calculate the current playback Position. * * @typedef {Object} PlaybackOptions * * @property {boolean} alternate - Should the next iteration reverse current direction? * @property {number} duration - Milliseconds that each iteration lasts. * @property {number} initialIterations - The starting number of iterations. * @property {number} iterations - The number of playback interations (additional to initialIterations). * @property {boolean} reverse - Should the first iteration start in a reverse direction? * @property {number} [started] - The UNIX timestamp of playback start. */ /** * PlaybackOptions and tween middleware. * * @typedef {Object} TimelineOptions * * @extends PlaybackOptions * @property {Middleware[]} middleware */ /** * A Shape and timeline related options. * * @typedef {Object} ShapeWithOptions * * @property {(string|number)} [after] - The name of the Shape to queue after (in sequence). * @property {(string|number)} [at] - The name of the Shape to queue at (in parallel). * @property {(string|number)} name - A unique reference. * @property {number} offset - The offset in milliseconds to adjust the queuing of this shape. * @property {Shape} shape */ /** * An object containing Middlware, PlaybackOptions and ShapesWithOptions. * * @typedef {Object} SortedTimelineProps * * @property {Middleware[]} middleware * @property {PlaybackOptions} playbackOptions * @property {ShapeWithOptions[]} shapesWithOptions */ /** * A sequence of Shapes. * * @typedef {Object} Timeline * * @property {Middleware[]} middleware * @property {PlaybackOptions} playbackOptions * @property {Object} state - Holds the last known state of the timeline. * @property {TimelineShape[]} timelineShapes */ /** * Runs each Middleware input function on every Keyframe's FrameShape. * * @param {Shape} shape * @param {Middleware[]} middleware * * @example * apply(shape, middleware) */ var apply = function apply(_ref, middleware) { var keyframes = _ref.keyframes; for (var i = 0, l = keyframes.length; i < l; i++) { var keyframe = keyframes[i]; keyframe.frameShape = input(keyframe.frameShape, middleware); } }; /** * Is playback currently in reverse? * * @param {PlaybackOptions} playbackOptions * @param {number} complete - The number of iterations complete. * * @example * currentReverse(playbackOptions, complete) */ var currentReverse = function currentReverse(playbackOptions, complete) { var reverse = playbackOptions.reverse; if (complete === 0) { return reverse; } var alternate = playbackOptions.alternate; var initialIterations = playbackOptions.initialIterations; var initialReverse = sameDirection(alternate, initialIterations) ? reverse : !reverse; return sameDirection(alternate, initialIterations + complete) ? initialReverse : !initialReverse; }; /** * The number of iterations a Timeline has completed. * * @param {PlaybackOptions} playbackOptions * @param {number} opts.at * * @returns {number} * * @example * iterationsComplete(playbackOptions, 1000) */ var iterationsComplete = function iterationsComplete(playbackOptions, at) { var duration = playbackOptions.duration; var iterations = playbackOptions.iterations; var started = playbackOptions.started; if (typeof started === 'undefined' || at <= started) { return 0; } var ms = at - started; var maxDuration = duration * iterations; if (ms >= maxDuration) { return iterations; } return ms / duration; }; /** * Stops playback of a Timeline. * * @param {Timeline} timeline * @param {PlaybackOptions} playbackOptions * @param {number} [at] * * @example * pause(timeline) */ var pause = function pause(timeline) { var playbackOptions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var at = arguments[2]; timeline.playbackOptions = updatePlaybackOptions({ at: at, timeline: timeline, pause: true, playbackOptions: playbackOptions }); updateState(timeline, at); }; /** * Starts playback of a Timeline. * * @param {Timeline} timeline * @param {PlaybackOptions} playbackOptions * @param {number} [at] * * @example * play(timeline, { initialIterations: 0 }) */ var play = function play(timeline) { var playbackOptions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; var at = arguments[2]; timeline.playbackOptions = updatePlaybackOptions({ at: at, timeline: timeline, playbackOptions: playbackOptions }); updateState(timeline, at); }; /** * Calculate the Timeline Position. * * @param {number} totalIterations - initialIterations + iterationsComplete. * @param {boolean} reverse - Is the Timeline currently in reverse? * * @returns {Position} * * @example * position(5.43, true) */ var position = function position(totalIterations, reverse) { var i = totalIterations >= 1 && totalIterations % 1 === 0 ? 1 : totalIterations % 1; return reverse ? 1 - i : i; }; /** * Is the direction same as initial direction? * * @param {boolean} alternate - Is iteration direction alternating? * @param {number} iterations - The number of iterations complete. * * @return {boolean} * * @example * sameDirection(true, 3.25) */ var sameDirection = function sameDirection(alternate, iterations) { var x = iterations % 2; return !alternate || iterations === 0 || x <= 1 && x % 2 > 0; }; /** * Calculate the start position of a Shape on the Timeline. * * @param {Object} props * @param {(string|number)} [props.after] * @param {(string|number)} [props.at] * @param {MsTimelineShape[]} props.msTimelineShapes * @param {number} props.offset * @param {number} props.timelineFinish - The current finish of the timeline. * * @returns {number} * * @example * shapeStart({ 'foo', msTimelineShapes, 200, 2000 }) */ var shapeStart = function shapeStart(_ref2) { var after = _ref2.after, at = _ref2.at, msTimelineShapes = _ref2.msTimelineShapes, offset = _ref2.offset, timelineFinish = _ref2.timelineFinish; if (typeof after !== 'undefined' || typeof at !== 'undefined') { var reference = typeof after !== 'undefined' ? after : at; for (var i = 0; i < msTimelineShapes.length; i++) { var s = msTimelineShapes[i]; if (reference === s.shape.name) { return (typeof at !== 'undefined' ? s.timelinePosition.start : s.timelinePosition.finish) + offset; } } for (var _i = 0; _i < msTimelineShapes.length; _i++) { var _s = msTimelineShapes[_i]; for (var j = 0; j < _s.shape.keyframes.length; j++) { var keyframe = _s.shape.keyframes[j]; if (reference === keyframe.name) { return _s.timelinePosition.start + _s.shape.duration * keyframe.position + offset; } } } if (process.env.NODE_ENV !== 'production') { throw new Error('No Shape or Keyframe matching name \'' + reference + '\''); } } return timelineFinish + offset; }; /** * Create a ShapeWithOptions from an array. * * @param {Object[]} arr * @param {Shape} arr.0 * @param {Object} arr.1 * * @returns {ShapeWithOptions} * * @example * shapeWithOptionsFromArray(arr, i) */ var shapeWithOptionsFromArray = function shapeWithOptionsFromArray(_ref3, i) { var _ref4 = _slicedToArray(_ref3, 2), shape = _ref4[0], options = _ref4[1]; if (process.env.NODE_ENV !== 'production' && ((typeof shape === 'undefined' ? 'undefined' : _typeof(shape)) !== 'object' || !shape.keyframes)) { throw new TypeError('When an array is passed to the timeline function the first item must be a Shape'); } if (process.env.NODE_ENV !== 'production' && (typeof options === 'undefined' ? 'undefined' : _typeof(options)) !== 'object') { throw new TypeError('When an array is passed to the timeline function the second item must be an object'); } var _options$name = options.name, name = _options$name === undefined ? i : _options$name, _options$queue = options.queue, queue = _options$queue === undefined ? config.defaults.timeline.queue : _options$queue; if (process.env.NODE_ENV !== 'production' && typeof name !== 'string' && typeof name !== 'number') { throw new TypeError('The name prop must be of type string or number'); } if ((typeof queue === 'undefined' ? 'undefined' : _typeof(queue)) === 'object' && !Array.isArray(queue) && queue !== null) { var after = queue.after, at = queue.at, _queue$offset = queue.offset, offset = _queue$offset === undefined ? 0 : _queue$offset; if (process.env.NODE_ENV !== 'production' && typeof offset !== 'undefined' && typeof offset !== 'number') { throw new TypeError('The queue.offset prop must be of type number'); } if (process.env.NODE_ENV !== 'production' && typeof at !== 'undefined' && typeof after !== 'undefined') { throw new TypeError('You cannot pass both queue.at and queue.after props'); } if (process.env.NODE_ENV !== 'production' && typeof at !== 'undefined' && typeof at !== 'string' && typeof at !== 'number') { throw new TypeError('The queue.at prop must be of type string or number'); } if (process.env.NODE_ENV !== 'production' && typeof after !== 'undefined' && typeof after !== 'string' && typeof after !== 'number') { throw new TypeError('The queue.after prop must be of type string or number'); } if (typeof at !== 'undefined') { return { at: at, name: name, offset: offset, shape: shape }; } if (typeof after !== 'undefined') { return { after: after, name: name, offset: offset, shape: shape }; } return { name: name, offset: offset, shape: shape }; } else if (typeof queue === 'number') { return { name: name, offset: queue, shape: shape }; } else if (typeof queue === 'string') { return { after: queue, name: name, offset: 0, shape: shape }; } if (process.env.NODE_ENV !== 'production') { throw new TypeError('The queue prop must be of type number, string or object'); } return { name: name, offset: 0, shape: shape }; }; /** * Sorts an array of Shapes, ShapesWithOptions and TimelineOptions. * * @param {(Shape|Object[]|TimelineOptions)[]} props * * @returns {SortedTimelineProps} * * @example * sort(props) */ var sort = function sort(props) { if (process.env.NODE_ENV !== 'production' && props.length === 0) { throw new TypeError('The timeline function must be passed at least one Shape'); } var options = {}; var shapesWithOptions = []; for (var i = 0, l = props.length; i < l; i++) { var prop = props[i]; if (Array.isArray(prop)) { shapesWithOptions.push(shapeWithOptionsFromArray(prop, i)); } else { if (process.env.NODE_ENV !== 'production' && (typeof prop === 'undefined' ? 'undefined' : _typeof(prop)) !== 'object') { throw new TypeError('The timeline function must only be passed objects and arrays'); } if (prop.keyframes) { shapesWithOptions.push({ name: i, offset: config.defaults.timeline.queue, shape: prop }); } else { if (process.env.NODE_ENV !== 'production') { if (i === 0) { throw new TypeError('The timeline function must receive a Shape as the first argument'); } else if (i !== props.length - 1) { throw new TypeError('The timeline function must receive options as the final argument'); } } options = clone(prop); } } } return { middleware: validMiddleware(options), playbackOptions: validPlaybackOptions(options), shapesWithOptions: shapesWithOptions }; }; /** * Creates a Timeline from one or more Shape. * Optionally can take an options object as the last argument, * as well as options for each Shape if passed in as an array. * * @param {...(Shape|Object[]|TimelineOptions)} props * * @returns {Timeline} * * @example * timeline(circle, [ square, { queue: -200 } ], { duration: 5000 }) */ var timeline = function timeline() { for (var _len = arguments.length, props = Array(_len), _key = 0; _key < _len; _key++) { props[_key] = arguments[_key]; } var _sort = sort(props), middleware = _sort.middleware, playbackOptions = _sort.playbackOptions, shapesWithOptions = _sort.shapesWithOptions; var _timelineShapesAndDur = timelineShapesAndDuration(shapesWithOptions, middleware), duration = _timelineShapesAndDur.duration, timelineShapes = _timelineShapesAndDur.timelineShapes; if (typeof playbackOptions.duration === 'undefined') { playbackOptions.duration = duration; } var t = { middleware: middleware, playbackOptions: playbackOptions, state: {}, timelineShapes: timelineShapes }; for (var i = 0, l = timelineShapes.length; i < l; i++) { var shape = timelineShapes[i].shape; shape.timeline = t; shape.timelineIndex = i; } updateState(t); t.event = event(t); return t; }; /** * Converts a set of MsTimelineShapes to a set of TimelineShapes * given the Timeline start and total duration values. * * @param {Object} props * @param {number} props.duration * @param {msTimelineShape[]} props.msTimelineShapes * @param {number} props.start * * @returns {TimelineShape[]} * * @example * timelineShapes() */ var timelineShapes = function timelineShapes(_ref5) { var duration = _ref5.duration, msTimelineShapes = _ref5.msTimelineShapes, start = _ref5.start; var s = []; for (var i = 0, l = msTimelineShapes.length; i < l; i++) { var msTimelineShape = msTimelineShapes[i]; var timelinePosition = msTimelineShape.timelinePosition; s.push({ shape: msTimelineShape.shape, timelinePosition: { start: (timelinePosition.start - start) / duration, finish: (timelinePosition.finish - start) / duration } }); } return s; }; /** * Converts an array of ShapesWithOptions into TimelineShapes * and their total duration. * * @param {ShapeWithOptions[]} shapesWithOptions * @param {Middleware[]} middleware * * @returns {TimelineShapesAndDuration} * * @example * timelineShapes(shapesWithOptions) */ var timelineShapesAndDuration = function timelineShapesAndDuration(shapesWithOptions, middleware) { var timelineStart = 0; var timelineFinish = 0; var msTimelineShapes = []; for (var i = 0, l = shapesWithOptions.length; i < l; i++) { var _shapesWithOptions$i = shapesWithOptions[i], after = _shapesWithOptions$i.after, at = _shapesWithOptions$i.at, name = _shapesWithOptions$i.name, offset = _shapesWithOptions$i.offset, shape = _shapesWithOptions$i.shape; if (process.env.NODE_ENV !== 'production' && typeof shape.timeline !== 'undefined') { throw new Error('A Shape can only be added to one timeline'); } shape.name = name; apply(shape, middleware); var start = shapeStart({ after: after, at: at, msTimelineShapes: msTimelineShapes, offset: offset, timelineFinish: timelineFinish }); var finish = start + shape.duration; timelineStart = Math.min(timelineStart, start); timelineFinish = Math.max(timelineFinish, finish); msTimelineShapes.push({ shape: shape, timelinePosition: { start: start, finish: finish } }); } var timelineDuration = Math.abs(timelineStart - timelineFinish); return { duration: timelineDuration, timelineShapes: timelineShapes({ duration: timelineDuration, msTimelineShapes: msTimelineShapes, start: timelineStart }) }; }; /** * Updates the PlaybackOptions of a Timeline. * * @param {Object} opts * @param {number} [opts.at] * @param {PlaybackOptions} opts.playbackOptions * @param {Timeline} opts.timeline * * @example * updatePlaybackOptions({ timeline, playbackOptions }) */ var updatePlaybackOptions = function updatePlaybackOptions(_ref6) { var at = _ref6.at, _ref6$pause = _ref6.pause, pause = _ref6$pause === undefined ? false : _ref6$pause, playbackOptions = _ref6.playbackOptions, timeline = _ref6.timeline; if (process.env.NODE_ENV !== 'production' && ((typeof timeline === 'undefined' ? 'undefined' : _typeof(timeline)) !== 'object' || !timeline.timelineShapes || !timeline.playbackOptions)) { throw new TypeError('The updatePlaybackOptions function must be passed a Timeline'); } if (process.env.NODE_ENV !== 'production' && typeof at !== 'undefined' && typeof at !== 'number') { throw new TypeError('The updatePlaybackOptions function at property must be of type number'); } var previous = timeline.playbackOptions; var next = validPlaybackOptions(_extends({}, previous, playbackOptions, { started: typeof at !== 'undefined' ? at : Date.now() })); if (typeof playbackOptions.initialIterations !== 'undefined') { if (typeof playbackOptions.reverse === 'undefined') { next.reverse = currentReverse(previous, next.initialIterations - previous.initialIterations); } if (typeof playbackOptions.iterations === 'undefined' && previous.iterations !== Infinity) { next.iterations = Math.max(0, previous.initialIterations + previous.iterations - next.initialIterations); } } else { var complete = iterationsComplete(previous, next.started); var reverse = currentReverse(previous, complete); next.initialIterations = previous.initialIterations + complete; if (typeof playbackOptions.iterations === 'undefined') { next.iterations = previous.iterations - complete; if (typeof playbackOptions.reverse !== 'undefined' && next.reverse !== previous.reverse && next.iterations !== Infinity) { var nextIterations = next.initialIterations; next.initialIterations = next.iterations; next.iterations = nextIterations; } } else { if (typeof playbackOptions.reverse !== 'undefined' && playbackOptions.reverse !== reverse && next.iterations !== Infinity) { next.initialIterations = previous.iterations - complete; } } if (typeof playbackOptions.reverse === 'undefined') { next.reverse = reverse; } else if (next.iterations === Infinity) { next.initialIterations = playbackOptions.reverse === reverse ? next.initialIterations % 1 : 1 - next.initialIterations % 1; } } if (pause) { delete next.started; } return next; }; /** * Updates the Timeline state. * * @param {Timeline} timeline * @param {number} at * * @example * updateState(timeline, Date.now()) */ var updateState = function updateState(t, at) { var playbackOptions = t.playbackOptions; var state = t.state; state.started = typeof playbackOptions.started !== 'undefined'; state.iterationsComplete = iterationsComplete(playbackOptions, at); state.totalIterations = playbackOptions.initialIterations + state.iterationsComplete; state.reverse = currentReverse(playbackOptions, state.iterationsComplete); state.finished = playbackOptions.iterations - state.iterationsComplete === 0; state.position = position(state.totalIterations, state.reverse); }; /** * Extracts and validates Middlware from an object. * * @param {Object} opts * * @returns {Middleware[]} * * @example * validMiddleware(opts) */ var validMiddleware = function validMiddleware(_ref7) { var _ref7$middleware = _ref7.middleware, middleware = _ref7$middleware === undefined ? config.defaults.timeline.middleware : _ref7$middleware; if (!Array.isArray(middleware)) { throw new TypeError('The timeline function middleware option must be of type array'); } for (var i = 0, l = middleware.length; i < l; i++) { var _middleware$i = middleware[i], name = _middleware$i.name, _input = _middleware$i.input, output = _middleware$i.output; if (typeof name !== 'string') { throw new TypeError('A middleware must have a name prop'); } if (typeof _input !== 'function') { throw new TypeError('The ' + name + ' middleware must have an input method'); } if (typeof output !== 'function') { throw new TypeError('The ' + name + ' middleware must have an output method'); } } return middleware; }; /** * Extracts and validates PlaybackOptions from an object. * * @param {Object} opts * * @returns {PlaybackOptions} * * @example * validPlaybackOptions(opts) */ var validPlaybackOptions = function validPlaybackOptions(_ref8) { var _ref8$alternate = _ref8.alternate, alternate = _ref8$alternate === undefined ? config.defaults.timeline.alternate : _ref8$alternate, duration = _ref8.duration, _ref8$initialIteratio = _ref8.initialIterations, initialIterations = _ref8$initialIteratio === undefined ? config.defaults.timeline.initialIterations : _ref8$initialIteratio, _ref8$iterations = _ref8.iterations, iterations = _ref8$iterations === undefined ? config.defaults.timeline.iterations : _ref8$iterations, _ref8$reverse = _ref8.reverse, reverse = _ref8$reverse === undefined ? config.defaults.timeline.reverse : _ref8$reverse, started = _ref8.started; var playbackOptions = {}; if (typeof duration !== 'undefined') { if (process.env.NODE_ENV !== 'production' && (typeof duration !== 'number' || duration < 0)) { throw new TypeError('The timeline function duration option must be a positive number or zero'); } playbackOptions.duration = duration; } if (process.env.NODE_ENV !== 'production') { if (typeof alternate !== 'boolean') { throw new TypeError('The timeline function alternate option must be true or false'); } if (typeof initialIterations !== 'number' || initialIterations < 0) { throw new TypeError('The timeline function initialIterations option must be a positive number or zero'); } if (typeof iterations !== 'number' || iterations < 0) { throw new TypeError('The timeline function iterations option must be a positive number or zero'); } if (typeof reverse !== 'boolean') { throw new TypeError('The timeline function reverse option must be true or false'); } } if (typeof started !== 'undefined') { if (process.env.NODE_ENV !== 'production' && (typeof started !== 'number' || started < 0)) { throw new TypeError('The timeline function started option must be a positive number or zero'); } playbackOptions.started = started; } return _extends({}, playbackOptions, { alternate: alternate, initialIterations: initialIterations, iterations: iterations, reverse: reverse }); }; export { currentReverse, iterationsComplete, pause, play, position, sameDirection, updateState }; export default timeline;