UNPKG

timing-object

Version:

An implementation of the timing object specification.

478 lines (461 loc) 22.7 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('@babel/runtime/helpers/classCallCheck'), require('@babel/runtime/helpers/createClass'), require('@babel/runtime/helpers/defineProperty'), require('@babel/runtime/helpers/possibleConstructorReturn'), require('@babel/runtime/helpers/getPrototypeOf'), require('@babel/runtime/helpers/inherits')) : typeof define === 'function' && define.amd ? define(['exports', '@babel/runtime/helpers/classCallCheck', '@babel/runtime/helpers/createClass', '@babel/runtime/helpers/defineProperty', '@babel/runtime/helpers/possibleConstructorReturn', '@babel/runtime/helpers/getPrototypeOf', '@babel/runtime/helpers/inherits'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.timingObject = {}, global._classCallCheck, global._createClass, global._defineProperty, global._possibleConstructorReturn, global._getPrototypeOf, global._inherits)); })(this, (function (exports, _classCallCheck, _createClass, _defineProperty, _possibleConstructorReturn, _getPrototypeOf, _inherits) { 'use strict'; var createCalculateDelta = function createCalculateDelta(calculatePositiveRealSolution) { return function (vector, startPosition, endPosition) { var deltaToStartPosition = calculatePositiveRealSolution(vector, startPosition); var deltaToEndPosition = calculatePositiveRealSolution(vector, endPosition); // @todo Is it theoretically possible to have both deltas? if (deltaToStartPosition !== null && deltaToEndPosition !== null) { if (deltaToStartPosition < deltaToEndPosition) { return deltaToStartPosition; } return deltaToEndPosition; } if (deltaToStartPosition !== null) { return deltaToStartPosition; } if (deltaToEndPosition !== null) { return deltaToEndPosition; } return null; }; }; var createCalculatePositiveRealSolution = function createCalculatePositiveRealSolution(calculateRealSolutions) { return function (_ref, x) { var acceleration = _ref.acceleration, position = _ref.position, velocity = _ref.velocity; var results = calculateRealSolutions(position, velocity, acceleration, x); if (results.length === 0) { return null; } if (results.length === 1) { if (results[0] > 0) { return results[0]; } return null; } if (results.length === 2) { if (results[1] < 0) { return null; } if (results[0] > 0) { return results[0]; } if (results[1] > 0) { return results[1]; } } return null; }; }; var createCalculateTimeoutDelay = function createCalculateTimeoutDelay(calculateDelta) { return function (vector, startPosition, endPosition) { var delta = calculateDelta(vector, startPosition, endPosition); if (delta === null || delta === Number.POSITIVE_INFINITY) { return null; } return delta; }; }; var createEventTargetConstructor = function createEventTargetConstructor(createEventTarget, wrapEventListener) { return /*#__PURE__*/function () { function EventTarget() { _classCallCheck(this, EventTarget); this._listeners = new WeakMap(); this._nativeEventTarget = createEventTarget(); } return _createClass(EventTarget, [{ key: "addEventListener", value: function addEventListener(type, listener, options) { if (listener !== null) { var wrappedEventListener = this._listeners.get(listener); if (wrappedEventListener === undefined) { wrappedEventListener = wrapEventListener(this, listener); if (typeof listener === 'function') { this._listeners.set(listener, wrappedEventListener); } } this._nativeEventTarget.addEventListener(type, wrappedEventListener, options); } } }, { key: "dispatchEvent", value: function dispatchEvent(event) { return this._nativeEventTarget.dispatchEvent(event); } }, { key: "removeEventListener", value: function removeEventListener(type, listener, options) { var wrappedEventListener = listener === null ? undefined : this._listeners.get(listener); this._nativeEventTarget.removeEventListener(type, wrappedEventListener === undefined ? null : wrappedEventListener, options); } }]); }(); }; var createEventTargetFactory = function createEventTargetFactory(window) { return function () { if (window === null) { throw new Error('A native EventTarget could not be created.'); } return window.document.createElement('p'); }; }; var createIllegalValueError = function createIllegalValueError() { try { return new DOMException('', 'IllegalValueError'); } catch (err) { // @todo err.code; err.name = 'IllegalValueError'; return err; } }; var createInvalidStateError = function createInvalidStateError() { try { return new DOMException('', 'InvalidStateError'); } catch (err) { err.code = 11; err.name = 'InvalidStateError'; return err; } }; function ownKeys$1(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread$1(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys$1(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys$1(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, [], _getPrototypeOf(t).constructor) : o.apply(t, e)); } function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); } var createTimingObjectConstructor = function createTimingObjectConstructor(calculateTimeoutDelay, createIllegalValueError, createInvalidStateError, eventTargetConstructor, filterTimingStateVectorUpdate, performance, setTimeout, translateTimingStateVector) { return /*#__PURE__*/function (_eventTargetConstruct) { function _class() { var _this; var timingProviderSourceOrVector = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; var startPosition = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : Number.NEGATIVE_INFINITY; var endPosition = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : Number.POSITIVE_INFINITY; _classCallCheck(this, _class); _this = _callSuper(this, _class); var _ref = timingProviderSourceOrVector.update === undefined ? { timingProviderSource: null, vector: timingProviderSourceOrVector } : { timingProviderSource: timingProviderSourceOrVector, vector: {} }, timingProviderSource = _ref.timingProviderSource, vector = _ref.vector; _this._endPosition = timingProviderSource === null ? endPosition : timingProviderSource.endPosition; _this._onchange = null; _this._onerror = null; _this._onreadystatechange = null; _this._readyState = timingProviderSource === null ? 'open' : timingProviderSource.readyState; _this._skew = timingProviderSource === null ? 0 : timingProviderSource.skew; _this._startPosition = timingProviderSource === null ? startPosition : timingProviderSource.startPosition; _this._timingProviderSource = timingProviderSource; _this._timeoutId = null; _this._vector = timingProviderSource === null ? _objectSpread$1(_objectSpread$1({ acceleration: 0, position: 0, velocity: 0 }, filterTimingStateVectorUpdate(vector)), {}, { timestamp: performance.now() / 1000 }) : timingProviderSource.vector; // @todo The spec doesn't require to check if the endPosition is actually greater than the startPosition. if (endPosition < _this._vector.position) { _this._vector = _objectSpread$1(_objectSpread$1({}, _this._vector), {}, { acceleration: 0, position: endPosition, velocity: 0 }); } if (startPosition > _this._vector.position) { _this._vector = _objectSpread$1(_objectSpread$1({}, _this._vector), {}, { acceleration: 0, position: startPosition, velocity: 0 }); } /* * @todo Check if the vector would leave the range immediately. * @todo The specification requires to run this._setInternalTimeout() only if the vector had to be modified above but it * probably should run in either case. * https://webtiming.github.io/timingobject/#x5-1-create-a-new-timing-object */ _this._setInternalTimeout(); if (timingProviderSource === null) { setTimeout(function () { return _this.dispatchEvent(new Event('readystatechange')); }); } else { var onAdjust = function onAdjust() { _this._skew = timingProviderSource.skew; /* * @todo Process skew change with newSkew as parameter. * https://webtiming.github.io/timingobject/#x5-7-process-skew-change * https://webtiming.github.io/timingobject/#x5-10-calculate-skew-adjustment */ }; var onChange = function onChange() { return _this._setInternalVector(timingProviderSource.vector); }; var _onReadyStateChange = function onReadyStateChange() { if (_this._isAllowedTransition(timingProviderSource.readyState)) { _this._readyState = timingProviderSource.readyState; } else { _this._readyState = 'closed'; timingProviderSource.removeEventListener('adjust', onAdjust); timingProviderSource.removeEventListener('change', onChange); timingProviderSource.removeEventListener('readystatechange', _onReadyStateChange); } if (timingProviderSource.error !== null) { setTimeout(function () { return _this.dispatchEvent(new ErrorEvent('error', { error: timingProviderSource.error })); }); } setTimeout(function () { return _this.dispatchEvent(new Event('readystatechange')); }); }; timingProviderSource.addEventListener('adjust', onAdjust); timingProviderSource.addEventListener('change', onChange); timingProviderSource.addEventListener('readystatechange', _onReadyStateChange); } return _this; } _inherits(_class, _eventTargetConstruct); return _createClass(_class, [{ key: "endPosition", get: function get() { return this._endPosition; } }, { key: "onchange", get: function get() { return this._onchange === null ? this._onchange : this._onchange[0]; }, set: function set(value) { if (this._onchange !== null) { this.removeEventListener('change', this._onchange[1]); } if (typeof value === 'function') { var boundListener = value.bind(this); this.addEventListener('change', boundListener); this._onchange = [value, boundListener]; } else { this._onchange = null; } } }, { key: "onerror", get: function get() { return this._onerror === null ? this._onerror : this._onerror[0]; }, set: function set(value) { if (this._onerror !== null) { this.removeEventListener('error', this._onerror[1]); } if (typeof value === 'function') { var boundListener = value.bind(this); this.addEventListener('error', boundListener); this._onerror = [value, boundListener]; } else { this._onerror = null; } } }, { key: "onreadystatechange", get: function get() { return this._onreadystatechange === null ? this._onreadystatechange : this._onreadystatechange[0]; }, set: function set(value) { if (this._onreadystatechange !== null) { this.removeEventListener('readystatechange', this._onreadystatechange[1]); } if (typeof value === 'function') { var boundListener = value.bind(this); this.addEventListener('readystatechange', boundListener); this._onreadystatechange = [value, boundListener]; } else { this._onreadystatechange = null; } } }, { key: "readyState", get: function get() { return this._readyState; } }, { key: "startPosition", get: function get() { return this._startPosition; } }, { key: "timingProviderSource", get: function get() { return this._timingProviderSource; } }, { key: "query", value: function query() { if (this._readyState !== 'open') { throw createInvalidStateError(); } var timestamp = performance.now() / 1000; // @todo Compute the delta by gradually applying the skew. var delta = this._timingProviderSource === null ? timestamp - this._vector.timestamp : timestamp + this._skew - this._vector.timestamp; var vector = translateTimingStateVector(this._vector, delta); if (this._endPosition < vector.position || this._startPosition > vector.position) { this._setInternalVector(_objectSpread$1(_objectSpread$1({}, vector), {}, { acceleration: 0, position: this._endPosition < vector.position ? this._endPosition : this._startPosition, velocity: 0 })); return this.query(); } return vector; } }, { key: "update", value: function update(newVector) { if (this._readyState !== 'open') { return Promise.reject(createInvalidStateError()); } if (this._timingProviderSource !== null) { var promise = this._timingProviderSource.update(newVector); if (promise instanceof Promise) { return promise; } return Promise.reject(new TypeError('The timingProviderSource failed to return a promise.')); } var filteredVector = filterTimingStateVectorUpdate(newVector); // Return immediately if there is nothing to update. if (Object.keys(filteredVector).length === 0) { return Promise.resolve(); } var normalizedNewVector = _objectSpread$1(_objectSpread$1({}, this.query()), filteredVector); var position = normalizedNewVector.position, velocity = normalizedNewVector.velocity, acceleration = normalizedNewVector.acceleration; if (position < this._startPosition || position > this._endPosition || position === this._startPosition && (velocity < 0 || velocity === 0 && acceleration < 0) || position === this._endPosition && (velocity > 0 || velocity === 0 && acceleration > 0)) { return Promise.reject(createIllegalValueError()); } this._setInternalVector(normalizedNewVector); return Promise.resolve(); } }, { key: "_isAllowedTransition", value: function _isAllowedTransition(readyState) { return this._readyState === 'closing' && readyState === 'closed' || this._readyState === 'connecting' || this._readyState === 'open' && (readyState === 'closed' || readyState === 'closing'); } }, { key: "_setInternalTimeout", value: function _setInternalTimeout() { var _this2 = this; if (this._timeoutId !== null) { clearTimeout(this._timeoutId); this._timeoutId = null; } if (this._endPosition === Number.POSITIVE_INFINITY && this._startPosition === Number.NEGATIVE_INFINITY || this._vector.acceleration === 0 && this._vector.velocity === 0) { return; } var delay = calculateTimeoutDelay(this._vector, this._startPosition, this._endPosition); if (delay === null) { return; } this._timeoutId = setTimeout(function () { return _this2.query(); }, delay); } }, { key: "_setInternalVector", value: function _setInternalVector(vector) { var _this3 = this; this._vector = vector; this._setInternalTimeout(); setTimeout(function () { return _this3.dispatchEvent(new Event('change')); }); } }]); }(eventTargetConstructor); }; var createWindow = function createWindow() { return typeof window === 'undefined' ? null : window; }; var calculateRealSolutions = function calculateRealSolutions(position, velocity, acceleration, x) { // The position is constant. if (acceleration === 0 && velocity === 0) { if (position !== x) { return []; } return [0]; } // The velocity is constant and not equal to zero. if (acceleration === 0) { return [(x - position) / velocity]; } // The velocity is accelerated. // This equals p/2 of the pq-formula. var firstSummand = velocity / acceleration; // This equals sqrt((p/2 ** 2) - q) of the pq-formula. var secondSummand = Math.sqrt(Math.pow(firstSummand, 2) - 2 / acceleration * (position - x)); return [secondSummand - firstSummand, -(secondSummand + firstSummand)].filter(function (solution) { return !isNaN(solution); }).sort(); }; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), true).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } var filterTimingStateVectorUpdate = function filterTimingStateVectorUpdate(vector) { if (vector === undefined) { return {}; } var filteredVector = vector.acceleration !== null && vector.acceleration !== undefined ? { acceleration: vector.acceleration } : {}; if (vector.position !== null && vector.position !== undefined) { filteredVector = _objectSpread(_objectSpread({}, filteredVector), {}, { position: vector.position }); } if (vector.velocity !== null && vector.velocity !== undefined) { return _objectSpread(_objectSpread({}, filteredVector), {}, { velocity: vector.velocity }); } return filteredVector; }; var translateTimingStateVector = function translateTimingStateVector(vector, delta) { var acceleration = vector.acceleration, position = vector.position, timestamp = vector.timestamp, velocity = vector.velocity; return { acceleration: acceleration, position: position + velocity * delta + 0.5 * acceleration * Math.pow(delta, 2), timestamp: timestamp + delta, velocity: velocity + acceleration * delta }; }; var wrapEventListener = function wrapEventListener(target, eventListener) { return function (event) { var descriptor = { value: target }; Object.defineProperties(event, { currentTarget: descriptor, target: descriptor }); if (typeof eventListener === 'function') { return eventListener.call(target, event); } return eventListener.handleEvent.call(target, event); }; }; var timingObjectConstructor = createTimingObjectConstructor(createCalculateTimeoutDelay(createCalculateDelta(createCalculatePositiveRealSolution(calculateRealSolutions))), createIllegalValueError, createInvalidStateError, createEventTargetConstructor(createEventTargetFactory(createWindow()), wrapEventListener), filterTimingStateVectorUpdate, performance, setTimeout, translateTimingStateVector); // @todo Expose an isSupported flag which checks for performance.now() support. exports.TimingObject = timingObjectConstructor; exports.filterTimingStateVectorUpdate = filterTimingStateVectorUpdate; exports.translateTimingStateVector = translateTimingStateVector; }));