UNPKG

wilderness-core

Version:
1,956 lines (1,651 loc) 145 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (factory((global.WildernessCore = {}))); }(this, (function (exports) { 'use strict'; 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 _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 objectWithoutProperties = function (obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }; 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"); } }; }(); var toArray = function (arr) { return Array.isArray(arr) ? arr : Array.from(arr); }; var toConsumableArray = function (arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; return arr2; } else { return Array.from(arr); } }; /** * A tweenable color. * * @typedef {Object} Color * * @property {string} middleware - The name of this middleware. * @property {number} r - The hexadecimal red value. * @property {number} g - The hexadecimal green value. * @property {number} b - The hexadecimal blue value. * @property {number} a - The alpha value. */ var name = 'color'; var htmlColors = { 'aliceblue': '#F0F8FF', 'antiquewhite': '#FAEBD7', 'aqua': '#00FFFF', 'aquamarine': '#7FFFD4', 'azure': '#F0FFFF', 'beige': '#F5F5DC', 'bisque': '#FFE4C4', 'black': '#000000', 'blanchedalmond': '#FFEBCD', 'blue': '#0000FF', 'blueviolet': '#8A2BE2', 'brown': '#A52A2A', 'burlywood': '#DEB887', 'cadetblue': '#5F9EA0', 'chartreuse': '#7FFF00', 'chocolate': '#D2691E', 'coral': '#FF7F50', 'cornflowerblue': '#6495ED', 'cornsilk': '#FFF8DC', 'crimson': '#DC143C', 'cyan': '#00FFFF', 'darkblue': '#00008B', 'darkcyan': '#008B8B', 'darkgoldenrod': '#B8860B', 'darkgray': '#A9A9A9', 'darkgreen': '#006400', 'darkgrey': '#A9A9A9', 'darkkhaki': '#BDB76B', 'darkmagenta': '#8B008B', 'darkolivegreen': '#556B2F', 'darkorange': '#FF8C00', 'darkorchid': '#9932CC', 'darkred': '#8B0000', 'darksalmon': '#E9967A', 'darkseagreen': '#8FBC8F', 'darkslateblue': '#483D8B', 'darkslategray': '#2F4F4F', 'darkslategrey': '#2F4F4F', 'darkturquoise': '#00CED1', 'darkviolet': '#9400D3', 'deeppink': '#FF1493', 'deepskyblue': '#00BFFF', 'dimgray': '#696969', 'dimgrey': '#696969', 'dodgerblue': '#1E90FF', 'firebrick': '#B22222', 'floralwhite': '#FFFAF0', 'forestgreen': '#228B22', 'fuchsia': '#FF00FF', 'gainsboro': '#DCDCDC', 'ghostwhite': '#F8F8FF', 'gold': '#FFD700', 'goldenrod': '#DAA520', 'gray': '#808080', 'green': '#008000', 'greenyellow': '#ADFF2F', 'grey': '#808080', 'honeydew': '#F0FFF0', 'hotpink': '#FF69B4', 'indianred': '#CD5C5C', 'indigo': '#4B0082', 'ivory': '#FFFFF0', 'khaki': '#F0E68C', 'lavender': '#E6E6FA', 'lavenderblush': '#FFF0F5', 'lawngreen': '#7CFC00', 'lemonchiffon': '#FFFACD', 'lightblue': '#ADD8E6', 'lightcoral': '#F08080', 'lightcyan': '#E0FFFF', 'lightgoldenrodyellow': '#FAFAD2', 'lightgray': '#D3D3D3', 'lightgreen': '#90EE90', 'lightgrey': '#D3D3D3', 'lightpink': '#FFB6C1', 'lightsalmon': '#FFA07A', 'lightseagreen': '#20B2AA', 'lightskyblue': '#87CEFA', 'lightslategray': '#778899', 'lightslategrey': '#778899', 'lightsteelblue': '#B0C4DE', 'lightyellow': '#FFFFE0', 'lime': '#00FF00', 'limegreen': '#32CD32', 'linen': '#FAF0E6', 'magenta': '#FF00FF', 'maroon': '#800000', 'mediumaquamarine': '#66CDAA', 'mediumblue': '#0000CD', 'mediumorchid': '#BA55D3', 'mediumpurple': '#9370DB', 'mediumseagreen': '#3CB371', 'mediumslateblue': '#7B68EE', 'mediumspringgreen': '#00FA9A', 'mediumturquoise': '#48D1CC', 'mediumvioletred': '#C71585', 'midnightblue': '#191970', 'mintcream': '#F5FFFA', 'mistyrose': '#FFE4E1', 'moccasin': '#FFE4B5', 'navajowhite': '#FFDEAD', 'navy': '#000080', 'oldlace': '#FDF5E6', 'olive': '#808000', 'olivedrab': '#6B8E23', 'orange': '#FFA500', 'orangered': '#FF4500', 'orchid': '#DA70D6', 'palegoldenrod': '#EEE8AA', 'palegreen': '#98FB98', 'paleturquoise': '#AFEEEE', 'palevioletred': '#DB7093', 'papayawhip': '#FFEFD5', 'peachpuff': '#FFDAB9', 'peru': '#CD853F', 'pink': '#FFC0CB', 'plum': '#DDA0DD', 'powderblue': '#B0E0E6', 'purple': '#800080', 'rebeccapurple': '#663399', 'red': '#FF0000', 'rosybrown': '#BC8F8F', 'royalblue': '#4169E1', 'saddlebrown': '#8B4513', 'salmon': '#FA8072', 'sandybrown': '#F4A460', 'seagreen': '#2E8B57', 'seashell': '#FFF5EE', 'sienna': '#A0522D', 'silver': '#C0C0C0', 'skyblue': '#87CEEB', 'slateblue': '#6A5ACD', 'slategray': '#708090', 'slategrey': '#708090', 'snow': '#FFFAFA', 'springgreen': '#00FF7F', 'steelblue': '#4682B4', 'tan': '#D2B48C', 'teal': '#008080', 'thistle': '#D8BFD8', 'tomato': '#FF6347', 'turquoise': '#40E0D0', 'violet': '#EE82EE', 'wheat': '#F5DEB3', 'white': '#FFFFFF', 'whitesmoke': '#F5F5F5', 'yellow': '#FFFF00', 'yellowgreen': '#9ACD32' }; var htmlColorKeys = Object.keys(htmlColors); /** * Converts a color string to a Color. * * @param {*} x - A potential color string. * * @returns {*} * * @example * input('#FFFFFF') */ var input = function input(x) { if (typeof x === 'string') { if (hex(x)) { return hexToColor(x); } else if (rgb(x)) { return rgbToColor(x); } else if (rgba(x)) { return rgbaToColor(x); } else if (html(x)) { return htmlToColor(x); } } return x; }; /** * Converts a Color to a rgba color string. * * @param {*} x - A potential Color. * * @returns {*} * * @example * output(color) */ var output = function output(x) { if ((typeof x === 'undefined' ? 'undefined' : _typeof(x)) === 'object' && x.middleware === name) { return colorToRgba(x); } return x; }; /** * Is string a hex color? * * @param {string} str - A potential hex color. * * @returns {boolean} * * @example * hex('#FFFFFF') */ var hex = function hex(str) { return str.match(/^#(?:[0-9a-f]{3}){1,2}$/i) !== null; }; /** * Is string a rgba color? * * @param {string} str - A potential rgba color. * * @returns {boolean} * * @example * rgba('rgba(255,255,255,1)') */ var rgba = function rgba(str) { return str.startsWith('rgba('); }; /** * Is string a rgb color? * * @param {string} str - A potential rgb color. * * @returns {boolean} * * @example * rgb('rgb(255,255,255)') */ var rgb = function rgb(str) { return str.startsWith('rgb('); }; /** * Is string a html color? * * @param {string} str - A potential html color. * * @returns {boolean} * * @example * html('limegreen') */ var html = function html(str) { return htmlColorKeys.indexOf(str) !== -1; }; /** * Converts a hex string to a Color. * * @param {string} hex - A hex color. * * @returns {Color} * * @example * hexToColor('#FFFFFF') */ var hexToColor = function hexToColor(hex) { var x = hex.replace('#', ''); if (x.length === 3) { var y = ''; for (var i = 0; i < 3; i++) { var v = x.charAt(i); y += '' + v + v; } x = y; } return { middleware: name, r: parseInt(x.slice(0, 2), 16), g: parseInt(x.slice(2, 4), 16), b: parseInt(x.slice(4, 6), 16), a: 1 }; }; /** * Converts a rgb string to a Color. * * @param {string} rgb - A rgb color. * * @returns {Color} * * @example * rgbToColor('rgb(255,255,255)') */ var rgbToColor = function rgbToColor(rgb) { var x = rgb.replace(/\s/g, ''); var _x$substring$split = x.substring(4, x.length - 1).split(','), _x$substring$split2 = slicedToArray(_x$substring$split, 3), r = _x$substring$split2[0], g = _x$substring$split2[1], b = _x$substring$split2[2]; return { middleware: name, r: parseFloat(r), g: parseFloat(g), b: parseFloat(b), a: 1 }; }; /** * Converts a rgba string to a Color. * * @param {string} rgba - A rgba color. * * @returns {Color} * * @example * rgbaToColor('rgba(255,255,255,1)') */ var rgbaToColor = function rgbaToColor(rgba) { var x = rgba.replace(/\s/g, ''); var _x$substring$split3 = x.substring(5, x.length - 1).split(','), _x$substring$split4 = slicedToArray(_x$substring$split3, 4), r = _x$substring$split4[0], g = _x$substring$split4[1], b = _x$substring$split4[2], a = _x$substring$split4[3]; return { middleware: 'color', r: parseFloat(r), g: parseFloat(g), b: parseFloat(b), a: parseFloat(a) }; }; /** * Converts a html string to a Color. * * @param {string} html - An html color. * * @returns {Color} * * @example * htmlToColor('limegreen') */ var htmlToColor = function htmlToColor(html) { return hexToColor(htmlColors[html]); }; /** * Converts a Color to a rgba color string. * * @param {Color} color * * @returns {string} * * @example * colorToRgba(color) */ var colorToRgba = function colorToRgba(_ref) { var r = _ref.r, g = _ref.g, b = _ref.b, a = _ref.a; return 'rgba(' + parseInt(limit(r, 0, 255), 10) + ',' + parseInt(limit(g, 0, 255), 10) + ',' + parseInt(limit(b, 0, 255), 10) + ',' + limit(a, 0, 1) + ')'; }; /** * Find the closest number within limits. * * @param {number} num - The desired number. * @param {number} min - The minimum returned number. * @param {number} max - the maximum returned number. * * @returns {number} * * @example * limit(-1, 2, 5) */ var limit = function limit(num, min, max) { return Math.max(min, Math.min(max, num)); }; var colorMiddleware = { name: name, input: input, output: output }; /** * A naive, but small, clone function. * * @param {*} value * * @returns {*} * * @example * clone('hello world') */ var clone = function clone(value) { if ((typeof value === 'undefined' ? 'undefined' : _typeof(value)) !== 'object') { return value; } else if (Array.isArray(value)) { var arr = []; for (var i = 0, l = value.length; i < l; i++) { arr.push(clone(value[i])); } return arr; } else if (value !== null) { var obj = {}; for (var key in value) { obj[key] = clone(value[key]); } return obj; } return value; }; /** * A tweenable unit. * * @typedef {Object} Unit * * @property {string} middleware - The name of this middleware. * @property {string} values - The type of color string to output. */ var name$1 = 'unit'; var units = ['ch', 'cm', 'em', 'ex', 'in', 'mm', 'pc', 'pt', 'px', 'rem', 'vh', 'vmax', 'vmin', 'vw', '%']; /** * Converts a unit string to a Unit. * * @param {*} x - A potential unit string. * * @returns {*} * * @example * input('20px') */ var input$1 = function input(x) { if (typeof x === 'string') { var parts = x.split(' '); var values = []; for (var i = 0, l = parts.length; i < l; i++) { var part = parts[i]; var number = parseFloat(part); var unit = part.replace(number, ''); if (!isNaN(number) && (unit === '' || units.indexOf(unit) !== -1)) { values.push([number, unit]); } else { values.push(part); } } if (values.toString() !== parts.toString()) { return { middleware: name$1, values: values }; } } return x; }; /** * Converts a Unit to a unit string. * * @param {*} x - A potential Unit. * * @returns {*} * * @example * output(unit) */ var output$1 = function output(x) { if ((typeof x === 'undefined' ? 'undefined' : _typeof(x)) === 'object' && x.middleware === name$1) { var values = x.values; var result = []; for (var i = 0, l = values.length; i < l; i++) { result.push(values[i].join('')); } return result.join(' '); } return x; }; var unitMiddleware = { name: name$1, input: input$1, output: output$1 }; var config = { defaults: { keyframe: { duration: 250, easing: 'easeInOutQuad' }, motionPath: { easing: 'easeInOutQuad' }, timeline: { alternate: false, initialIterations: 0, iterations: 1, middleware: [colorMiddleware, unitMiddleware], queue: 0, reverse: false } } }; /** * A group of functions to transform/untransform a value. * * @typedef {Object} Middleware * * @property {string} name - The name of the middleware. * @property {function} input - Transform. * @property {function} output - Untransform. */ /** * Run every part of a value through a function. * * @param {*} value * @param {function} func * * @returns {*} * * @example * apply(2, n => n * 2) */ var apply$1 = function apply(value, func) { var v = func(value); if ((typeof v === 'undefined' ? 'undefined' : _typeof(v)) !== 'object') { return v; } else if (Array.isArray(v)) { var arr = []; for (var i = 0, l = v.length; i < l; i++) { arr.push(apply(v[i], func)); } return arr; } else if (v !== null) { var obj = {}; for (var k in v) { obj[k] = apply(v[k], func); } return obj; } return v; }; /** * Runs each Middleware input function in turn on a value. * * @param {*} value * @param {Middleware[]} middleware * * @returns {*} * * @example * input({ foo: 1, bar: [ 2, 3 ] }, middleware) */ var input$2 = function input(value, middleware) { var v = value; for (var i = 0, l = middleware.length; i < l; i++) { v = apply$1(v, middleware[i].input); } return v; }; /** * Runs each Middleware output function in reverse on a value. * * @param {*} value * @param {Middleware[]} middleware * * @returns {*} * * @example * output({ foo: 1, bar: [ 2, 3 ] }, middleware) */ var output$2 = function output(value, middleware) { var v = value; for (var i = middleware.length - 1; i >= 0; i--) { v = apply$1(v, middleware[i].output); } return v; }; /* globals true */ /** * 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$2(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; } } } { 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 (true && ((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 (true && (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 (true && 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 (true && typeof offset !== 'undefined' && typeof offset !== 'number') { throw new TypeError('The queue.offset prop must be of type number'); } if (true && typeof at !== 'undefined' && typeof after !== 'undefined') { throw new TypeError('You cannot pass both queue.at and queue.after props'); } if (true && typeof at !== 'undefined' && typeof at !== 'string' && typeof at !== 'number') { throw new TypeError('The queue.at prop must be of type string or number'); } if (true && 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 }; } { 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 (true && 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 (true && (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 (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 (true && 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 (true && ((typeof timeline === 'undefined' ? 'undefined' : _typeof(timeline)) !== 'object' || !timeline.timelineShapes || !timeline.playbackOptions)) { throw new TypeError('The updatePlaybackOptions function must be passed a Timeline'); } if (true && 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 (true && (typeof duration !== 'number' || duration < 0)) { throw new TypeError('The timeline function duration option must be a positive number or zero'); } playbackOptions.duration = duration; } { 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 (true && (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 }); }; /* globals true */ /** * An event. * * @typedef {Object} Event * * @property {number} at - The time the event occured. * @property {string} name - The event name. * @property {Object} options - Any additional event data. */ /** * A Timeline event subscription. * * @typedef {Object} EventSubscription * * @property {function} callback * @property {string} name * @property {number} token */ /** * An object to hold Timeline EventSubscriptions, and subscribe/unsubscribe functions. * * @typedef {Object} EventObject * * @property {Object} previousPlaybackOptions * @property {Object} previousState * @property {function} subscribe - A function to subscribe to Timeline events. * @property {EventSubscription[]} subscriptions * @property {function} unsubscribe - A function to unsubscribe to Timeline events. */ /** * Token incrementor. */ var t = 0; /** * Accepted event names. */ var acceptedEventNames = ['timeline.start', 'timeline.finish', 'shape.start', 'shape.finish', 'keyframe', 'frame']; /** * An EventObject creator. * * @param {Timeline} timeline * * @returns {EventObject} * * @example * event(timeline) */ var event = function event(timeline) { return { previousPlaybackOptions: {}, previousState: {}, subscribe: subscribe(timeline), subscriptions: [], unsubscribe: unsubscribe(timeline) }; }; /** * Is a Timeline active? * * @param {Timeline} timeline * * @returns {boolean} * * @example * active(timeline) */ var active = function active(_ref) { var event = _ref.event, state = _ref.state; return state.started && (!state.finished || typeof event.previousState === 'undefined' || !event.previousState.finished); }; /** * A unique list of Timeline EventSubscription names. * * @param {Timeline} timeline * * @returns {string[]} * * @example * activeEventNames(timeline) */ var activeEventNames = function activeEventNames(_ref2) { var subscriptions = _ref2.event.subscriptions; var s = []; for (var i = 0, l = subscriptions.length; i < l; i++) { var name = subscriptions[i].name; if (s.indexOf(name) === -1) { s.push(name); } } return s; }; /** * Run EventSubscription callbacks for every event that has occured since last check. * * @param {Timeline} timeline * * @example * events(timeline) */ var events = function events(timeline) { if (playbackOptionsChanged(timeline)) { timeline.event.previousPlaybackOptions = {}; timeline.event.previousState = {}; } var subscriptions = timeline.event.subscriptions; if (subscriptions.length && active(timeline)) { var eventNames = activeEventNames(timeline); var queue = eventQueue(timeline, eventNames); for (var i = 0, l = queue.length; i < l; i++) { var _event = queue[i]; var eventName = _event.name; var options = _event.options || {}; for (var _i = 0, _l = subscriptions.length; _i < _l; _i++) { var subscription = subscriptions[_i]; if (eventName === subscription.name) { subscription.callback(options); } } } } timeline.event.previousPlaybackOptions = _extends({}, timeline.playbackOptions); timeline.event.previousState = _extends({}, timeline.state); }; /** * An array of Events that have occured since last checked. * * @param {Timeline} timeline * @param {string[]} eventNames * * @returns {Event[]} * * @example * eventQueue(timeline, eventNames) */ var eventQueue = function eventQueue(_ref3, eventNames) { var previousState = _ref3.event.previousState, playbackOptions = _ref3.playbackOptions, state = _ref3.state, timelineShapes = _ref3.timelineShapes; var queue = []; var alternate = playbackOptions.alternate, duration = playbackOptions.duration, initialIterations = playbackOptions.initialIterations, iterations = playbackOptions.iterations, reverse = playbackOptions.reverse, started = playbackOptions.started; var max = started + duration * state.iterationsComplete; var min = typeof previousState.iterationsComplete !== 'undefined' ? started + duration * previousState.iterationsComplete + 1 : 0; var getTimestamps = function getTimestamps(pos) { return positionTimestamps({ alternate: alternate, duration: duration, initialIterations: initialIterations, iterations: iterations, max: max, min: min, position: pos, reverse: reverse, started: started }); }; if (eventNames.indexOf('timeline.start') !== -1) { var timestamps = getTimestamps(0); for (var i = 0, l = timestamps.length; i < l; i++) { queue.push({ name: 'timeline.start', at: timestamps[i] }); } } if (eventNames.indexOf('timeline.finish') !== -1) { var _timestamps = getTimestamps(1); for (var _i2 = 0, _l2 = _timestamps.length; _i2 < _l2; _i2++) { queue.push({ name: 'timeline.finish', at: _timestamps[_i2] }); } } if (eventNames.indexOf('shape.start') !== -1) { for (var _i3 = 0, _l3 = timelineShapes.length; _i3 < _l3; _i3++) { var _timelineShapes$_i = timelineShapes[_i3], shapeName = _timelineShapes$_i.shape.name, start = _timelineShapes$_i.timelinePosition.start; var _timestamps2 = getTimestamps(start); for (var _i = 0, _l = _timestamps2.length; _i < _l; _i++) { queue.push({ name: 'shape.start', at: _timestamps2[_i], options: { shapeName: shapeName } }); } } } if (eventNames.indexOf('shape.finish') !== -1) { for (var _i4 = 0, _l4 = timelineShapes.length; _i4 < _l4; _i4++) { var _timelineShapes$_i2 = timelineShapes[_i4], shapeName = _timelineShapes$_i2.shape.name, finish = _timelineShapes$_i2.timelinePosition.finish; var _timestamps3 = getTimestamps(finish); for (var _i5 = 0, _l5 = _timestamps3.length; _i5 < _l5; _i5++) { queue.push({ name: 'shape.finish', at: _timestamps3[_i5], options: { shapeName: shapeName } }); } } } if (eventNames.indexOf('keyframe') !== -1) { for (var _i6 = 0, _l6 = timelineShapes.length; _i6 < _l6; _i6++) { var _timelineShapes$_i3 = timelineShapes[_i6], _timelineShapes$_i3$s = _timelineShapes$_i3.shape, shapeName = _timelineShapes$_i3$s.name, keyframes = _timelineShapes$_i3$s.keyframes, _timelineShapes$_i3$t = _timelineShapes$_i3.timelinePosition, start = _timelineShapes$_i3$t.start, finish = _timelineShapes$_i3$t.finish; for (var _i7 = 0, _l7 = keyframes.length; _i7 < _l7; _i7++) { var _keyframes$_i = keyframes[_i7], keyframeName = _keyframes$_i.name, position$$1 = _keyframes$_i.position; var keyframePosition = start + (finish - start) * position$$1; var _timestamps4 = getTimestamps(keyframePosition); for (var __i = 0, __l = _timestamps4.length; __i < __l; __i++) { queue.push({ name: 'keyframe', at: _timestamps4[__i], options: { keyframeName: keyframeName, shapeName: shapeName } }); } } } } if (eventNames.indexOf('frame') !== -1) { queue.push({ name: 'frame', at: max }); } return queue.sort(oldest); }; /** * A sort function for Events. * * @param {Event} a * @param {Event} b * * @returns {number} * * @example * oldest(event1, event2) */ var oldest = function oldest(a, b) { return a.at === b.at ? 0 : a.at < b.at ? -1 : 1; }; /** * Have playbackOptions changed since last check? * * @param {Timeline} timeline * * @return {boolean} * * @example * playbackOptionsChanged(timeline) */ var playbackOptionsChanged = function playbackOptionsChanged(timeline) { return JSON.stringify(timeline.playbackOptions) !== JSON.stringify(timeline.event.previousPlaybackOptions); }; /** * Timestamps at which a Timeline was at a Position. * * @param {Object} opts * @param {boolean} opts.alternate * @param {number} opts.duration * @param {number} initialIterations * @param {number} iterations * @param {number} opts.max - The maximum bound within which to look for timestamps. * @param {number} opts.min - The minimum bound within which to look for timestamps. * @param {Position} opts.position - The Position in question. * @param {boolean} opts.reverse * @param {number} opts.started * * @returns {number[]} * * @example * positionTimestamps(opts) */ var positionTimestamps = function positionTimestamps(_ref4) { var alternate = _ref4.alternate, duration = _ref4.duration, initialIterations = _ref4.initialIterations, iterations = _ref4.iterations, max = _ref4.max, min = _ref4.min, position$$1 = _ref4.position, reverse = _ref4.reverse, started = _ref4.started; var startedPosition = position(initialIterations, reverse); var finishedTimestamp = started + duration * iterations; var timestamps = function timestamps(timestamp) { if (timestamp <= max) { var timestampReverse = currentReverse({ alternate: alternate, initialIterations: initialIterations, iterations: iterations, reverse: reverse }, iterationsComplete({ duration: duration, iterations: iterations, started: started }, timestamp)); var positionAtEnd = position$$1 === 0 || position$$1 === 1; var timelineFinished = timestamp === finishedTimestamp; var finishedAtPosition = position$$1 === 0 && timestampReverse || position$$1 === 1 && !timestampReverse; if (timestamp <= finishedTimestamp && (!positionAtEnd || !timelineFinished || finishedAtPosition)) { var _t = timestamp >= min ? [timestamp] : []; return _t.concat(timestamps(timestamp + timeToSamePosition({ alternate: alternate, duration: duration, position: position$$1, reverse: timestampReverse }))); } } return []; }; return timestamps(started + timeToPosition({ alternate: alternate, duration: duration, from: startedPosition, reverse: reverse, to: position$$1 })); }; /** * The number of milliseconds between two Positions during Timeline playback. * * @param {Object} opts * @param {boolean} opts.alternate * @param {number} opts.duration * @param {Position} opts.from - The from Position. * @param {boolean} opts.reverse - Is Timeline in reverse at the from Position? * @param {Position} opts.to - The to Position. * * @returns {number} * * @example * timeToPosition(opts) */ var timeToPosition = function timeToPosition(_ref5) { var alternate = _ref5.alternate, duration = _ref5.duration, from = _ref5.from, reverse = _ref5.reverse, to = _ref5.to; return duration * (alternate ? reverse ? from < to ? to + from : from - to : from > to ? 2 - (to + from) : to - from : reverse ? from === 1 && to === 0 ? 1 : (1 - to + from) % 1 : from === 0 && to === 1 ? 1 : (1 - from + to) % 1); }; /** * The number of milliseconds between the same Position during Timeline playback. * * @param {Object} opts * @param {boolean} opts.alternate * @param {number} opts.duration * @param {Position} opts.position * @param {boolean} opts.reverse - Is Timeline in reverse at the Position? * * @returns {number} * * @example * timeToSamePosition(opts) */ var timeToSamePosition = function timeToSamePosition(_ref6) { var alternate = _ref6.alternate, duration = _ref6.duration, position$$1 = _ref6.position, reverse = _ref6.reverse; return duration * (alternate ? reverse ? (position$$1 === 0 ? 1 : position$$1) * 2 : 2 - (position$$1 === 1 ? 0 : position$$1) * 2 : 1); }; /** * Creates a sub