UNPKG

dynamic-marquee

Version:
987 lines (962 loc) 35.6 kB
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); } function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); } function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _nonIterableSpread(); } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } /** * A boundary represents everything below a given point in the call stack. * * It can have an `onEnter` function which is called on entry and an `onExit` * function which is called on exit. * * To enter the boundry call `enter` with the function you want to run inside * it. On the first call to `enter` in the current stack `onEnter` will be * called before the provided function and `onExit` will be called after it. * * Nested `enter` calls will be called immediately. * * The function provided to `enter` will receive the return value from `onEnter` * as the first argument. This will be `undefined` if the `onEnter` function is still * executing. * * `onExit` will receive the the return value from `onEnter` and also the exception * if one is thrown from an `enter` call. It can choose to handle it, or leave it * to be rethrown. */ var Boundary = /** @class */ (function () { /** * Takes an object with the following properties: * - onEnter (optional): A function that is called immediately before the boundary * is entered. It must not call `enter` on the boundary. * - onExit (optional): A function that is called immediately after leaving the * boundary. It receives an object that contains the following * properties: * - onEnterResult: The return value from `onEnter`. This will be * `undefined` if `onEnter` threw an exception. * - exceptionOccurred: `true` if an exception occured inside * the boundary. * - retrieveException: A function that returns the exception * that occurred. Calling this will prevent * the exception being thrown from `enter()`. * Rethrow it if you don't want to handle it * yourself. * If an exception occurs inside the boundary this will still * be called, and the exception will be rethrown, unless you call * `retrieveException`. */ function Boundary(_a) { var _b = _a === void 0 ? {} : _a, onEnter = _b.onEnter, onExit = _b.onExit; this._execution = null; this.inBoundary = this.inBoundary.bind(this); this.enter = this.enter.bind(this); this._onEnter = onEnter || null; this._onExit = onExit || null; } /** * Returns `true` if called from within the boundary. This includes the `onEnter` * callback. */ Boundary.prototype.inBoundary = function () { return !!this._execution; }; Boundary.prototype.enter = function (fn) { if (this._execution) { return fn ? fn(this._execution.onEnterResult) : undefined; } var execution = (this._execution = { onEnterResult: undefined, }); var returnVal = undefined; var exceptionOccurred = false; var exception = undefined; try { if (this._onEnter) { execution.onEnterResult = this._onEnter(); } if (fn) { returnVal = fn(execution.onEnterResult); } } catch (e) { exceptionOccurred = true; exception = e; } this._execution = null; var exceptionHandled = !exceptionOccurred; if (this._onExit) { try { this._onExit({ onEnterResult: execution.onEnterResult, exceptionOccurred: exceptionOccurred, retrieveException: function () { exceptionHandled = true; return exception; }, }); } catch (e) { if (exceptionHandled) { // if an error occured before onExit prioritise that one // (similar to how `finally` works) throw e; } } } if (!exceptionHandled) { throw exception; } return returnVal; }; return Boundary; }()); var DIRECTION = { RIGHT: 'right', DOWN: 'down' }; function deferException$1(callback) { try { return callback(); } catch (e) { window.setTimeout(function () { throw e; }, 0); } } function Listeners() { var listeners = []; return { add: function add(listener) { listeners.push(listener); return function () { var index = listeners.indexOf(listener); if (index >= 0) listeners.splice(index, 1); }; }, invoke: function invoke() { [].concat(listeners).forEach(function (callback) { return deferException$1(function () { return callback(); }); }); } }; } var SizeWatcher = /*#__PURE__*/function () { function SizeWatcher($el) { var _this = this, _this$_observer; _classCallCheck(this, SizeWatcher); var listeners = Listeners(); this.onSizeChange = listeners.add; this._$el = $el; this._width = null; this._height = null; this._observer = 'ResizeObserver' in window ? new ResizeObserver(function (entries) { var entry = entries[entries.length - 1]; var size = entry.borderBoxSize[0] || entry.borderBoxSize; _this._width = size.inlineSize; _this._height = size.blockSize; listeners.invoke(); }) : null; (_this$_observer = this._observer) === null || _this$_observer === void 0 || _this$_observer.observe($el); } return _createClass(SizeWatcher, [{ key: "getWidth", value: function getWidth() { if (this._width !== null) return this._width; var width = this._$el.getBoundingClientRect().width; if (this._observer) this._width = width; return width; } }, { key: "getHeight", value: function getHeight() { if (this._height !== null) return this._height; var height = this._$el.getBoundingClientRect().height; if (this._observer) this._height = height; return height; } }, { key: "tearDown", value: function tearDown() { var _this$_observer2; (_this$_observer2 = this._observer) === null || _this$_observer2 === void 0 || _this$_observer2.disconnect(); this._observer = null; } }]); }(); var Item = /*#__PURE__*/function () { function Item(_ref) { var $el = _ref.$el, direction = _ref.direction, metadata = _ref.metadata, snapToNeighbor = _ref.snapToNeighbor; _classCallCheck(this, Item); var $container = document.createElement('div'); $container.style.all = 'unset'; $container.style.display = 'block'; $container.style.opacity = '0'; $container.style.pointerEvents = 'none'; $container.style.position = 'absolute'; if (direction === DIRECTION.RIGHT) { $container.style.whiteSpace = 'nowrap'; } else { $container.style.left = '0'; $container.style.right = '0'; } $container.setAttribute('aria-hidden', 'true'); this._sizeWatcher = new SizeWatcher($container); this.onSizeChange = this._sizeWatcher.onSizeChange; $container.appendChild($el); this._$container = $container; this._$el = $el; this._direction = direction; this._metadata = metadata; this._snapToNeighbor = snapToNeighbor; this._offset = null; } return _createClass(Item, [{ key: "getSize", value: function getSize() { var _ref2 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, _ref2$inverse = _ref2.inverse, inverse = _ref2$inverse === void 0 ? false : _ref2$inverse; var dir = this._direction; if (inverse) { dir = dir === DIRECTION.RIGHT ? DIRECTION.DOWN : DIRECTION.RIGHT; } return dir === DIRECTION.RIGHT ? this._sizeWatcher.getWidth() : this._sizeWatcher.getHeight(); } }, { key: "setOffset", value: function setOffset(offset) { if (this._offset === offset) return; this._offset = offset; this._$container.style.removeProperty('opacity'); this._$container.style.removeProperty('pointer-events'); this._$container.removeAttribute('aria-hidden'); if (this._direction === DIRECTION.RIGHT) { this._$container.style.left = "".concat(offset, "px"); } else { this._$container.style.top = "".concat(offset, "px"); } } }, { key: "remove", value: function remove() { this._sizeWatcher.tearDown(); this._$container.parentNode.removeChild(this._$container); } }, { key: "getContainer", value: function getContainer() { return this._$container; } }, { key: "getOriginalEl", value: function getOriginalEl() { return this._$el; } }, { key: "getMetadata", value: function getMetadata() { return this._metadata; } }, { key: "getSnapToNeighbor", value: function getSnapToNeighbor() { return this._snapToNeighbor; } }]); }(); var transitionDuration = 30000; var Slider = /*#__PURE__*/function () { function Slider($el, direction) { _classCallCheck(this, Slider); this._$el = $el; this._direction = direction; this._transitionState = null; } return _createClass(Slider, [{ key: "setOffset", value: function setOffset(offset, rate, force) { var transitionState = this._transitionState; var rateChanged = !transitionState || transitionState.rate !== rate; if (transitionState && !force) { var timePassed = performance.now() - transitionState.time; if (timePassed < transitionDuration - 10000 && !rateChanged) { return; } } if (force || rateChanged) { if (this._direction === DIRECTION.RIGHT) { this._$el.style.transform = "translateX(".concat(offset, "px)"); } else { this._$el.style.transform = "translateY(".concat(offset, "px)"); } this._$el.style.transition = 'none'; this._$el.offsetLeft; } if (rate && (force || rateChanged)) { this._$el.style.transition = "transform ".concat(transitionDuration, "ms linear"); } if (rate) { var futureOffset = offset + rate / 1000 * transitionDuration; if (this._direction === DIRECTION.RIGHT) { this._$el.style.transform = "translateX(".concat(futureOffset, "px)"); } else { this._$el.style.transform = "translateY(".concat(futureOffset, "px)"); } } this._transitionState = { time: performance.now(), rate: rate }; } }]); }(); function defer(fn) { window.setTimeout(function () { return fn(); }, 0); } function deferException(cb) { try { return cb(); } catch (e) { defer(function () { throw e; }); } } function toDomEl($el) { if (typeof $el === 'string' || typeof $el === 'number') { // helper. convert string to div var $div = document.createElement('div'); $div.textContent = $el + ''; return $div; } return $el; } function last(input) { return input.length ? input[input.length - 1] : null; } function first(input) { return input.length ? input[0] : null; } var maxTranslateDistance = 500000; var renderInterval = 100; var Marquee = /*#__PURE__*/function () { function Marquee($container) { var _this = this; var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, _ref$rate = _ref.rate, rate = _ref$rate === void 0 ? -25 : _ref$rate, _ref$upDown = _ref.upDown, upDown = _ref$upDown === void 0 ? false : _ref$upDown, _ref$startOnScreen = _ref.startOnScreen, startOnScreen = _ref$startOnScreen === void 0 ? false : _ref$startOnScreen; _classCallCheck(this, Marquee); this._boundary = new Boundary({ onEnter: function onEnter() { return { callbacks: [] }; }, onExit: function onExit(_ref2) { var callbacks = _ref2.onEnterResult.callbacks; callbacks.forEach(function (cb) { return defer(function () { return cb(); }); }); } }); this._waitingForItem = true; this._askedForItem = true; this._nextAppendIsSynchronous = false; this._rate = rate; this._lastEffectiveRate = rate; this._justReversedRate = false; this._correlation = null; this._direction = upDown ? DIRECTION.DOWN : DIRECTION.RIGHT; this._startOnScreen = startOnScreen; this._onItemRequired = []; this._onItemRemoved = []; this._onAllItemsRemoved = []; this._windowOffset = 0; this._resyncSlider = false; this._gapSize = 0; this._items = []; this._pendingItem = null; this._visible = !!document.hidden; this._waitingForRaf = false; var $window = document.createElement('div'); $window.style.all = 'unset'; $window.style.display = 'block'; $window.style.overflow = 'hidden'; $window.style.position = 'relative'; if (this._direction === DIRECTION.DOWN) { $window.style.height = '100%'; } this._$window = $window; this._containerSizeWatcher = new SizeWatcher($window); this._containerSizeWatcher.onSizeChange(function () { // when the page zoom changes the slider transform transition behaves in a weird way so this resets it _this._resyncSlider = true; _this._tickOnRaf(); }); this.windowInverseSize = null; this._updateWindowInverseSize(); var $moving = document.createElement('div'); this._$moving = $moving; $moving.style.all = 'unset'; $moving.style.display = 'block'; $moving.style.position = 'absolute'; $moving.style.left = '0'; $moving.style.right = '0'; this._slider = new Slider($moving, this._direction); $window.appendChild($moving); $container.appendChild($window); } // called when there's room for a new item. // You can return the item to append next return _createClass(Marquee, [{ key: "onItemRequired", value: function onItemRequired(cb) { this._onItemRequired.push(cb); } // Called when an item is removed }, { key: "onItemRemoved", value: function onItemRemoved(cb) { this._onItemRemoved.push(cb); } // Called when the last item is removed }, { key: "onAllItemsRemoved", value: function onAllItemsRemoved(cb) { this._onAllItemsRemoved.push(cb); } }, { key: "getNumItems", value: function getNumItems() { return this._items.length; } }, { key: "setRate", value: function setRate(rate) { if (rate === this._rate) { return; } if (rate * this._lastEffectiveRate < 0) { this._justReversedRate = !this._justReversedRate; // flip to false which will cause a new item to be asked for if necessary this._waitingForItem = this._askedForItem = false; } this._rate = rate; if (rate) { this._lastEffectiveRate = rate; } this._tick(true); } }, { key: "getRate", value: function getRate() { return this._rate; } }, { key: "clear", value: function clear() { var _this2 = this; this._boundary.enter(function () { _this2._items.forEach(function (_ref3) { var item = _ref3.item; return _this2._removeItem(item); }); _this2._items = []; _this2._waitingForItem = true; _this2._askedForItem = true; _this2._nextAppendIsSynchronous = false; _this2._updateWindowInverseSize(); _this2._cleanup(); }); } }, { key: "isWaitingForItem", value: function isWaitingForItem() { return this._waitingForItem; } }, { key: "watchItemSize", value: function watchItemSize(elOrString) { var $el = toDomEl(elOrString); var item = new Item({ $el: $el, direction: this._direction }); this._$window.appendChild(item.getContainer()); return { getSize: function getSize() { return item.getSize(); }, onSizeChange: item.onSizeChange, stopWatching: function stopWatching() { return item.remove(); } }; } }, { key: "appendItem", value: function appendItem(elOrString) { var _this3 = this; var _ref4 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}, _ref4$metadata = _ref4.metadata, metadata = _ref4$metadata === void 0 ? null : _ref4$metadata, _ref4$snapToNeighbour = _ref4.snapToNeighbour, snapToNeighbour = _ref4$snapToNeighbour === void 0 ? false : _ref4$snapToNeighbour; this._boundary.enter(function () { if (!_this3._waitingForItem) { throw new Error('No room for item.'); } var $el = toDomEl(elOrString); var itemAlreadyExists = _this3._items.some(function (_ref5) { var item = _ref5.item; return item.getOriginalEl() === $el; }); if (itemAlreadyExists) { throw new Error('Item already exists.'); } _this3._waitingForItem = false; _this3._askedForItem = false; var resolvedSnap = snapToNeighbour || _this3._startOnScreen && !_this3._items.length || _this3._nextAppendIsSynchronous; _this3._nextAppendIsSynchronous = false; _this3._pendingItem = new Item({ $el: $el, direction: _this3._direction, metadata: metadata, snapToNeighbor: resolvedSnap }); _this3._pendingItem.onSizeChange(function () { return _this3._tickOnRaf(); }); _this3._tick(); }); } /** * Returns the amount of pixels that need to be filled. * * If `snapToNeighbour` is `true` then this includes the empty space * after the neighbouring item. * * If `snapToNeighbour` is `false`, then this will just contain the buffer * space, unless the `startOnScreen` option is `true` and there are currently * no items. */ }, { key: "getGapSize", value: function getGapSize() { var _ref6 = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, snapToNeighbour = _ref6.snapToNeighbour; if (!this._waitingForItem) return 0; var size; if (this._items.length) { size = snapToNeighbour ? this._gapSize : this._getBuffer(); } else { size = this._startOnScreen || snapToNeighbour ? this._getContainerSize() + this._getBuffer() : this._getBuffer(); } // if we're waiting for an item pretend it's at least 1 to handle cases like where we request for an item // but then the container size gets smalller meaning in reality the gap size becomes negative. If this happens // we don't flip from wanting an item to not wanting one so pretend there is some space. return Math.max(1, size); } }, { key: "_getContainerSize", value: function _getContainerSize() { // if container has size 0 pretend it is 1 to prevent infinite loop // of adding items that are instantly removed return Math.max(this._direction === DIRECTION.RIGHT ? this._containerSizeWatcher.getWidth() : this._containerSizeWatcher.getHeight(), 1); } }, { key: "_getBuffer", value: function _getBuffer() { return renderInterval / 1000 * Math.abs(this._rate); } }, { key: "_removeItem", value: function _removeItem(item) { var _this4 = this; this._boundary.enter(function (_ref7) { var callbacks = _ref7.callbacks; item.remove(); _this4._items.splice(_this4._items.indexOf(item), 1); _this4._onItemRemoved.forEach(function (cb) { callbacks.push(function () { return cb(item.getOriginalEl()); }); }); }); } // update size of container so that the marquee items fit inside it. // This is needed because the items are posisitioned absolutely, so not in normal flow. // Without this, for DIRECTION.RIGHT, the height of the container would always be 0px, which is not useful }, { key: "_updateWindowInverseSize", value: function _updateWindowInverseSize() { if (this._direction === DIRECTION.DOWN) { return; } var maxSize = this._items.length ? Math.max.apply(Math, _toConsumableArray(this._items.map(function (_ref8) { var item = _ref8.item; return item.getSize({ inverse: true }); }))) : 0; if (this.windowInverseSize !== maxSize) { this.windowInverseSize = maxSize; this._$window.style.height = "".concat(maxSize, "px"); } } }, { key: "_scheduleRender", value: function _scheduleRender() { var _this5 = this; if (!this._renderTimer) { // ideally we'd use requestAnimationFrame here but there's a bug in // chrome which means when the callback is called it triggers a style // recalculation even when nothing changes, which is not efficient // see https://bugs.chromium.org/p/chromium/issues/detail?id=1252311 // and https://stackoverflow.com/q/69293778/1048589 this._renderTimer = window.setTimeout(function () { return _this5._tick(); }, renderInterval); } } }, { key: "_cleanup", value: function _cleanup() { this._correlation = null; this._windowOffset = 0; this._resyncSlider = false; } }, { key: "_tickOnRaf", value: function _tickOnRaf() { var _this6 = this; if (!window.requestAnimationFrame || this._waitingForRaf) return; this._waitingForRaf = true; window.requestAnimationFrame(function () { _this6._waitingForRaf = false; _this6._tick(); }); } }, { key: "_tick", value: function _tick() { var _this7 = this; var force = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; this._boundary.enter(function (_ref9) { var callbacks = _ref9.callbacks; _this7._renderTimer && clearTimeout(_this7._renderTimer); _this7._renderTimer = null; if (!force && !_this7._items.length && !_this7._pendingItem) { _this7._cleanup(); return; } _this7._scheduleRender(); if (!_this7._$window.isConnected) { // pause if we've been removed from the dom _this7._correlation = null; return; } var now = performance.now(); var resynced = false; if (_this7._correlation) { var timePassed = now - _this7._correlation.time; _this7._windowOffset = _this7._correlation.offset + _this7._correlation.rate * -1 * (timePassed / 1000); } else { resynced = true; } if (Math.abs(_this7._windowOffset) > maxTranslateDistance) { // resync so that the number of pixels we are translating doesn't get too big resynced = true; var shiftAmount = _this7._windowOffset; _this7._items.forEach(function (item) { return item.offset -= shiftAmount; }); _this7._correlation = null; _this7._windowOffset = 0; } var visible = !document.hidden; var goneVisible = visible && _this7._visible !== visible; _this7._visible = visible; _this7._slider.setOffset(_this7._windowOffset * -1, _this7._rate, resynced || goneVisible || _this7._resyncSlider); _this7._resyncSlider = false; if (!_this7._correlation || _this7._correlation.rate !== _this7._rate) { _this7._correlation = { time: now, offset: _this7._windowOffset, rate: _this7._rate }; } var containerSize = _this7._getContainerSize(); var justReversedRate = _this7._justReversedRate; _this7._justReversedRate = false; // remove items that are off screen _this7._items = _toConsumableArray(_this7._items).filter(function (_ref10) { var item = _ref10.item, offset = _ref10.offset; var keep = _this7._lastEffectiveRate <= 0 ? offset + item.getSize() > _this7._windowOffset : offset < _this7._windowOffset + containerSize; if (!keep) _this7._removeItem(item); return keep; }); // calculate what the new offsets should be given item sizes may have changed _this7._items.reduce(function (newOffset, item) { if (newOffset !== null && ( // size of the item before has increased and would be overlapping item.offset < newOffset || // this item is meant to always snap to the previous item.item.getSnapToNeighbor())) { item.offset = newOffset; } item.item.setOffset(item.offset); return item.offset + item.item.getSize(); }, null); if (_this7._pendingItem) { _this7._$moving.appendChild(_this7._pendingItem.getContainer()); if (_this7._lastEffectiveRate <= 0) { var neighbour = last(_this7._items); var offsetIfWasTouching = neighbour ? neighbour.offset + neighbour.item.getSize() : _this7._windowOffset; _this7._items = [].concat(_toConsumableArray(_this7._items), [{ item: _this7._pendingItem, offset: _this7._pendingItem.getSnapToNeighbor() ? offsetIfWasTouching : Math.max( // edge case that would happen if new item requested and synchronously provided, // but before during that another item size increases, or if new item was provided // when it wasn't strictly needed, which can happen if you have negative rate, // switch to positive which requests an item, and then switch back to negative again // and provide an item offsetIfWasTouching, _this7._windowOffset + containerSize) }]); } else { var _neighbour = first(_this7._items); var _offsetIfWasTouching = _neighbour ? _neighbour.offset - _this7._pendingItem.getSize() : _this7._windowOffset + containerSize - _this7._pendingItem.getSize(); _this7._items = [{ item: _this7._pendingItem, offset: _this7._pendingItem.getSnapToNeighbor() ? _offsetIfWasTouching : Math.min( // edge case that would happen if new item was provided when it wasn't strictly needed, // which can happen if you have positive rate, switch to negative which requests an item, // and then switch back to positive again and provide an item _offsetIfWasTouching, _this7._windowOffset - _this7._pendingItem.getSize()) }].concat(_toConsumableArray(_this7._items)); } _this7._pendingItem = null; } var nextItemTouching = null; _this7._gapSize = 0; // add a buffer on the side to make sure that new elements are added before they would actually be on screen var buffer = _this7._getBuffer(); if (_this7._items.length) { var firstItem = first(_this7._items); var lastItem = last(_this7._items); var _neighbour2 = _this7._lastEffectiveRate <= 0 ? lastItem : firstItem; if (_this7._lastEffectiveRate <= 0) { _this7._gapSize = containerSize + buffer - (lastItem.offset + lastItem.item.getSize() - _this7._windowOffset); } else { _this7._gapSize = firstItem.offset - _this7._windowOffset + buffer; } if (_this7._gapSize > 0) { _this7._waitingForItem = true; // if an item is appended immediately below, it would be considered touching // the previous if we haven't just changed direction. // This is useful when deciding whether to add a separator on the side that enters the // screen first or not nextItemTouching = !justReversedRate ? { $el: _neighbour2.item.getOriginalEl(), metadata: _neighbour2.item.getMetadata() } : null; } } else { _this7._waitingForItem = true; } if (!_this7._items.length) { _this7._onAllItemsRemoved.forEach(function (cb) { return callbacks.push(cb); }); } _this7._updateWindowInverseSize(); if (_this7._waitingForItem && !_this7._askedForItem) { _this7._askedForItem = true; var nextItem; if (nextItemTouching) { _this7._nextAppendIsSynchronous = true; } _this7._onItemRequired.some(function (cb) { return deferException(function () { nextItem = cb({ /** @deprecated */ immediatelyFollowsPrevious: !!nextItemTouching, touching: nextItemTouching }); return !!nextItem; }); }); if (nextItem) { // Note appendItem() will call _tick() synchronously again _this7.appendItem(nextItem); } _this7._nextAppendIsSynchronous = false; } }); } }]); }(); function getDefaultExportFromCjs (x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x; } var indexMap = function(list) { var map = {}; list.forEach(function(each, i) { map[each] = map[each] || []; map[each].push(i); }); return map }; var longestCommonSubstring = function(seq1, seq2) { var result = {startString1:0, startString2:0, length:0}; var indexMapBefore = indexMap(seq1); var previousOverlap = []; seq2.forEach(function(eachAfter, indexAfter) { var overlapLength; var overlap = []; var indexesBefore = indexMapBefore[eachAfter] || []; indexesBefore.forEach(function(indexBefore) { overlapLength = ((indexBefore && previousOverlap[indexBefore-1]) || 0) + 1; if (overlapLength > result.length) { result.length = overlapLength; result.startString1 = indexBefore - overlapLength + 1; result.startString2 = indexAfter - overlapLength + 1; } overlap[indexBefore] = overlapLength; }); previousOverlap = overlap; }); return result }; var longestCommonSubstring_1 = longestCommonSubstring; var longestSubstring = /*@__PURE__*/getDefaultExportFromCjs(longestCommonSubstring_1); function loop(marquee) { var buildersIn = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : []; var seperatorBuilder = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; var lastIndex = -1; var builders = buildersIn.slice(); var getNextBuilder = function getNextBuilder() { var offset = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1; var nextIndex = (lastIndex + offset) % builders.length; return { builder: builders[nextIndex], index: nextIndex }; }; var appendItem = function appendItem(touching) { var _touching$metadata; if (!builders.length || !marquee.isWaitingForItem()) { return; } if (seperatorBuilder && touching && ((_touching$metadata = touching.metadata) === null || _touching$metadata === void 0 ? void 0 : _touching$metadata.isSeperator) !== true) { var $el = toDomEl(seperatorBuilder()); marquee.appendItem($el, { metadata: { isSeperator: true } }); return; } var _getNextBuilder = getNextBuilder(), builder = _getNextBuilder.builder, index = _getNextBuilder.index; lastIndex = index; marquee.appendItem(toDomEl(builder())); }; marquee.onItemRequired(function (_ref) { var touching = _ref.touching; return appendItem(touching); }); appendItem(); return { update: function update(newBuilders) { // try and start from somewhere that makes sense var calculateNewIndex = function calculateNewIndex() { // convert array of function references to array of ids var buildersStructure = builders.map(function (b, i) { var prevIndex = builders.indexOf(b); // if already seen builder, give it the same number return prevIndex < i ? prevIndex : i; }); var newBuildersStructure = newBuilders.map(function (b) { // matching indexes where they exist, and -1 for all unknown return builders.indexOf(b); }); var _longestSubstring = longestSubstring(buildersStructure, newBuildersStructure), startString1 = _longestSubstring.startString1, startString2 = _longestSubstring.startString2, length = _longestSubstring.length; if (lastIndex >= startString1 && lastIndex < startString1 + length) { // we are in the overlapping region return lastIndex + (startString2 - startString1); } return -1; }; lastIndex = calculateNewIndex(); builders = newBuilders.slice(); appendItem(false); } }; } export { Marquee, loop };