UNPKG

@egjs/flicking

Version:

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

1,520 lines (1,518 loc) 346 kB
(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