@egjs/flicking
Version:
Everyday 30 million people experience. It's reliable, flexible and extendable carousel.
1,520 lines (1,518 loc) • 346 kB
JavaScript
(function(global, factory) {
typeof exports === "object" && typeof module !== "undefined" ? module.exports = factory() : typeof define === "function" && define.amd ? define(factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, global.Flicking = factory());
})(this, (function() {
"use strict";var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __reflectGet = Reflect.get;
var __pow = Math.pow;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __superGet = (cls, obj, key) => __reflectGet(__getProtoOf(cls), key, obj);
var __async = (__this, __arguments, generator) => {
return new Promise((resolve, reject) => {
var fulfilled = (value) => {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
};
var rejected = (value) => {
try {
step(generator.throw(value));
} catch (e) {
reject(e);
}
};
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
step((generator = generator.apply(__this, __arguments)).next());
});
};
function __values(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function() {
if (o && i >= o.length) o = void 0;
return {
value: o && o[i++],
done: !o
};
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
}
function __read(o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
} catch (error) {
e = {
error
};
} finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
} finally {
if (e) throw e.error;
}
}
return ar;
}
function __spread() {
for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i]));
return ar;
}
var isUndefined = function(value) {
return typeof value === "undefined";
};
var ComponentEvent = /* @__PURE__ */ (function() {
function ComponentEvent2(eventType, props) {
var e_1, _a;
this._canceled = false;
if (props) {
try {
for (var _b = __values(Object.keys(props)), _c = _b.next(); !_c.done; _c = _b.next()) {
var key = _c.value;
this[key] = props[key];
}
} catch (e_1_1) {
e_1 = {
error: e_1_1
};
} finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
} finally {
if (e_1) throw e_1.error;
}
}
}
this.eventType = eventType;
}
var __proto = ComponentEvent2.prototype;
__proto.stop = function() {
this._canceled = true;
};
__proto.isCanceled = function() {
return this._canceled;
};
return ComponentEvent2;
})();
var Component = /* @__PURE__ */ (function() {
function Component2() {
this._eventHandler = {};
}
var __proto = Component2.prototype;
__proto.trigger = function(event) {
var params = [];
for (var _i = 1; _i < arguments.length; _i++) {
params[_i - 1] = arguments[_i];
}
var eventName = event instanceof ComponentEvent ? event.eventType : event;
var handlers = __spread(this._eventHandler[eventName] || []);
if (handlers.length <= 0) {
return this;
}
if (event instanceof ComponentEvent) {
event.currentTarget = this;
handlers.forEach(function(handler) {
handler(event);
});
} else {
handlers.forEach(function(handler) {
handler.apply(void 0, __spread(params));
});
}
return this;
};
__proto.once = function(eventName, handlerToAttach) {
var _this = this;
if (typeof eventName === "object" && isUndefined(handlerToAttach)) {
var eventHash = eventName;
for (var key in eventHash) {
this.once(key, eventHash[key]);
}
return this;
} else if (typeof eventName === "string" && typeof handlerToAttach === "function") {
var listener_1 = function() {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
handlerToAttach.apply(void 0, __spread(args));
_this.off(eventName, listener_1);
};
this.on(eventName, listener_1);
}
return this;
};
__proto.hasOn = function(eventName) {
return !!this._eventHandler[eventName];
};
__proto.on = function(eventName, handlerToAttach) {
if (typeof eventName === "object" && isUndefined(handlerToAttach)) {
var eventHash = eventName;
for (var name in eventHash) {
this.on(name, eventHash[name]);
}
return this;
} else if (typeof eventName === "string" && typeof handlerToAttach === "function") {
var handlerList = this._eventHandler[eventName];
if (isUndefined(handlerList)) {
this._eventHandler[eventName] = [];
handlerList = this._eventHandler[eventName];
}
handlerList.push(handlerToAttach);
}
return this;
};
__proto.off = function(eventName, handlerToDetach) {
if (isUndefined(eventName)) {
this._eventHandler = {};
return this;
}
if (isUndefined(handlerToDetach)) {
if (typeof eventName === "string") {
delete this._eventHandler[eventName];
return this;
} else {
var eventHash = eventName;
for (var name in eventHash) {
this.off(name, eventHash[name]);
}
return this;
}
}
var handlerList = this._eventHandler[eventName];
if (handlerList) {
var length = handlerList.length;
for (var i = 0; i < length; ++i) {
if (handlerList[i] === handlerToDetach) {
handlerList.splice(i, 1);
if (length <= 1) {
delete this._eventHandler[eventName];
}
break;
}
}
}
return this;
};
Component2.VERSION = "3.0.5";
return Component2;
})();
var ComponentEvent$1 = ComponentEvent;
const ALIGN = {
/** left/top align */
PREV: "prev",
/** center align */
CENTER: "center",
/** right/bottom align */
NEXT: "next"
};
const DIRECTION = {
/** "left" when {@link Flicking.horizontal | horizontal} is true, and "top" when {@link Flicking.horizontal | horizontal} is false */
PREV: "PREV",
/** "right" when {@link Flicking.horizontal | horizontal} is true, and "bottom" when {@link Flicking.horizontal | horizontal} is false */
NEXT: "NEXT",
/** This value usually means it's the same position */
NONE: null
};
const MOVE_TYPE = {
/** Flicking's {@link Flicking.moveType | moveType} that enables {@link SnapControl} as a Flicking's {@link Flicking.control | control} */
SNAP: "snap",
/** Flicking's {@link Flicking.moveType | moveType} that enables {@link FreeControl} as a Flicking's {@link Flicking.control | control} */
FREE_SCROLL: "freeScroll",
/** Flicking's {@link Flicking.moveType | moveType} that enables {@link StrictControl} as a Flicking's {@link Flicking.control | control} */
STRICT: "strict"
};
const CLASS = {
VIEWPORT: "flicking-viewport",
CAMERA: "flicking-camera",
VERTICAL: "vertical",
HIDDEN: "flicking-hidden",
DEFAULT_VIRTUAL: "flicking-panel"
};
const CIRCULAR_FALLBACK = {
/** "linear" */
LINEAR: "linear",
/** "bound" */
BOUND: "bound"
};
const ORDER = {
/** "ltr" */
LTR: "ltr",
/** "rtl" */
RTL: "rtl"
};
const MOVE_DIRECTION = {
/** horizontal */
HORIZONTAL: "horizontal",
/** vertical */
VERTICAL: "vertical"
};
const Values = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
__proto__: null,
ALIGN,
CIRCULAR_FALLBACK,
CLASS,
DIRECTION,
MOVE_DIRECTION,
MOVE_TYPE,
ORDER
}, Symbol.toStringTag, { value: "Module" }));
const EVENTS = {
/** ready event */
READY: "ready",
/** beforeResize event */
BEFORE_RESIZE: "beforeResize",
/** afterResize event */
AFTER_RESIZE: "afterResize",
/** holdStart event */
HOLD_START: "holdStart",
/** holdEnd event */
HOLD_END: "holdEnd",
/** moveStart event */
MOVE_START: "moveStart",
/** move event */
MOVE: "move",
/** moveEnd event */
MOVE_END: "moveEnd",
/** willChange event */
WILL_CHANGE: "willChange",
/** changed event */
CHANGED: "changed",
/** willRestore event */
WILL_RESTORE: "willRestore",
/** restored event */
RESTORED: "restored",
/** select event */
SELECT: "select",
/** needPanel event */
NEED_PANEL: "needPanel",
/** visibleChange event */
VISIBLE_CHANGE: "visibleChange",
/** reachEdge event */
REACH_EDGE: "reachEdge",
/**
* panelChange event
* @since 4.1.0
*/
PANEL_CHANGE: "panelChange"
};
const Events = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
__proto__: null,
EVENTS
}, Symbol.toStringTag, { value: "Module" }));
class AnchorPoint {
/**
* Index of AnchorPoint
* @readonly
*/
get index() {
return this._index;
}
/**
* Position of AnchorPoint
* @readonly
*/
get position() {
return this._pos;
}
/**
* A {@link Panel} instance AnchorPoint is referencing to
* @readonly
*/
get panel() {
return this._panel;
}
/**
* @param options - {@link AnchorPointOptions}
*/
constructor(options) {
const { index, position, panel } = options;
this._index = index;
this._pos = position;
this._panel = panel;
}
}
const errors = {
WRONG_TYPE: {
code: 0,
message: (wrongVal, correctTypes) => `${wrongVal}(${typeof wrongVal}) is not a ${correctTypes.map((type) => `"${type}"`).join(" or ")}.`
},
ELEMENT_NOT_FOUND: {
code: 1,
message: (selector) => `Element with selector "${selector}" not found.`
},
VAL_MUST_NOT_NULL: {
code: 2,
message: (val, name) => `${name} should be provided. Given: ${val}`
},
NOT_ATTACHED_TO_FLICKING: {
code: 3,
message: 'This module is not attached to the Flicking instance. "init()" should be called first.'
},
WRONG_OPTION: {
code: 4,
message: (optionName, val) => `Option "${optionName}" is not in correct format, given: ${val}`
},
INDEX_OUT_OF_RANGE: {
code: 5,
message: (val, min, max) => `Index "${val}" is out of range: should be between ${min} and ${max}.`
},
POSITION_NOT_REACHABLE: {
code: 6,
message: (position) => `Position "${position}" is not reachable.`
},
TRANSFORM_NOT_SUPPORTED: {
code: 7,
message: "Browser does not support CSS transform."
},
STOP_CALLED_BY_USER: {
code: 8,
message: "Event stop() is called by user."
},
ANIMATION_INTERRUPTED: {
code: 9,
message: "Animation is interrupted by user input."
},
ANIMATION_ALREADY_PLAYING: {
code: 10,
message: "Animation is already playing."
},
NOT_ALLOWED_IN_FRAMEWORK: {
code: 11,
message: "This behavior is not allowed in the frameworks like React, Vue, or Angular."
},
NOT_INITIALIZED: {
code: 12,
message: "Flicking is not initialized yet, call init() first."
},
NO_ACTIVE: {
code: 13,
message: "There's no active panel that Flicking has selected. This may be due to the absence of any panels."
},
NOT_ALLOWED_IN_VIRTUAL: {
code: 14,
message: "This behavior is not allowed when the virtual option is enabled"
}
};
const CODE = Object.keys(errors).reduce(
(acc, key) => {
acc[key] = errors[key].code;
return acc;
},
{}
);
const MESSAGE = Object.keys(errors).reduce(
(acc, key) => {
acc[key] = errors[key].message;
return acc;
},
{}
);
const merge = (target, ...sources) => {
sources.forEach((source) => {
Object.keys(source).forEach((key) => {
target[key] = source[key];
});
});
return target;
};
const getElement = (el, parent) => {
let targetEl = null;
if (isString(el)) {
const parentEl = parent ? parent : document;
const queryResult = parentEl.querySelector(el);
if (!queryResult) {
throw new FlickingError(MESSAGE.ELEMENT_NOT_FOUND(el), CODE.ELEMENT_NOT_FOUND);
}
targetEl = queryResult;
} else if (el && el.nodeType === Node.ELEMENT_NODE) {
targetEl = el;
}
if (!targetEl) {
throw new FlickingError(MESSAGE.WRONG_TYPE(el, ["HTMLElement", "string"]), CODE.WRONG_TYPE);
}
return targetEl;
};
const checkExistence = (value, nameOnErrMsg) => {
if (value == null) {
throw new FlickingError(MESSAGE.VAL_MUST_NOT_NULL(value, nameOnErrMsg), CODE.VAL_MUST_NOT_NULL);
}
};
const clamp$1 = (x, min, max) => Math.max(Math.min(x, max), min);
const getFlickingAttached = (val) => {
if (!val) {
throw new FlickingError(MESSAGE.NOT_ATTACHED_TO_FLICKING, CODE.NOT_ATTACHED_TO_FLICKING);
}
return val;
};
const toArray$2 = (iterable) => [].slice.call(iterable);
const parseAlign$1 = (align, size) => {
let alignPoint;
if (isString(align)) {
switch (align) {
case ALIGN.PREV:
alignPoint = 0;
break;
case ALIGN.CENTER:
alignPoint = 0.5 * size;
break;
case ALIGN.NEXT:
alignPoint = size;
break;
default:
alignPoint = parseArithmeticSize(align, size);
if (alignPoint == null) {
throw new FlickingError(MESSAGE.WRONG_OPTION("align", align), CODE.WRONG_OPTION);
}
}
} else {
alignPoint = align;
}
return alignPoint;
};
const parseBounce = (bounce, size) => {
let parsedBounce;
if (Array.isArray(bounce)) {
parsedBounce = bounce.map((val) => parseArithmeticSize(val, size));
} else {
const parsedVal = parseArithmeticSize(bounce, size);
parsedBounce = [parsedVal, parsedVal];
}
return parsedBounce.map((val) => {
if (val == null) {
throw new FlickingError(MESSAGE.WRONG_OPTION("bounce", bounce), CODE.WRONG_OPTION);
}
return val;
});
};
const parseArithmeticSize = (cssValue, base) => {
const parsed = parseArithmeticExpression(cssValue);
if (parsed == null) return null;
return parsed.percentage * base + parsed.absolute;
};
const parseArithmeticExpression = (cssValue) => {
const cssRegex = /(?:(\+|-)\s*)?(\d+(?:\.\d+)?(%|px)?)/g;
if (typeof cssValue === "number") {
return { percentage: 0, absolute: cssValue };
}
const parsed = {
percentage: 0,
absolute: 0
};
let idx = 0;
let matchResult = cssRegex.exec(cssValue);
while (matchResult != null) {
let sign = matchResult[1];
const value = matchResult[2];
const unit = matchResult[3];
const parsedValue = parseFloat(value);
if (idx <= 0) {
sign = sign || "+";
}
if (!sign) {
return null;
}
const signMultiplier = sign === "+" ? 1 : -1;
if (unit === "%") {
parsed.percentage += signMultiplier * (parsedValue / 100);
} else {
parsed.absolute += signMultiplier * parsedValue;
}
++idx;
matchResult = cssRegex.exec(cssValue);
}
if (idx === 0) {
return null;
}
return parsed;
};
const parseCSSSizeValue = (val) => isString(val) ? val : `${val}px`;
const parsePanelAlign = (align) => typeof align === "object" ? align.panel : align;
const getDirection$1 = (start, end) => {
if (start === end) return DIRECTION.NONE;
return start < end ? DIRECTION.NEXT : DIRECTION.PREV;
};
const parseElement = (element) => {
if (!Array.isArray(element)) {
element = [element];
}
const elements2 = [];
element.forEach((el) => {
if (isString(el)) {
const tempDiv = document.createElement("div");
tempDiv.innerHTML = el;
elements2.push(...toArray$2(tempDiv.children));
while (tempDiv.firstChild) {
tempDiv.removeChild(tempDiv.firstChild);
}
} else if (el && el.nodeType === Node.ELEMENT_NODE) {
elements2.push(el);
} else {
throw new FlickingError(MESSAGE.WRONG_TYPE(el, ["HTMLElement", "string"]), CODE.WRONG_TYPE);
}
});
return elements2;
};
const getMinusCompensatedIndex = (idx, max) => idx < 0 ? clamp$1(idx + max, 0, max) : clamp$1(idx, 0, max);
const includes = (array, target) => {
for (const val of array) {
if (val === target) return true;
}
return false;
};
const isString = (val) => typeof val === "string";
const circulatePosition = (pos, min, max) => {
const size = max - min;
if (pos < min) {
const offset = (min - pos) % size;
pos = max - offset;
} else if (pos > max) {
const offset = (pos - max) % size;
pos = min + offset;
}
return pos;
};
const find$1 = (array, checker) => {
for (const val of array) {
if (checker(val)) {
return val;
}
}
return null;
};
const findRight = (array, checker) => {
for (let idx = array.length - 1; idx >= 0; idx--) {
const val = array[idx];
if (checker(val)) {
return val;
}
}
return null;
};
const findIndex = (array, checker) => {
for (let idx = 0; idx < array.length; idx++) {
if (checker(array[idx])) {
return idx;
}
}
return -1;
};
const getProgress$1 = (pos, prev, next) => (pos - prev) / (next - prev);
const getStyle = (el) => {
if (!el) {
return {};
}
return window.getComputedStyle(el) || el.currentStyle;
};
const setSize = (el, {
width,
height
}) => {
if (!el) {
return;
}
if (width != null) {
if (isString(width)) {
el.style.width = width;
} else {
el.style.width = `${width}px`;
}
}
if (height != null) {
if (isString(height)) {
el.style.height = height;
} else {
el.style.height = `${height}px`;
}
}
};
const isBetween = (val, min, max) => val >= min && val <= max;
const circulateIndex = (index, max) => {
if (index >= max) {
return index % max;
} else if (index < 0) {
return getMinusCompensatedIndex((index + 1) % max - 1, max);
} else {
return index;
}
};
const range = (end) => {
const arr = new Array(end);
for (let i = 0; i < end; i++) {
arr[i] = i;
}
return arr;
};
const getElementSize = ({
el,
horizontal,
useFractionalSize,
useOffset,
style
}) => {
let size = 0;
if (useFractionalSize) {
const baseSize = parseFloat(horizontal ? style.width : style.height) || 0;
const isBorderBoxSizing = style.boxSizing === "border-box";
const border = horizontal ? parseFloat(style.borderLeftWidth || "0") + parseFloat(style.borderRightWidth || "0") : parseFloat(style.borderTopWidth || "0") + parseFloat(style.borderBottomWidth || "0");
if (isBorderBoxSizing) {
size = useOffset ? baseSize : baseSize - border;
} else {
const padding = horizontal ? parseFloat(style.paddingLeft || "0") + parseFloat(style.paddingRight || "0") : parseFloat(style.paddingTop || "0") + parseFloat(style.paddingBottom || "0");
size = useOffset ? baseSize + padding + border : baseSize + padding;
}
} else {
const sizeStr = horizontal ? "Width" : "Height";
size = useOffset ? el[`offset${sizeStr}`] : el[`client${sizeStr}`];
}
return Math.max(size, 0);
};
const setPrototypeOf = Object.setPrototypeOf || ((obj, proto) => {
obj.__proto__ = proto;
return obj;
});
const camelize = (str) => {
return str.replace(/[\s-_]([a-z])/g, (all, letter) => letter.toUpperCase());
};
const getDataAttributes = (element, attributePrefix) => {
const dataAttributes = {};
const attributes = element.attributes;
const length = attributes.length;
for (let i = 0; i < length; ++i) {
const attribute = attributes[i];
const { name, value } = attribute;
if (name.indexOf(attributePrefix) === -1) {
continue;
}
dataAttributes[camelize(name.replace(attributePrefix, ""))] = value;
}
return dataAttributes;
};
const Utils = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
__proto__: null,
camelize,
checkExistence,
circulateIndex,
circulatePosition,
clamp: clamp$1,
find: find$1,
findIndex,
findRight,
getDataAttributes,
getDirection: getDirection$1,
getElement,
getElementSize,
getFlickingAttached,
getMinusCompensatedIndex,
getProgress: getProgress$1,
getStyle,
includes,
isBetween,
isString,
merge,
parseAlign: parseAlign$1,
parseArithmeticExpression,
parseArithmeticSize,
parseBounce,
parseCSSSizeValue,
parseElement,
parsePanelAlign,
range,
setPrototypeOf,
setSize,
toArray: toArray$2
}, Symbol.toStringTag, { value: "Module" }));
class FlickingError extends Error {
/**
* @param message - Error message
* @param code - Error code
*/
constructor(message, code) {
super(message);
setPrototypeOf(this, FlickingError.prototype);
this.name = "FlickingError";
this.code = code;
}
}
class CameraMode {
constructor(flicking) {
this._flicking = flicking;
}
getAnchors() {
const panels = this._flicking.renderer.panels;
return panels.map(
(panel, index) => new AnchorPoint({
index,
position: panel.position,
panel
})
);
}
findAnchorIncludePosition(position) {
const anchors = this._flicking.camera.anchorPoints;
const anchorsIncludingPosition = anchors.filter((anchor) => anchor.panel.includePosition(position, true));
return anchorsIncludingPosition.reduce((nearest, anchor) => {
if (!nearest) return anchor;
return Math.abs(nearest.position - position) < Math.abs(anchor.position - position) ? nearest : anchor;
}, null);
}
findNearestAnchor(position) {
const anchors = this._flicking.camera.anchorPoints;
if (anchors.length <= 0) return null;
let prevDist = Infinity;
for (let anchorIdx = 0; anchorIdx < anchors.length; anchorIdx++) {
const anchor = anchors[anchorIdx];
const dist = Math.abs(anchor.position - position);
if (dist > prevDist) {
return anchors[anchorIdx - 1];
}
prevDist = dist;
}
return anchors[anchors.length - 1];
}
clampToReachablePosition(position) {
const camera = this._flicking.camera;
const range2 = camera.range;
return clamp$1(position, range2.min, range2.max);
}
getCircularOffset() {
return 0;
}
canReach(panel) {
const camera = this._flicking.camera;
const range2 = camera.range;
if (panel.removed) return false;
const panelPos = panel.position;
return panelPos >= range2.min && panelPos <= range2.max;
}
canSee(panel) {
const camera = this._flicking.camera;
const visibleRange = camera.visibleRange;
return panel.isVisibleOnRange(visibleRange.min, visibleRange.max);
}
}
class BoundCameraMode extends CameraMode {
checkAvailability() {
const flicking = this._flicking;
const renderer = flicking.renderer;
const firstPanel = renderer.getPanel(0);
const lastPanel = renderer.getPanel(renderer.panelCount - 1);
if (!firstPanel || !lastPanel) {
return false;
}
const viewportSize = flicking.camera.size;
const firstPanelPrev = firstPanel.range.min;
const lastPanelNext = lastPanel.range.max;
const panelAreaSize = lastPanelNext - firstPanelPrev;
const isBiggerThanViewport = viewportSize < panelAreaSize;
return isBiggerThanViewport;
}
getRange() {
const flicking = this._flicking;
const renderer = flicking.renderer;
const alignPos = flicking.camera.alignPosition;
const firstPanel = renderer.getPanel(0);
const lastPanel = renderer.getPanel(renderer.panelCount - 1);
if (!firstPanel || !lastPanel) {
return { min: 0, max: 0 };
}
const viewportSize = flicking.camera.size;
const firstPanelPrev = firstPanel.range.min;
const lastPanelNext = lastPanel.range.max;
const panelAreaSize = lastPanelNext - firstPanelPrev;
const isBiggerThanViewport = viewportSize < panelAreaSize;
const firstPos = firstPanelPrev + alignPos;
const lastPos = lastPanelNext - viewportSize + alignPos;
if (isBiggerThanViewport) {
return { min: firstPos, max: lastPos };
} else {
const align = flicking.camera.align;
const alignVal = typeof align === "object" ? align.camera : align;
const pos = firstPos + parseAlign$1(alignVal, lastPos - firstPos);
return { min: pos, max: pos };
}
}
getAnchors() {
const flicking = this._flicking;
const camera = flicking.camera;
const panels = flicking.renderer.panels;
if (panels.length <= 0) {
return [];
}
const range2 = flicking.camera.range;
const reachablePanels = panels.filter((panel) => camera.canReach(panel));
if (reachablePanels.length > 0) {
const shouldPrependBoundAnchor = reachablePanels[0].position !== range2.min;
const shouldAppendBoundAnchor = reachablePanels[reachablePanels.length - 1].position !== range2.max;
const indexOffset = shouldPrependBoundAnchor ? 1 : 0;
const newAnchors = reachablePanels.map(
(panel, idx) => new AnchorPoint({
index: idx + indexOffset,
position: panel.position,
panel
})
);
if (shouldPrependBoundAnchor) {
newAnchors.splice(
0,
0,
new AnchorPoint({
index: 0,
position: range2.min,
panel: panels[reachablePanels[0].index - 1]
})
);
}
if (shouldAppendBoundAnchor) {
newAnchors.push(
new AnchorPoint({
index: newAnchors.length,
position: range2.max,
panel: panels[reachablePanels[reachablePanels.length - 1].index + 1]
})
);
}
return newAnchors;
} else if (range2.min !== range2.max) {
const nearestPanelAtMin = this._findNearestPanel(range2.min, panels);
const panelAtMin = nearestPanelAtMin.index === panels.length - 1 ? nearestPanelAtMin.prev() : nearestPanelAtMin;
const panelAtMax = panelAtMin.next();
return [
new AnchorPoint({
index: 0,
position: range2.min,
panel: panelAtMin
}),
new AnchorPoint({
index: 1,
position: range2.max,
panel: panelAtMax
})
];
} else {
return [
new AnchorPoint({
index: 0,
position: range2.min,
panel: this._findNearestPanel(range2.min, panels)
})
];
}
}
findAnchorIncludePosition(position) {
const camera = this._flicking.camera;
const range2 = camera.range;
const anchors = camera.anchorPoints;
if (anchors.length <= 0) return null;
if (position <= range2.min) {
return anchors[0];
} else if (position >= range2.max) {
return anchors[anchors.length - 1];
} else {
return super.findAnchorIncludePosition(position);
}
}
/**
* @internal
*/
_findNearestPanel(pos, panels) {
let prevDist = Infinity;
for (let panelIdx = 0; panelIdx < panels.length; panelIdx++) {
const panel = panels[panelIdx];
const dist = Math.abs(panel.position - pos);
if (dist > prevDist) {
return panels[panelIdx - 1];
}
prevDist = dist;
}
return panels[panels.length - 1];
}
}
class CircularCameraMode extends CameraMode {
checkAvailability() {
const flicking = this._flicking;
const renderer = flicking.renderer;
const panels = renderer.panels;
if (panels.length <= 0) {
return false;
}
const firstPanel = panels[0];
const lastPanel = panels[panels.length - 1];
const firstPanelPrev = firstPanel.range.min - firstPanel.margin.prev;
const lastPanelNext = lastPanel.range.max + lastPanel.margin.next;
const visibleSize = flicking.camera.size;
const panelSizeSum = lastPanelNext - firstPanelPrev;
const canSetCircularMode = panels.every((panel) => panelSizeSum - panel.size >= visibleSize);
return canSetCircularMode;
}
getRange() {
const flicking = this._flicking;
const panels = flicking.renderer.panels;
if (panels.length <= 0) {
return { min: 0, max: 0 };
}
const firstPanel = panels[0];
const lastPanel = panels[panels.length - 1];
const firstPanelPrev = firstPanel.range.min - firstPanel.margin.prev;
const lastPanelNext = lastPanel.range.max + lastPanel.margin.next;
return { min: firstPanelPrev, max: lastPanelNext };
}
getAnchors() {
const flicking = this._flicking;
const panels = flicking.renderer.panels;
return panels.map(
(panel, index) => new AnchorPoint({
index,
position: panel.position,
panel
})
);
}
findNearestAnchor(position) {
const camera = this._flicking.camera;
const anchors = camera.anchorPoints;
if (anchors.length <= 0) return null;
const camRange = camera.range;
let minDist = Infinity;
let minDistIndex = -1;
for (let anchorIdx = 0; anchorIdx < anchors.length; anchorIdx++) {
const anchor = anchors[anchorIdx];
const dist = Math.min(
Math.abs(anchor.position - position),
Math.abs(anchor.position - camRange.min + camRange.max - position),
Math.abs(position - camRange.min + camRange.max - anchor.position)
);
if (dist < minDist) {
minDist = dist;
minDistIndex = anchorIdx;
}
}
return anchors[minDistIndex];
}
findAnchorIncludePosition(position) {
const camera = this._flicking.camera;
const range2 = camera.range;
const anchors = camera.anchorPoints;
const rangeDiff = camera.rangeDiff;
const anchorCount = anchors.length;
const positionInRange = circulatePosition(position, range2.min, range2.max);
let anchorInRange = super.findAnchorIncludePosition(positionInRange);
if (anchorCount > 0 && (position === range2.min || position === range2.max)) {
const possibleAnchors = [
anchorInRange,
new AnchorPoint({
index: 0,
position: anchors[0].position + rangeDiff,
panel: anchors[0].panel
}),
new AnchorPoint({
index: anchorCount - 1,
position: anchors[anchorCount - 1].position - rangeDiff,
panel: anchors[anchorCount - 1].panel
})
].filter((anchor) => !!anchor);
anchorInRange = possibleAnchors.reduce((nearest, anchor) => {
if (!nearest) return anchor;
return Math.abs(nearest.position - position) < Math.abs(anchor.position - position) ? nearest : anchor;
}, null);
}
if (!anchorInRange) return null;
if (position < range2.min) {
const loopCount = -Math.floor((range2.min - position) / rangeDiff) - 1;
return new AnchorPoint({
index: anchorInRange.index,
position: anchorInRange.position + rangeDiff * loopCount,
panel: anchorInRange.panel
});
} else if (position > range2.max) {
const loopCount = Math.floor((position - range2.max) / rangeDiff) + 1;
return new AnchorPoint({
index: anchorInRange.index,
position: anchorInRange.position + rangeDiff * loopCount,
panel: anchorInRange.panel
});
}
return anchorInRange;
}
getCircularOffset() {
const flicking = this._flicking;
const camera = flicking.camera;
if (!camera.circularEnabled) return 0;
const toggled = flicking.panels.filter((panel) => panel.toggled);
const toggledPrev = toggled.filter((panel) => panel.toggleDirection === DIRECTION.PREV);
const toggledNext = toggled.filter((panel) => panel.toggleDirection === DIRECTION.NEXT);
return this._calcPanelAreaSum(toggledPrev) - this._calcPanelAreaSum(toggledNext);
}
clampToReachablePosition(position) {
return position;
}
canReach(panel) {
if (panel.removed) return false;
return true;
}
canSee(panel) {
const camera = this._flicking.camera;
const range2 = camera.range;
const rangeDiff = camera.rangeDiff;
const visibleRange = camera.visibleRange;
const visibleInCurrentRange = super.canSee(panel);
if (visibleRange.min < range2.min) {
return visibleInCurrentRange || panel.isVisibleOnRange(visibleRange.min + rangeDiff, visibleRange.max + rangeDiff);
} else if (visibleRange.max > range2.max) {
return visibleInCurrentRange || panel.isVisibleOnRange(visibleRange.min - rangeDiff, visibleRange.max - rangeDiff);
}
return visibleInCurrentRange;
}
/**
* @internal
*/
_calcPanelAreaSum(panels) {
return panels.reduce((sum, panel) => sum + panel.sizeIncludingMargin, 0);
}
}
class LinearCameraMode extends CameraMode {
checkAvailability() {
return true;
}
getRange() {
var _a, _b;
const renderer = this._flicking.renderer;
const firstPanel = renderer.getPanel(0);
const lastPanel = renderer.getPanel(renderer.panelCount - 1);
return { min: (_a = firstPanel == null ? void 0 : firstPanel.position) != null ? _a : 0, max: (_b = lastPanel == null ? void 0 : lastPanel.position) != null ? _b : 0 };
}
}
let Camera$1 = class Camera {
/**
* Creates a new Camera instance
* @param flicking - An instance of {@link Flicking}
* @param options - Options for the Camera
*/
constructor(flicking, { align = ALIGN.CENTER } = {}) {
this._lookedOffset = 0;
this._checkTranslateSupport = () => {
const transforms = ["webkitTransform", "msTransform", "MozTransform", "OTransform", "transform"];
const supportedStyle = document.documentElement.style;
let transformName = "";
for (const prefixedTransform of transforms) {
if (prefixedTransform in supportedStyle) {
transformName = prefixedTransform;
}
}
if (!transformName) {
throw new FlickingError(MESSAGE.TRANSFORM_NOT_SUPPORTED, CODE.TRANSFORM_NOT_SUPPORTED);
}
this._transform = transformName;
};
this._flicking = flicking;
this._resetInternalValues();
this._align = align;
}
// Internal states getter
/**
* The camera element(`.flicking-camera`)
* @readonly
*/
get element() {
return this._el;
}
/**
* An array of the child elements of the camera element(`.flicking-camera`)
* @readonly
*/
get children() {
return toArray$2(this._el.children);
}
/**
* Current position of the camera
* @readonly
*/
get position() {
return this._position;
}
/**
* Align position inside the viewport where {@link Panel}'s {@link Panel.alignPosition | alignPosition} should be located at
* @readonly
*/
get alignPosition() {
return this._alignPos;
}
/**
* Position offset, used for the {@link Flicking.renderOnlyVisible | renderOnlyVisible} option
* @defaultValue 0
* @readonly
*/
get offset() {
return this._offset - this._circularOffset;
}
/**
* Whether the `circular` option is enabled.
* @remarks
* The {@link Flicking.circular | circular} option can't be enabled when sum of the panel sizes are too small.
* @defaultValue false
* @readonly
*/
get circularEnabled() {
return this._circularEnabled;
}
/**
* A current camera mode
* @readonly
*/
get mode() {
return this._mode;
}
/**
* A range that Camera's {@link Camera.position | position} can reach
* @readonly
*/
get range() {
return this._range;
}
/**
* A difference between Camera's minimum and maximum position that can reach
* @readonly
*/
get rangeDiff() {
return this._range.max - this._range.min;
}
/**
* An array of visible panels from the current position
* @readonly
*/
get visiblePanels() {
return this._visiblePanels;
}
/**
* A range of the visible area from the current position
* @readonly
*/
get visibleRange() {
return { min: this._position - this._alignPos, max: this._position - this._alignPos + this.size };
}
/**
* An array of {@link AnchorPoint}s that Camera can be stopped at
* @readonly
*/
get anchorPoints() {
return this._anchors;
}
/**
* A current parameters of the Camera for updating {@link AxesController}
* @readonly
*/
get controlParams() {
return { range: this._range, position: this._position, circular: this._circularEnabled };
}
/**
* A Boolean value indicating whether Camera's over the minimum or maximum position reachable
* @readonly
*/
get atEdge() {
return this._position <= this._range.min || this._position >= this._range.max;
}
/**
* Return the size of the viewport
* @readonly
*/
get size() {
const flicking = this._flicking;
return flicking ? flicking.horizontal ? flicking.viewport.width : flicking.viewport.height : 0;
}
/**
* Return the camera's position progress from the first panel to last panel
* @remarks
* Range is from 0 to last panel's index
* @readonly
*/
get progress() {
const flicking = this._flicking;
const position = this._position + this._offset;
const nearestAnchor = this.findNearestAnchor(this._position);
if (!flicking || !nearestAnchor) {
return NaN;
}
const nearestPanel = nearestAnchor.panel;
const panelPos = nearestPanel.position + nearestPanel.offset;
const bounceSize = flicking.control.controller.bounce;
const { min: prevRange, max: nextRange } = this.range;
const rangeDiff = this.rangeDiff;
if (position === panelPos) {
return nearestPanel.index;
}
if (position < panelPos) {
const prevPanel = nearestPanel.prev();
let prevPosition = prevPanel ? prevPanel.position + prevPanel.offset : prevRange - bounceSize[0];
if (prevPosition > panelPos) {
prevPosition -= rangeDiff;
}
return nearestPanel.index - 1 + getProgress$1(position, prevPosition, panelPos);
} else {
const nextPanel = nearestPanel.next();
let nextPosition = nextPanel ? nextPanel.position + nextPanel.offset : nextRange + bounceSize[1];
if (nextPosition < panelPos) {
nextPosition += rangeDiff;
}
return nearestPanel.index + getProgress$1(position, panelPos, nextPosition);
}
}
/**
* {@link https://developer.mozilla.org/en-US/docs/Web/CSS/direction | direction} CSS property applied to the camera element(`.flicking-camera`)
* @readonly
*/
get panelOrder() {
return this._panelOrder;
}
// Options Getter
/**
* A value indicating where the {@link Camera.alignPosition | alignPosition} should be located at inside the viewport element
*/
get align() {
return this._align;
}
// Options Setter
set align(val) {
this._align = val;
}
/**
* Initialize Camera
* @remarks
* This method is called automatically during {@link Flicking.init}. It finds the camera element inside the viewport.
* @throws {@link InitializationErrors}
* @returns The current instance for method chaining
*/
init() {
const viewportEl = this._flicking.viewport.element;
checkExistence(viewportEl.firstElementChild, "First element child of the viewport element");
this._el = viewportEl.firstElementChild;
this._checkTranslateSupport();
this._updateMode();
this.updatePanelOrder();
return this;
}
/**
* Destroy Camera and return to initial state
* @remarks
* This method resets all internal values to their initial state.
* @returns The current instance for method chaining
*/
destroy() {
this._resetInternalValues();
return this;
}
/**
* Move to the given position and apply CSS transform
* @remarks
* This method updates the camera position, toggles panels for circular mode, and refreshes visible panels.
* @param pos - A new position
* @throws {@link InitializationErrors}
*/
lookAt(pos) {
const prevOffset = this._offset;
const isChangedOffset = this._lookedOffset !== prevOffset;
const flicking = getFlickingAttached(this._flicking);
const prevPos = this._position;
this._position = pos;
const toggled = this._togglePanels(prevPos, pos);
this._refreshVisiblePanels();
this._checkNeedPanel();
this._checkReachEnd(prevPos, pos);
if (toggled) {
void flicking.renderer.render().then(() => {
this.updateOffset();
this._lookedOffset = this._offset;
});
} else if (isChangedOffset) {
this.updateOffset();
this._lookedOffset = this._offset;
} else {
this.applyTransform();
}
}
/**
* Return a previous {@link AnchorPoint} of given {@link AnchorPoint}
* @remarks
* If it does not exist, return `null` instead
* @param anchor - A reference {@link AnchorPoint}
* @returns The previous {@link AnchorPoint}
*/
getPrevAnchor(anchor) {
if (!this._circularEnabled || anchor.index !== 0) {
return this._anchors[anchor.index - 1] || null;
} else {
const anchors = this._anchors;
const rangeDiff = this.rangeDiff;
const lastAnchor = anchors[anchors.length - 1];
return new AnchorPoint({
index: lastAnchor.index,
position: lastAnchor.position - rangeDiff,
panel: lastAnchor.panel
});
}
}
/**
* Return a next {@link AnchorPoint} of given {@link AnchorPoint}
* @remarks
* If it does not exist, return `null` instead
* @param anchor - A reference {@link AnchorPoint}
* @returns The next {@link AnchorPoint}
*/
getNextAnchor(anchor) {
const anchors = this._anchors;
if (!this._circularEnabled || anchor.index !== anchors.length - 1) {
return anchors[anchor.index + 1] || null;
} else {
const rangeDiff = this.rangeDiff;
const firstAnchor = anchors[0];
return new AnchorPoint({
index: firstAnchor.index,
position: firstAnchor.position + rangeDiff,
panel: firstAnchor.panel
});
}
}
/**
* Return the camera's position progress in the panel below
* @remarks
* Value is from 0 to 1 when the camera's inside panel.
* Value can be lower than 0 or bigger than 1 when it's in the margin area
* @param panel - A panel to check
* @returns Progress value from 0 to 1 (or outside this range when in margin area)
*/
getProgressInPanel(panel) {
const panelRange = panel.range;
return (this._position - panelRange.min) / (panelRange.max - panelRange.min);
}
/**
* Return {@link AnchorPoint} that includes given position
* @remarks
* If there's no {@link AnchorPoint} that includes the given position, return `null` instead
* @param position - A position to check
* @returns The {@link AnchorPoint} that includes the given position
*/
findAnchorIncludePosition(position) {
return this._mode.findAnchorIncludePosition(position);
}
/**
* Return {@link AnchorPoint} nearest to given position
* @remarks
* If there're no {@link AnchorPoint}s, return `null` instead
* @param position - A position to check
* @returns The {@link AnchorPoint} nearest to the given position
*/
findNearestAnchor(position) {
return this._mode.findNearestAnchor(position);
}
/**
* Return {@link AnchorPoint} that matches {@link Flicking.currentPanel}
* @returns The {@link AnchorPoint} that matches current panel
*/
findActiveAnchor() {
var _a;
const flicking = getFlickingAttached(this._flicking);
const activePanel = flicking.control.activePanel;
if (!activePanel) return null;
return (_a = find$1(this._anchors, (anchor) => anchor.panel.index === activePanel.index)) != null ? _a : this.findNearestAnchor(activePanel.position);
}
/**
* Clamp the given position between camera's range
* @param position - A position to clamp
* @returns A clamped position
*/
clampToReachablePosition(position) {
return this._mode.clampToReachablePosition(position);
}
/**
* Check whether the given panel is inside of the Camera's range
* @param panel - An instance of {@link Panel} to check
* @returns Whether the panel's inside Camera's range
*/
canReach(panel) {
return this._mode.canReach(panel);
}
/**
* Check whether the given panel element is visible at the current position
* @param panel - An instance of {@link Panel} to check
* @returns Whether the panel element is visible at the current position
*/
canSee(panel) {
return this._mode.canSee(panel);
}
/**
* Update {@link Camera.range | range} of Camera
* @remarks
* This method recalculates the camera range based on the current panel positions and circular mode settings.
* @throws {@link InitializationErrors}
* @returns The current instance for method chaining
*/
updateRange() {
const flicking = getFlickingAttached(this._flicking);
const renderer = flicking.renderer;
const panels = renderer.panels;
this._updateMode();
this._range = this._mode.getRange();
panels.forEach((panel) => panel.updateCircularToggleDirection());
return this;
}
/**
* Update Camera's {@link Camera.alignPosition | alignPosition}
* @returns The current instance for method chaining
*/
updateAlignPos() {
const align = this._align;
const alignVal = typeof align === "object" ? align.camera : align;
this._alignPos = parseAlign$1(alignVal, this.size);
return this;
}
/**
* Update Camera's {@link Camera.anchorPoints | anchorPoints}
* @remarks
* Anchor points are positions where the camera can stop. This method recalculates them based on the current mode.
* @throws {@link InitializationErrors}
* @returns The current instance for method chaining
*/
updateAnchors() {
this._anchors = this._mode.getAnchors();
return this;
}
/**
* Update Viewport's height to visible panel's max height
* @remarks
* This method only takes effect when {@link FlickingOptions.horizontal | horizontal} is `true` and {@link FlickingOptions.adaptive | adaptive} is enabled.
* @throws {@link InitializationErrors}
*/
updateAdaptiveHeight() {
const flicking = getFlickingAttached(this._flicking);
const activePanel = flicking.control.activePanel;
const visiblePanels = flicking.visiblePanels;
const selectedPanels = [...visiblePanels];
if (activePanel) {
selectedPanels.push(activePanel);
}
if (!flicking.horizontal || !flicking.adaptive || !selectedPanels.length) return;
const maxHeight = Math.max(...selectedPanels.map((panel) => panel.height));
flicking.viewport.setSize({
height: maxHeight