UNPKG

@egjs/flicking

Version:

Everyday 30 million people experience. It's reliable, flexible and extendable carousel.

1,786 lines (1,464 loc) 171 kB
/* Copyright (c) 2015-present NAVER Corp. name: @egjs/flicking license: MIT author: NAVER Corp. repository: https://github.com/naver/egjs-flicking version: 3.7.1 */ import Component from '@egjs/component'; import ImReady from '@egjs/imready'; import Axes, { PanInput } from '@egjs/axes'; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise */ var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || { __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; } || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; function __extends(d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); } function __spreadArrays() { for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; for (var r = Array(s), k = 0, i = 0; i < il; i++) for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; } /** * Copyright (c) 2015 NAVER Corp. * egjs projects are licensed under the MIT license */ var MOVE_TYPE = { SNAP: "snap", FREE_SCROLL: "freeScroll" }; var DEFAULT_MOVE_TYPE_OPTIONS = { snap: { type: "snap", count: 1 }, freeScroll: { type: "freeScroll" } }; var isBrowser = typeof document !== "undefined"; /** * Default options for creating Flicking. * @ko 플리킹을 만들 때 사용하는 기본 옵션들 * @private * @memberof eg.Flicking */ var DEFAULT_OPTIONS = { classPrefix: "eg-flick", deceleration: 0.0075, horizontal: true, circular: false, infinite: false, infiniteThreshold: 0, lastIndex: Infinity, threshold: 40, duration: 100, panelEffect: function (x) { return 1 - Math.pow(1 - x, 3); }, defaultIndex: 0, inputType: ["touch", "mouse"], thresholdAngle: 45, bounce: 10, autoResize: false, adaptive: false, zIndex: 2000, bound: false, overflow: false, hanger: "50%", anchor: "50%", gap: 0, moveType: DEFAULT_MOVE_TYPE_OPTIONS.snap, useOffset: false, isEqualSize: false, isConstantSize: false, renderOnlyVisible: false, renderExternal: false, resizeOnContentsReady: false, iOSEdgeSwipeThreshold: 30, collectStatistics: true }; var DEFAULT_VIEWPORT_CSS = { position: "relative", zIndex: DEFAULT_OPTIONS.zIndex, overflow: "hidden" }; var DEFAULT_CAMERA_CSS = { width: "100%", height: "100%", willChange: "transform" }; var DEFAULT_PANEL_CSS = { position: "absolute" }; var EVENTS = { HOLD_START: "holdStart", HOLD_END: "holdEnd", MOVE_START: "moveStart", MOVE: "move", MOVE_END: "moveEnd", CHANGE: "change", RESTORE: "restore", SELECT: "select", NEED_PANEL: "needPanel", VISIBLE_CHANGE: "visibleChange", CONTENT_ERROR: "contentError" }; var AXES_EVENTS = { HOLD: "hold", CHANGE: "change", RELEASE: "release", ANIMATION_END: "animationEnd", FINISH: "finish" }; var STATE_TYPE = { IDLE: 0, HOLDING: 1, DRAGGING: 2, ANIMATING: 3, DISABLED: 4 }; var DIRECTION = { PREV: "PREV", NEXT: "NEXT" }; var FLICKING_METHODS = { prev: true, next: true, moveTo: true, getIndex: true, getAllPanels: true, getCurrentPanel: true, getElement: true, getSize: true, getPanel: true, getPanelCount: true, getStatus: true, getVisiblePanels: true, enableInput: true, disableInput: true, destroy: true, resize: true, setStatus: true, isPlaying: true }; // Check whether browser supports transform: translate3d // https://stackoverflow.com/questions/5661671/detecting-transform-translate3d-support var checkTranslateSupport = function () { var transforms = { webkitTransform: "-webkit-transform", msTransform: "-ms-transform", MozTransform: "-moz-transform", OTransform: "-o-transform", transform: "transform" }; if (!isBrowser) { return { name: transforms.transform, has3d: true }; } var supportedStyle = document.documentElement.style; var transformName = ""; for (var prefixedTransform in transforms) { if (prefixedTransform in supportedStyle) { transformName = prefixedTransform; } } if (!transformName) { throw new Error("Browser doesn't support CSS3 2D Transforms."); } var el = document.createElement("div"); document.documentElement.insertBefore(el, null); el.style[transformName] = "translate3d(1px, 1px, 1px)"; var styleVal = window.getComputedStyle(el).getPropertyValue(transforms[transformName]); el.parentElement.removeChild(el); var transformInfo = { name: transformName, has3d: styleVal.length > 0 && styleVal !== "none" }; checkTranslateSupport = function () { return transformInfo; }; return transformInfo; }; var TRANSFORM = checkTranslateSupport(); /** * Copyright (c) 2015 NAVER Corp. * egjs projects are licensed under the MIT license */ function merge(target) { var srcs = []; for (var _i = 1; _i < arguments.length; _i++) { srcs[_i - 1] = arguments[_i]; } srcs.forEach(function (source) { Object.keys(source).forEach(function (key) { var value = source[key]; target[key] = value; }); }); return target; } function parseElement(element) { if (!Array.isArray(element)) { element = [element]; } var elements = []; element.forEach(function (el) { if (isString(el)) { var tempDiv = document.createElement("div"); tempDiv.innerHTML = el; elements.push.apply(elements, toArray(tempDiv.children)); while (tempDiv.firstChild) { tempDiv.removeChild(tempDiv.firstChild); } } else { elements.push(el); } }); return elements; } function isString(value) { return typeof value === "string"; } // Get class list of element as string array function addClass(element, className) { if (element.classList) { element.classList.add(className); } else { if (!hasClass(element, className)) { element.className = (element.className + " " + className).replace(/\s{2,}/g, " "); } } } function hasClass(element, className) { if (element.classList) { return element.classList.contains(className); } else { return element.className.split(" ").indexOf(className) >= 0; } } function applyCSS(element, cssObj) { Object.keys(cssObj).forEach(function (property) { element.style[property] = cssObj[property]; }); } function clamp(val, min, max) { return Math.max(Math.min(val, max), min); } // Min: inclusive, Max: exclusive function isBetween(val, min, max) { return val >= min && val <= max; } function toArray(iterable) { return [].slice.call(iterable); } function isArray(arr) { return arr && arr.constructor === Array; } function parseArithmeticExpression(cssValue, base, defaultVal) { // Set base / 2 to default value, if it's undefined var defaultValue = defaultVal != null ? defaultVal : base / 2; var cssRegex = /(?:(\+|\-)\s*)?(\d+(?:\.\d+)?(%|px)?)/g; if (typeof cssValue === "number") { return clamp(cssValue, 0, base); } var idx = 0; var calculatedValue = 0; var matchResult = cssRegex.exec(cssValue); while (matchResult != null) { var sign = matchResult[1]; var value = matchResult[2]; var unit = matchResult[3]; var parsedValue = parseFloat(value); if (idx <= 0) { sign = sign || "+"; } // Return default value for values not in good form if (!sign) { return defaultValue; } if (unit === "%") { parsedValue = parsedValue / 100 * base; } calculatedValue += sign === "+" ? parsedValue : -parsedValue; // Match next occurrence ++idx; matchResult = cssRegex.exec(cssValue); } // None-matched if (idx === 0) { return defaultValue; } // Clamp between 0 ~ base return clamp(calculatedValue, 0, base); } function getProgress(pos, range) { // start, anchor, end // -1 , 0 , 1 var min = range[0], center = range[1], max = range[2]; if (pos > center && max - center) { // 0 ~ 1 return (pos - center) / (max - center); } else if (pos < center && center - min) { // -1 ~ 0 return (pos - center) / (center - min); } else if (pos !== center && max - min) { return (pos - min) / (max - min); } return 0; } function findIndex(iterable, callback) { for (var i = 0; i < iterable.length; i += 1) { var element = iterable[i]; if (element && callback(element)) { return i; } } return -1; } // return [0, 1, ...., max - 1] function counter(max) { var counterArray = []; for (var i = 0; i < max; i += 1) { counterArray[i] = i; } return counterArray; } // Circulate number between range [min, max] /* * "indexed" means min and max is not same, so if it's true "min - 1" should be max * While if it's false, "min - 1" should be "max - 1" * use `indexed: true` when it should be used for circulating integers like index * or `indexed: false` when it should be used for something like positions. */ function circulate(value, min, max, indexed) { var size = indexed ? max - min + 1 : max - min; if (value < min) { var offset = indexed ? (min - value - 1) % size : (min - value) % size; value = max - offset; } else if (value > max) { var offset = indexed ? (value - max - 1) % size : (value - max) % size; value = min + offset; } return value; } function restoreStyle(element, originalStyle) { originalStyle.className ? element.setAttribute("class", originalStyle.className) : element.removeAttribute("class"); originalStyle.style ? element.setAttribute("style", originalStyle.style) : element.removeAttribute("style"); } /** * Decorator that makes the method of flicking available in the framework. * @ko 프레임워크에서 플리킹의 메소드를 사용할 수 있게 하는 데코레이터. * @memberof eg.Flicking * @private * @example * ```js * import Flicking, { withFlickingMethods } from "@egjs/flicking"; * * class Flicking extends React.Component<Partial<FlickingProps & FlickingOptions>> { * &#64;withFlickingMethods * private flicking: Flicking; * } * ``` */ function withFlickingMethods(prototype, flickingName) { Object.keys(FLICKING_METHODS).forEach(function (name) { if (prototype[name]) { return; } prototype[name] = function () { var _a; var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } var result = (_a = this[flickingName])[name].apply(_a, args); // fix `this` type to return your own `flicking` instance to the instance using the decorator. if (result === this[flickingName]) { return this; } else { return result; } }; }); } function getBbox(element, useOffset) { var bbox; if (useOffset) { bbox = { x: 0, y: 0, width: element.offsetWidth, height: element.offsetHeight }; } else { var clientRect = element.getBoundingClientRect(); bbox = { x: clientRect.left, y: clientRect.top, width: clientRect.width, height: clientRect.height }; } return bbox; } /** * Copyright (c) 2015 NAVER Corp. * egjs projects are licensed under the MIT license */ var Panel = /*#__PURE__*/ function () { function Panel(element, index, viewport) { this.viewport = viewport; this.prevSibling = null; this.nextSibling = null; this.clonedPanels = []; this.state = { index: index, position: 0, relativeAnchorPosition: 0, size: 0, isClone: false, isVirtual: false, cloneIndex: -1, originalStyle: { className: "", style: "" }, cachedBbox: null }; this.setElement(element); } var __proto = Panel.prototype; __proto.resize = function (givenBbox) { var state = this.state; var options = this.viewport.options; var bbox = givenBbox ? givenBbox : this.getBbox(); this.state.cachedBbox = bbox; var prevSize = state.size; state.size = options.horizontal ? bbox.width : bbox.height; if (prevSize !== state.size) { state.relativeAnchorPosition = parseArithmeticExpression(options.anchor, state.size); } if (!state.isClone) { this.clonedPanels.forEach(function (panel) { var cloneState = panel.state; cloneState.size = state.size; cloneState.cachedBbox = state.cachedBbox; cloneState.relativeAnchorPosition = state.relativeAnchorPosition; }); } }; __proto.unCacheBbox = function () { this.state.cachedBbox = null; }; __proto.getProgress = function () { var viewport = this.viewport; var options = viewport.options; var panelCount = viewport.panelManager.getPanelCount(); var scrollAreaSize = viewport.getScrollAreaSize(); var relativeIndex = (options.circular ? Math.floor(this.getPosition() / scrollAreaSize) * panelCount : 0) + this.getIndex(); var progress = relativeIndex - viewport.getCurrentProgress(); return progress; }; __proto.getOutsetProgress = function () { var viewport = this.viewport; var outsetRange = [-this.getSize(), viewport.getRelativeHangerPosition() - this.getRelativeAnchorPosition(), viewport.getSize()]; var relativePanelPosition = this.getPosition() - viewport.getCameraPosition(); var outsetProgress = getProgress(relativePanelPosition, outsetRange); return outsetProgress; }; __proto.getVisibleRatio = function () { var viewport = this.viewport; var panelSize = this.getSize(); var relativePanelPosition = this.getPosition() - viewport.getCameraPosition(); var rightRelativePanelPosition = relativePanelPosition + panelSize; var visibleSize = Math.min(viewport.getSize(), rightRelativePanelPosition) - Math.max(relativePanelPosition, 0); var visibleRatio = visibleSize >= 0 ? visibleSize / panelSize : 0; return visibleRatio; }; __proto.focus = function (duration) { var viewport = this.viewport; var currentPanel = viewport.getCurrentPanel(); var hangerPosition = viewport.getHangerPosition(); var anchorPosition = this.getAnchorPosition(); if (hangerPosition === anchorPosition || !currentPanel) { return; } var currentPosition = currentPanel.getPosition(); var eventType = currentPosition === this.getPosition() ? "" : EVENTS.CHANGE; viewport.moveTo(this, viewport.findEstimatedPosition(this), eventType, null, duration); }; __proto.update = function (updateFunction, shouldResize) { if (updateFunction === void 0) { updateFunction = null; } if (shouldResize === void 0) { shouldResize = true; } var identicalPanels = this.getIdenticalPanels(); if (updateFunction) { identicalPanels.forEach(function (eachPanel) { updateFunction(eachPanel.getElement()); }); } if (shouldResize) { identicalPanels.forEach(function (eachPanel) { eachPanel.unCacheBbox(); }); this.viewport.addVisiblePanel(this); this.viewport.resize(); } }; __proto.prev = function () { var viewport = this.viewport; var options = viewport.options; var prevSibling = this.prevSibling; if (!prevSibling) { return null; } var currentIndex = this.getIndex(); var currentPosition = this.getPosition(); var prevPanelIndex = prevSibling.getIndex(); var prevPanelPosition = prevSibling.getPosition(); var prevPanelSize = prevSibling.getSize(); var hasEmptyPanelBetween = currentIndex - prevPanelIndex > 1; var notYetMinPanel = options.infinite && currentIndex > 0 && prevPanelIndex > currentIndex; if (hasEmptyPanelBetween || notYetMinPanel) { // Empty panel exists between return null; } var newPosition = currentPosition - prevPanelSize - options.gap; var prevPanel = prevSibling; if (prevPanelPosition !== newPosition) { prevPanel = prevSibling.clone(prevSibling.getCloneIndex(), true); prevPanel.setPosition(newPosition); } return prevPanel; }; __proto.next = function () { var viewport = this.viewport; var options = viewport.options; var nextSibling = this.nextSibling; var lastIndex = viewport.panelManager.getLastIndex(); if (!nextSibling) { return null; } var currentIndex = this.getIndex(); var currentPosition = this.getPosition(); var nextPanelIndex = nextSibling.getIndex(); var nextPanelPosition = nextSibling.getPosition(); var hasEmptyPanelBetween = nextPanelIndex - currentIndex > 1; var notYetMaxPanel = options.infinite && currentIndex < lastIndex && nextPanelIndex < currentIndex; if (hasEmptyPanelBetween || notYetMaxPanel) { return null; } var newPosition = currentPosition + this.getSize() + options.gap; var nextPanel = nextSibling; if (nextPanelPosition !== newPosition) { nextPanel = nextSibling.clone(nextSibling.getCloneIndex(), true); nextPanel.setPosition(newPosition); } return nextPanel; }; __proto.insertBefore = function (element) { var viewport = this.viewport; var parsedElements = parseElement(element); var firstPanel = viewport.panelManager.firstPanel(); var prevSibling = this.prevSibling; // Finding correct inserting index // While it should insert removing empty spaces, // It also should have to be bigger than prevSibling' s index var targetIndex = prevSibling && firstPanel.getIndex() !== this.getIndex() ? Math.max(prevSibling.getIndex() + 1, this.getIndex() - parsedElements.length) : Math.max(this.getIndex() - parsedElements.length, 0); return viewport.insert(targetIndex, parsedElements); }; __proto.insertAfter = function (element) { return this.viewport.insert(this.getIndex() + 1, element); }; __proto.remove = function () { this.viewport.remove(this.getIndex()); return this; }; __proto.destroy = function (option) { if (!option.preserveUI) { var originalStyle = this.state.originalStyle; restoreStyle(this.element, originalStyle); } // release resources for (var x in this) { this[x] = null; } }; __proto.getElement = function () { return this.element; }; __proto.getAnchorPosition = function () { return this.state.position + this.state.relativeAnchorPosition; }; __proto.getRelativeAnchorPosition = function () { return this.state.relativeAnchorPosition; }; __proto.getIndex = function () { return this.state.index; }; __proto.getPosition = function () { return this.state.position; }; __proto.getSize = function () { return this.state.size; }; __proto.getBbox = function () { var state = this.state; var viewport = this.viewport; var element = this.element; var options = viewport.options; if (!element) { state.cachedBbox = { x: 0, y: 0, width: 0, height: 0 }; } else if (!state.cachedBbox) { var wasVisible = Boolean(element.parentNode); var cameraElement = viewport.getCameraElement(); if (!wasVisible) { cameraElement.appendChild(element); viewport.addVisiblePanel(this); } state.cachedBbox = getBbox(element, options.useOffset); if (!wasVisible && viewport.options.renderExternal) { cameraElement.removeChild(element); } } return state.cachedBbox; }; __proto.isClone = function () { return this.state.isClone; }; __proto.getOverlappedClass = function (classes) { var element = this.element; for (var _i = 0, classes_1 = classes; _i < classes_1.length; _i++) { var className = classes_1[_i]; if (hasClass(element, className)) { return className; } } }; __proto.getCloneIndex = function () { return this.state.cloneIndex; }; __proto.getClonedPanels = function () { var state = this.state; return state.isClone ? this.original.getClonedPanels() : this.clonedPanels; }; __proto.getIdenticalPanels = function () { var state = this.state; return state.isClone ? this.original.getIdenticalPanels() : __spreadArrays([this], this.clonedPanels); }; __proto.getOriginalPanel = function () { return this.state.isClone ? this.original : this; }; __proto.setIndex = function (index) { var state = this.state; state.index = index; this.clonedPanels.forEach(function (panel) { return panel.state.index = index; }); }; __proto.setPosition = function (pos) { this.state.position = pos; return this; }; __proto.setPositionCSS = function (offset) { if (offset === void 0) { offset = 0; } if (!this.element) { return; } var state = this.state; var pos = state.position; var options = this.viewport.options; var elementStyle = this.element.style; var currentElementStyle = options.horizontal ? elementStyle.left : elementStyle.top; var styleToApply = pos - offset + "px"; if (!state.isVirtual && currentElementStyle !== styleToApply) { options.horizontal ? elementStyle.left = styleToApply : elementStyle.top = styleToApply; } }; __proto.clone = function (cloneIndex, isVirtual, element) { if (isVirtual === void 0) { isVirtual = false; } var state = this.state; var viewport = this.viewport; var cloneElement = element; if (!cloneElement && this.element) { cloneElement = isVirtual ? this.element : this.element.cloneNode(true); } var clonedPanel = new Panel(cloneElement, state.index, viewport); var clonedState = clonedPanel.state; clonedPanel.original = state.isClone ? this.original : this; clonedState.isClone = true; clonedState.isVirtual = isVirtual; clonedState.cloneIndex = cloneIndex; // Inherit some state values clonedState.size = state.size; clonedState.relativeAnchorPosition = state.relativeAnchorPosition; clonedState.originalStyle = state.originalStyle; clonedState.cachedBbox = state.cachedBbox; if (!isVirtual) { this.clonedPanels.push(clonedPanel); } else { clonedPanel.prevSibling = this.prevSibling; clonedPanel.nextSibling = this.nextSibling; } return clonedPanel; }; __proto.removeElement = function () { if (!this.viewport.options.renderExternal) { var element = this.element; element.parentNode && element.parentNode.removeChild(element); } // Do the same thing for clones if (!this.state.isClone) { this.removeClonedPanelsAfter(0); } }; __proto.removeClonedPanelsAfter = function (start) { var options = this.viewport.options; var removingPanels = this.clonedPanels.splice(start); if (!options.renderExternal) { removingPanels.forEach(function (panel) { panel.removeElement(); }); } }; __proto.setElement = function (element) { if (!element) { return; } var currentElement = this.element; if (element !== currentElement) { var options = this.viewport.options; if (currentElement) { if (options.horizontal) { element.style.left = currentElement.style.left; } else { element.style.top = currentElement.style.top; } } else { var originalStyle = this.state.originalStyle; originalStyle.className = element.getAttribute("class"); originalStyle.style = element.getAttribute("style"); } this.element = element; if (options.classPrefix) { addClass(element, options.classPrefix + "-panel"); } // Update size info after applying panel css applyCSS(this.element, DEFAULT_PANEL_CSS); } }; return Panel; }(); /** * Copyright (c) 2015 NAVER Corp. * egjs projects are licensed under the MIT license */ var PanelManager = /*#__PURE__*/ function () { function PanelManager(cameraElement, options) { this.cameraElement = cameraElement; this.panels = []; this.clones = []; this.range = { min: -1, max: -1 }; this.length = 0; this.cloneCount = 0; this.options = options; this.lastIndex = options.lastIndex; } var __proto = PanelManager.prototype; __proto.firstPanel = function () { return this.panels[this.range.min]; }; __proto.lastPanel = function () { return this.panels[this.range.max]; }; __proto.allPanels = function () { return __spreadArrays(this.panels, this.clones.reduce(function (allClones, clones) { return __spreadArrays(allClones, clones); }, [])); }; __proto.originalPanels = function () { return this.panels; }; __proto.clonedPanels = function () { return this.clones; }; __proto.replacePanels = function (newPanels, newClones) { this.panels = newPanels; this.clones = newClones; this.range = { min: findIndex(newPanels, function (panel) { return Boolean(panel); }), max: newPanels.length - 1 }; this.length = newPanels.filter(function (panel) { return Boolean(panel); }).length; }; __proto.has = function (index) { return !!this.panels[index]; }; __proto.get = function (index) { return this.panels[index]; }; __proto.getPanelCount = function () { return this.length; }; __proto.getLastIndex = function () { return this.lastIndex; }; __proto.getRange = function () { return this.range; }; __proto.getCloneCount = function () { return this.cloneCount; }; __proto.setLastIndex = function (lastIndex) { this.lastIndex = lastIndex; var firstPanel = this.firstPanel(); var lastPanel = this.lastPanel(); if (!firstPanel || !lastPanel) { return; // no meaning of updating range & length } // Remove panels above new last index var range = this.range; if (lastPanel.getIndex() > lastIndex) { var removingPanels = this.panels.splice(lastIndex + 1); this.length -= removingPanels.length; var firstRemovedPanel = removingPanels.filter(function (panel) { return !!panel; })[0]; var possibleLastPanel = firstRemovedPanel.prevSibling; if (possibleLastPanel) { range.max = possibleLastPanel.getIndex(); } else { range.min = -1; range.max = -1; } if (this.shouldRender()) { removingPanels.forEach(function (panel) { return panel.removeElement(); }); } } }; __proto.setCloneCount = function (cloneCount) { this.cloneCount = cloneCount; }; // Insert at index // Returns pushed elements from index, inserting at 'empty' position doesn't push elements behind it __proto.insert = function (index, newPanels) { var panels = this.panels; var range = this.range; var isCircular = this.options.circular; var lastIndex = this.lastIndex; // Find first panel that index is greater than inserting index var nextSibling = this.findFirstPanelFrom(index); // if it's null, element will be inserted at last position // https://developer.mozilla.org/ko/docs/Web/API/Node/insertBefore#Syntax var firstPanel = this.firstPanel(); var siblingElement = nextSibling ? nextSibling.getElement() : isCircular && firstPanel ? firstPanel.getClonedPanels()[0].getElement() : null; // Insert panels before sibling element this.insertNewPanels(newPanels, siblingElement); var pushedIndex = newPanels.length; // Like when setting index 50 while visible panels are 0, 1, 2 if (index > range.max) { newPanels.forEach(function (panel, offset) { panels[index + offset] = panel; }); } else { var panelsAfterIndex = panels.slice(index, index + newPanels.length); // Find empty from beginning var emptyPanelCount = findIndex(panelsAfterIndex, function (panel) { return !!panel; }); if (emptyPanelCount < 0) { // All empty emptyPanelCount = panelsAfterIndex.length; } pushedIndex = newPanels.length - emptyPanelCount; // Insert removing empty panels panels.splice.apply(panels, __spreadArrays([index, emptyPanelCount], newPanels)); // Remove panels after last index if (panels.length > lastIndex + 1) { var removedPanels = panels.splice(lastIndex + 1).filter(function (panel) { return Boolean(panel); }); this.length -= removedPanels.length; // Find first var newLastIndex = lastIndex - findIndex(this.panels.concat().reverse(), function (panel) { return !!panel; }); // Can be filled with empty after newLastIndex this.panels.splice(newLastIndex + 1); this.range.max = newLastIndex; if (this.shouldRender()) { removedPanels.forEach(function (panel) { return panel.removeElement(); }); } } } // Update index of previous panels if (pushedIndex > 0) { panels.slice(index + newPanels.length).forEach(function (panel) { panel.setIndex(panel.getIndex() + pushedIndex); }); } // Update state this.length += newPanels.length; this.updateIndex(index); if (isCircular) { this.addNewClones(index, newPanels, newPanels.length - pushedIndex, nextSibling); var clones = this.clones; var panelCount_1 = this.panels.length; if (clones[0] && clones[0].length > lastIndex + 1) { clones.forEach(function (cloneSet) { cloneSet.splice(panelCount_1); }); } } return pushedIndex; }; __proto.replace = function (index, newPanels) { var panels = this.panels; var range = this.range; var options = this.options; var isCircular = options.circular; // Find first panel that index is greater than inserting index var nextSibling = this.findFirstPanelFrom(index + newPanels.length); // if it's null, element will be inserted at last position // https://developer.mozilla.org/ko/docs/Web/API/Node/insertBefore#Syntax var firstPanel = this.firstPanel(); var siblingElement = nextSibling ? nextSibling.getElement() : isCircular && firstPanel ? firstPanel.getClonedPanels()[0].getElement() : null; // Insert panels before sibling element this.insertNewPanels(newPanels, siblingElement); if (index > range.max) { // Temporarily insert null at index to use splice() panels[index] = null; } var replacedPanels = panels.splice.apply(panels, __spreadArrays([index, newPanels.length], newPanels)); var wasNonEmptyCount = replacedPanels.filter(function (panel) { return Boolean(panel); }).length; // Suppose inserting [1, 2, 3] at 0 position when there were [empty, 1] // So length should be increased by 3(inserting panels) - 1(non-empty panels) this.length += newPanels.length - wasNonEmptyCount; this.updateIndex(index); if (isCircular) { this.addNewClones(index, newPanels, newPanels.length, nextSibling); } if (this.shouldRender()) { replacedPanels.forEach(function (panel) { return panel && panel.removeElement(); }); } return replacedPanels; }; __proto.remove = function (index, deleteCount) { if (deleteCount === void 0) { deleteCount = 1; } var isCircular = this.options.circular; var panels = this.panels; var clones = this.clones; // Delete count should be equal or larger than 0 deleteCount = Math.max(deleteCount, 0); var deletedPanels = panels.splice(index, deleteCount).filter(function (panel) { return !!panel; }); if (this.shouldRender()) { deletedPanels.forEach(function (panel) { return panel.removeElement(); }); } if (isCircular) { clones.forEach(function (cloneSet) { cloneSet.splice(index, deleteCount); }); } // Update indexes panels.slice(index).forEach(function (panel) { panel.setIndex(panel.getIndex() - deleteCount); }); // Check last panel is empty var lastIndex = panels.length - 1; if (!panels[lastIndex]) { var reversedPanels = panels.concat().reverse(); var nonEmptyIndexFromLast = findIndex(reversedPanels, function (panel) { return !!panel; }); lastIndex = nonEmptyIndexFromLast < 0 ? -1 // All empty : lastIndex - nonEmptyIndexFromLast; // Remove all empty panels from last panels.splice(lastIndex + 1); if (isCircular) { clones.forEach(function (cloneSet) { cloneSet.splice(lastIndex + 1); }); } } // Update range & length this.range = { min: findIndex(panels, function (panel) { return !!panel; }), max: lastIndex }; this.length -= deletedPanels.length; if (this.length <= 0) { // Reset clones this.clones = []; this.cloneCount = 0; } return deletedPanels; }; __proto.chainAllPanels = function () { var allPanels = this.allPanels().filter(function (panel) { return !!panel; }); var allPanelsCount = allPanels.length; if (allPanelsCount <= 1) { return; } allPanels.slice(1, allPanels.length - 1).forEach(function (panel, idx) { var prevPanel = allPanels[idx]; var nextPanel = allPanels[idx + 2]; panel.prevSibling = prevPanel; panel.nextSibling = nextPanel; }); var firstPanel = allPanels[0]; var lastPanel = allPanels[allPanelsCount - 1]; firstPanel.prevSibling = null; firstPanel.nextSibling = allPanels[1]; lastPanel.prevSibling = allPanels[allPanelsCount - 2]; lastPanel.nextSibling = null; if (this.options.circular) { firstPanel.prevSibling = lastPanel; lastPanel.nextSibling = firstPanel; } }; __proto.insertClones = function (cloneIndex, index, clonedPanels, deleteCount) { if (deleteCount === void 0) { deleteCount = 0; } var clones = this.clones; var lastIndex = this.lastIndex; if (!clones[cloneIndex]) { var newClones_1 = []; clonedPanels.forEach(function (panel, offset) { newClones_1[index + offset] = panel; }); clones[cloneIndex] = newClones_1; } else { var insertTarget_1 = clones[cloneIndex]; if (index >= insertTarget_1.length) { clonedPanels.forEach(function (panel, offset) { insertTarget_1[index + offset] = panel; }); } else { insertTarget_1.splice.apply(insertTarget_1, __spreadArrays([index, deleteCount], clonedPanels)); // Remove panels after last index if (clonedPanels.length > lastIndex + 1) { clonedPanels.splice(lastIndex + 1); } } } }; // clones are operating in set __proto.removeClonesAfter = function (cloneIndex) { var panels = this.panels; panels.forEach(function (panel) { panel.removeClonedPanelsAfter(cloneIndex); }); this.clones.splice(cloneIndex); }; __proto.findPanelOf = function (element) { var allPanels = this.allPanels(); for (var _i = 0, allPanels_1 = allPanels; _i < allPanels_1.length; _i++) { var panel = allPanels_1[_i]; if (!panel) { continue; } var panelElement = panel.getElement(); if (panelElement.contains(element)) { return panel; } } }; __proto.findFirstPanelFrom = function (index) { for (var _i = 0, _a = this.panels.slice(index); _i < _a.length; _i++) { var panel = _a[_i]; if (panel && panel.getIndex() >= index && panel.getElement().parentNode) { return panel; } } }; __proto.addNewClones = function (index, originalPanels, deleteCount, nextSibling) { var _this = this; var cameraElement = this.cameraElement; var cloneCount = this.getCloneCount(); var lastPanel = this.lastPanel(); var lastPanelClones = lastPanel ? lastPanel.getClonedPanels() : []; var nextSiblingClones = nextSibling ? nextSibling.getClonedPanels() : []; var _loop_1 = function (cloneIndex) { var cloneNextSibling = nextSiblingClones[cloneIndex]; var lastPanelSibling = lastPanelClones[cloneIndex]; var cloneSiblingElement = cloneNextSibling ? cloneNextSibling.getElement() : lastPanelSibling ? lastPanelSibling.getElement().nextElementSibling : null; var newClones = originalPanels.map(function (panel) { var clone = panel.clone(cloneIndex); if (_this.shouldRender()) { cameraElement.insertBefore(clone.getElement(), cloneSiblingElement); } return clone; }); this_1.insertClones(cloneIndex, index, newClones, deleteCount); }; var this_1 = this; for (var _i = 0, _a = counter(cloneCount); _i < _a.length; _i++) { var cloneIndex = _a[_i]; _loop_1(cloneIndex); } }; __proto.updateIndex = function (insertingIndex) { var panels = this.panels; var range = this.range; var newLastIndex = panels.length - 1; if (newLastIndex > range.max) { range.max = newLastIndex; } if (insertingIndex < range.min || range.min < 0) { range.min = insertingIndex; } }; __proto.insertNewPanels = function (newPanels, siblingElement) { if (this.shouldRender()) { var fragment_1 = document.createDocumentFragment(); newPanels.forEach(function (panel) { return fragment_1.appendChild(panel.getElement()); }); this.cameraElement.insertBefore(fragment_1, siblingElement); } }; __proto.shouldRender = function () { var options = this.options; return !options.renderExternal && !options.renderOnlyVisible; }; return PanelManager; }(); /** * Copyright (c) 2015 NAVER Corp. * egjs projects are licensed under the MIT license */ var State = /*#__PURE__*/ function () { function State() { this.delta = 0; this.direction = null; this.targetPanel = null; this.lastPosition = 0; } var __proto = State.prototype; __proto.onEnter = function (prevState) { this.delta = prevState.delta; this.direction = prevState.direction; this.targetPanel = prevState.targetPanel; this.lastPosition = prevState.lastPosition; }; __proto.onExit = function (nextState) {// DO NOTHING }; __proto.onHold = function (e, context) {// DO NOTHING }; __proto.onChange = function (e, context) {// DO NOTHING }; __proto.onRelease = function (e, context) {// DO NOTHING }; __proto.onAnimationEnd = function (e, context) {// DO NOTHING }; __proto.onFinish = function (e, context) {// DO NOTHING }; return State; }(); /** * Copyright (c) 2015 NAVER Corp. * egjs projects are licensed under the MIT license */ var IdleState = /*#__PURE__*/ function (_super) { __extends(IdleState, _super); function IdleState() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.type = STATE_TYPE.IDLE; _this.holding = false; _this.playing = false; return _this; } var __proto = IdleState.prototype; __proto.onEnter = function () { this.direction = null; this.targetPanel = null; this.delta = 0; this.lastPosition = 0; }; __proto.onHold = function (e, _a) { var flicking = _a.flicking, viewport = _a.viewport, triggerEvent = _a.triggerEvent, transitTo = _a.transitTo; // Shouldn't do any action until any panels on flicking area if (flicking.getPanelCount() <= 0) { if (viewport.options.infinite) { viewport.moveCamera(viewport.getCameraPosition(), e); } transitTo(STATE_TYPE.DISABLED); return; } this.lastPosition = viewport.getCameraPosition(); triggerEvent(EVENTS.HOLD_START, e, true).onSuccess(function () { transitTo(STATE_TYPE.HOLDING); }).onStopped(function () { transitTo(STATE_TYPE.DISABLED); }); }; // By methods call __proto.onChange = function (e, context) { var triggerEvent = context.triggerEvent, transitTo = context.transitTo; triggerEvent(EVENTS.MOVE_START, e, false).onSuccess(function () { // Trigger AnimatingState's onChange, to trigger "move" event immediately transitTo(STATE_TYPE.ANIMATING).onChange(e, context); }).onStopped(function () { transitTo(STATE_TYPE.DISABLED); }); }; return IdleState; }(State); /** * Copyright (c) 2015 NAVER Corp. * egjs projects are licensed under the MIT license */ var HoldingState = /*#__PURE__*/ function (_super) { __extends(HoldingState, _super); function HoldingState() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.type = STATE_TYPE.HOLDING; _this.holding = true; _this.playing = true; _this.releaseEvent = null; return _this; } var __proto = HoldingState.prototype; __proto.onChange = function (e, context) { var flicking = context.flicking, triggerEvent = context.triggerEvent, transitTo = context.transitTo; var offset = flicking.options.horizontal ? e.inputEvent.offsetX : e.inputEvent.offsetY; this.direction = offset < 0 ? DIRECTION.NEXT : DIRECTION.PREV; triggerEvent(EVENTS.MOVE_START, e, true).onSuccess(function () { // Trigger DraggingState's onChange, to trigger "move" event immediately transitTo(STATE_TYPE.DRAGGING).onChange(e, context); }).onStopped(function () { transitTo(STATE_TYPE.DISABLED); }); }; __proto.onRelease = function (e, context) { var viewport = context.viewport, triggerEvent = context.triggerEvent, transitTo = context.transitTo; triggerEvent(EVENTS.HOLD_END, e, true); if (e.delta.flick !== 0) { // Sometimes "release" event on axes triggered before "change" event // Especially if user flicked panel fast in really short amount of time // if delta is not zero, that means above case happened. // Event flow should be HOLD_START -> MOVE_START -> MOVE -> HOLD_END // At least one move event should be included between holdStart and holdEnd e.setTo({ flick: viewport.getCameraPosition() }, 0); transitTo(STATE_TYPE.IDLE); return; } // Can't handle select event here, // As "finish" axes event happens this.releaseEvent = e; }; __proto.onFinish = function (e, _a) { var viewport = _a.viewport, triggerEvent = _a.triggerEvent, transitTo = _a.transitTo; // Should transite to IDLE state before select event // As user expects hold is already finished transitTo(STATE_TYPE.IDLE); if (!this.releaseEvent) { return; } // Handle release event here // To prevent finish event called twice var releaseEvent = this.releaseEvent; // Static click var srcEvent = releaseEvent.inputEvent.srcEvent; var clickedElement; if (srcEvent.type === "touchend") { var touchEvent = srcEvent; var touch = touchEvent.changedTouches[0]; clickedElement = document.elementFromPoint(touch.clientX, touch.clientY); } else { clickedElement = srcEvent.target; } var clickedPanel = viewport.panelManager.findPanelOf(clickedElement); var cameraPosition = viewport.getCameraPosition(); if (clickedPanel) { var clickedPanelPosition = clickedPanel.getPosition(); var direction = clickedPanelPosition > cameraPosition ? DIRECTION.NEXT : clickedPanelPosition < cameraPosition ? DIRECTION.PREV : null; // Don't provide axes event, to use axes instance instead triggerEvent(EVENTS.SELECT, null, true, { direction: direction, index: clickedPanel.getIndex(), panel: clickedPanel }); } }; return HoldingState; }(State); /** * Copyright (c) 2015 NAVER Corp. * egjs projects are licensed under the MIT license */ var DraggingState = /*#__PURE__*/ function (_super) { __extends(DraggingState, _super); function DraggingState() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.type = STATE_TYPE.DRAGGING; _this.holding = true; _this.playing = true; return _this; } var __proto = DraggingState.prototype; __proto.onChange = function (e, _a) { var moveCamera = _a.moveCamera, transitTo = _a.transitTo; if (!e.delta.flick) { return; } moveCamera(e).onStopped(function () { transitTo(STATE_TYPE.DISABLED); }); }; __proto.onRelease = function (e, context) { var flicking = context.flicking, viewport = context.viewport, triggerEvent = context.triggerEvent, transitTo = context.transitTo, stopCamera = context.stopCamera; var delta = this.delta; var absDelta = Math.abs(delta); var options = flicking.options; var horizontal = options.horizontal; var moveType = viewport.moveType; var inputEvent = e.inputEvent; var velocity = horizontal ? inputEvent.velocityX : inputEvent.velocityY; var inputDelta = horizontal ? inputEvent.deltaX : inputEvent.deltaY; var isNextDirection = Math.abs(velocity) > 1 ? velocity < 0 : absDelta > 0 ? delta > 0 : inputDelta < 0; var swipeDistance = viewport.options.bound ? Math.max(absDelta, Math.abs(inputDelta)) : absDelta; var swipeAngle = inputEvent.deltaX ? Math.abs(180 * Math.atan(inputEvent.deltaY / inputEvent.deltaX) / Math.PI) : 90; var belowAngleThreshold = horizontal ? swipeAngle <= options.thresholdAngle : swipeAngle > options.thresholdAngle; var overThreshold = swipeDistance >= options.threshold && belowAngleThreshold; var moveTypeContext = { viewport: viewport, axesEvent: e, state: this, swipeDistance: swipeDistance, isNextDirection: isNextDirection }; // Update last position to cope with Axes's animating behavior // Axes uses start position when animation start triggerEvent(EVENTS.HOLD_END, e, true); var targetPanel = this.targetPanel; if (!overThreshold && targetPanel) { // Interrupted while animating var interruptDestInfo = moveType.findPanelWhenInterrupted(moveTypeContext); viewport.moveTo(interruptDestInfo.panel, interruptDestInfo.destPos, interruptDestInfo.eventType, e, interruptDestInfo.duration); transitTo(STATE_TYPE.ANIMATING); return; } var currentPanel = viewport.getCurrentPanel(); var nearestPanel = viewport.getNearestPanel(); if (!currentPanel || !nearestPanel) { // There're no panels e.stop(); transitTo(STATE_TYPE.IDLE); return; } var destInfo = overThreshold ? moveType.findTargetPanel(moveTypeContext) : moveType.findRestorePanel(moveTypeContext); viewport.moveTo(destInfo.panel, destInfo.destPos, destInfo.eventType, e, destInfo.duration).onSuccess(function () { transitTo(STATE_TYPE.ANIMATING); }).onStopped(function () { transitTo(STATE_TYPE.DISABLED); stopCamera(e); }); }; return DraggingState; }(State); /** * Copyright (c) 2015 NAVER Corp. * egjs projects are licensed under the MIT license */ var AnimatingState = /*#__PURE__*/ function (_super) { __extends(AnimatingState, _super); function AnimatingState() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.type = STATE_TYPE.ANIMATING; _this.holding = false; _this.playing = true; return _this; } var __proto = AnimatingState.prototype; __proto.onHold = function (e, _a) { var viewport = _a.viewport, triggerEvent = _a.triggerEvent, transitTo = _a.transitTo; var options = viewport.options; var scrollArea = viewport.getScrollArea(); var scrollAreaSize = viewport.getScrollAreaSize(); var loopCount = Math.floor((this.lastPosition + this.delta - scrollArea.prev) / scrollAreaSize); var targetPanel = this.targetPanel; if (options.circular && loopCount !== 0 && targetPanel) { var cloneCount = viewport.panelManager.getCloneCount(); var originalTargetPosition = targetPanel.getPosition(); // cloneIndex is from -1 to cloneCount - 1 var newCloneIndex = circulate(targetPanel.getCloneIndex() - loopCount, -1, cloneCount - 1, true); var newTargetPosition = originalTargetPosition - loopCount * scrollAreaSize; var newTargetPanel = targetPanel.getIdenticalPanels()[newCloneIndex + 1].clone(newCloneIndex, true); // Set new target panel considering looped count newTargetPanel.setPosition(newTargetPosition); this.targetPanel = newTargetPanel; } // Reset last position and delta this.delta = 0; this.lastPosition = viewport.getCameraPosition(); // Update current panel as current nearest panel viewport.setCurrentPanel(viewport.getNearestPanel()); triggerEvent(EVENTS.HOLD_START, e, true).onSuccess(function () { transitTo(STATE_TYPE.DRAGGING); }).onStopped(function () { transitTo(STATE_TYPE.DISABLED); }); }; __proto.onChange = function (e, _a) { var moveCamera = _a.moveCamera, transitTo = _a.transitTo; if (!e.delta.flick) { return; } moveCamera(e).onStopped(function () { transitTo(STATE_TYPE.DISABLED); }); }; __proto.onFinish = function (e, _a) { var flicking = _