UNPKG

@egjs/axes

Version:

A module used to change the information of user action entered by various input devices such as touch screen or mouse into the logical virtual coordinates. You can easily create a UI that responds to user actions.

1,502 lines (1,478 loc) 130 kB
/* Copyright (c) NAVER Corp. name: @egjs/axes license: MIT author: NAVER Corp. repository: https://github.com/naver/egjs-axes version: 3.9.2 */ import getAgent from '@egjs/agent'; import Component, { ComponentEvent } from '@egjs/component'; import { getObserver, Observe, ReactiveSubscribe, Computed } from '@cfcs/core'; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ /* 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 __()); } var __assign = function () { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __decorate(decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; } /* * Copyright (c) 2015 NAVER Corp. * egjs projects are licensed under the MIT license */ /* eslint-disable no-new-func, no-nested-ternary */ var win; if (typeof window === "undefined") { // window is undefined in node.js win = { navigator: { userAgent: "" } }; } else { win = window; } /* * Copyright (c) 2015 NAVER Corp. * egjs projects are licensed under the MIT license */ var DIRECTION_NONE = 1; var DIRECTION_LEFT = 2; var DIRECTION_RIGHT = 4; var DIRECTION_HORIZONTAL = 2 | 4; var DIRECTION_UP = 8; var DIRECTION_DOWN = 16; var DIRECTION_VERTICAL = 8 | 16; var DIRECTION_ALL = 2 | 4 | 8 | 16; var MOUSE_LEFT = "left"; var MOUSE_RIGHT = "right"; var MOUSE_MIDDLE = "middle"; var MOUSE_BUTTON_CODE_MAP = { 1: MOUSE_LEFT, 2: MOUSE_MIDDLE, 3: MOUSE_RIGHT }; var ANY = "any"; var NONE = "none"; var SHIFT = "shift"; var CTRL = "ctrl"; var ALT = "alt"; var META = "meta"; var VELOCITY_INTERVAL = 16; var AXES_METHODS = ["connect", "disconnect", "get", "setTo", "setBy", "setOptions", "setAxis", "stopAnimation", "updateAnimation", "isBounceArea"]; var AXES_EVENTS = ["hold", "release", "change", "animationStart", "animationEnd", "finish"]; var IOS_EDGE_THRESHOLD = 30; var IS_IOS_SAFARI = "ontouchstart" in win && getAgent().browser.name === "safari"; var TRANSFORM = function () { if (typeof document === "undefined") { return ""; } var bodyStyle = (document.head || document.getElementsByTagName("head")[0]).style; var target = ["transform", "webkitTransform", "msTransform", "mozTransform"]; for (var i = 0, len = target.length; i < len; i++) { if (target[i] in bodyStyle) { return target[i]; } } return ""; }(); var PREVENT_DRAG_CSSPROPS = { "-webkit-user-select": "none", "-ms-user-select": "none", "-moz-user-select": "none", "user-select": "none", "-webkit-user-drag": "none" }; var toArray = function (nodes) { // const el = Array.prototype.slice.call(nodes); // for IE8 var el = []; for (var i = 0, len = nodes.length; i < len; i++) { el.push(nodes[i]); } return el; }; var $ = function (param, multi) { if (multi === void 0) { multi = false; } var el; if (typeof param === "string") { // String (HTML, Selector) // check if string is HTML tag format var match = param.match(/^<([a-z]+)\s*([^>]*)>/); // creating element if (match) { // HTML var dummy = document.createElement("div"); dummy.innerHTML = param; el = toArray(dummy.childNodes); } else { // Selector el = toArray(document.querySelectorAll(param)); } if (!multi) { el = el.length >= 1 ? el[0] : undefined; } } else if (param === win) { // window el = param; } else if ("value" in param || "current" in param) { el = param.value || param.current; } else if (param.nodeName && (param.nodeType === 1 || param.nodeType === 9)) { // HTMLElement, Document el = param; } else if ("jQuery" in win && param instanceof jQuery || param.constructor.prototype.jquery) { // jQuery el = multi ? param.toArray() : param.get(0); } else if (Array.isArray(param)) { el = param.map(function (v) { return $(v); }); if (!multi) { el = el.length >= 1 ? el[0] : undefined; } } return el; }; var raf = win.requestAnimationFrame || win.webkitRequestAnimationFrame; var caf = win.cancelAnimationFrame || win.webkitCancelAnimationFrame; if (raf && !caf) { var keyInfo_1 = {}; var oldraf_1 = raf; raf = function (callback) { var wrapCallback = function (timestamp) { if (keyInfo_1[key]) { callback(timestamp); } }; var key = oldraf_1(wrapCallback); keyInfo_1[key] = true; return key; }; caf = function (key) { delete keyInfo_1[key]; }; } else if (!(raf && caf)) { raf = function (callback) { return win.setTimeout(function () { callback(win.performance && win.performance.now && win.performance.now() || new Date().getTime()); }, 16); }; caf = win.clearTimeout; } /** * A polyfill for the window.requestAnimationFrame() method. * @see https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame * @private */ var requestAnimationFrame = function (fp) { return raf(fp); }; /** * A polyfill for the window.cancelAnimationFrame() method. It cancels an animation executed through a call to the requestAnimationFrame() method. * @param {Number} key − The ID value returned through a call to the requestAnimationFrame() method. <ko>requestAnimationFrame() 메서드가 반환한 아이디 값</ko> * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/cancelAnimationFrame * @private */ var cancelAnimationFrame = function (key) { caf(key); }; var map = function (obj, callback) { var tranformed = {}; for (var k in obj) { if (k) { tranformed[k] = callback(obj[k], k); } } return tranformed; }; var filter = function (obj, callback) { var filtered = {}; for (var k in obj) { if (k && callback(obj[k], k)) { filtered[k] = obj[k]; } } return filtered; }; var every = function (obj, callback) { for (var k in obj) { if (k && !callback(obj[k], k)) { return false; } } return true; }; var equal = function (target, base) { return every(target, function (v, k) { return v === base[k]; }); }; var roundNumFunc = {}; var roundNumber = function (num, roundUnit) { // Cache for performance if (!roundNumFunc[roundUnit]) { roundNumFunc[roundUnit] = getRoundFunc(roundUnit); } return roundNumFunc[roundUnit](num); }; var roundNumbers = function (num, roundUnit) { if (!num || !roundUnit) { return num; } return map(num, function (value, key) { return roundNumber(value, typeof roundUnit === "number" ? roundUnit : roundUnit[key]); }); }; var getDecimalPlace = function (val) { if (!isFinite(val)) { return 0; } var v = "".concat(val); if (v.indexOf("e") >= 0) { // Exponential Format // 1e-10, 1e-12 var p = 0; var e = 1; while (Math.round(val * e) / e !== val) { e *= 10; p++; } return p; } // In general, following has performance benefit. // https://jsperf.com/precision-calculation return v.indexOf(".") >= 0 ? v.length - v.indexOf(".") - 1 : 0; }; var inversePow = function (n) { // replace Math.pow(10, -n) to solve floating point issue. // eg. Math.pow(10, -4) => 0.00009999999999999999 return 1 / Math.pow(10, n); }; var getRoundFunc = function (v) { var p = v < 1 ? Math.pow(10, getDecimalPlace(v)) : 1; return function (n) { if (v === 0) { return 0; } return Math.round(Math.round(n / v) * v * p) / p; }; }; var getAngle = function (posX, posY) { return Math.atan2(posY, posX) * 180 / Math.PI; }; var isCssPropsFromAxes = function (originalCssProps) { var same = true; Object.keys(PREVENT_DRAG_CSSPROPS).forEach(function (prop) { if (!originalCssProps || originalCssProps[prop] !== PREVENT_DRAG_CSSPROPS[prop]) { same = false; } }); return same; }; var getDirection = function (useHorizontal, useVertical) { if (useHorizontal && useVertical) { return DIRECTION_ALL; } else if (useHorizontal) { return DIRECTION_HORIZONTAL; } else if (useVertical) { return DIRECTION_VERTICAL; } else { return DIRECTION_NONE; } }; var useDirection = function (checkType, direction, userDirection) { if (userDirection) { return !!(direction === DIRECTION_ALL || direction & checkType && userDirection & checkType); } else { return !!(direction & checkType); } }; var setCssProps = function (element, option, direction) { var _a; var touchActionMap = (_a = {}, _a[DIRECTION_NONE] = "auto", _a[DIRECTION_ALL] = "none", _a[DIRECTION_VERTICAL] = "pan-x", _a[DIRECTION_HORIZONTAL] = "pan-y", _a); var oldCssProps = {}; if (element && element.style) { var touchAction = option.touchAction ? option.touchAction : touchActionMap[direction]; var newCssProps_1 = __assign(__assign({}, PREVENT_DRAG_CSSPROPS), { "touch-action": element.style["touch-action"] === "none" ? "none" : touchAction }); Object.keys(newCssProps_1).forEach(function (prop) { oldCssProps[prop] = element.style[prop]; }); // Old style props like user-select can be corrupted if you change the style directly in the logic above. Object.keys(newCssProps_1).forEach(function (prop) { element.style[prop] = newCssProps_1[prop]; }); } return oldCssProps; }; var revertCssProps = function (element, originalCssProps) { if (element && element.style && originalCssProps) { Object.keys(originalCssProps).forEach(function (prop) { element.style[prop] = originalCssProps[prop]; }); } return; }; var EventManager = /*#__PURE__*/function () { function EventManager(_axes) { this._axes = _axes; this.holdingCount = 0; } /** * This event is fired when a user holds an element on the screen of the device. * @ko 사용자가 기기의 화면에 손을 대고 있을 때 발생하는 이벤트 * @event Axes#hold * @type {object} * @property {Object.<string, number>} pos coordinate <ko>좌표 정보</ko> * @property {Object} input The instance of inputType where the event occurred<ko>이벤트가 발생한 inputType 인스턴스</ko> * @property {Object} inputEvent The event object received from inputType <ko>inputType으로 부터 받은 이벤트 객체</ko> * @property {Boolean} isTrusted Returns true if an event was generated by the user action, or false if it was caused by a script or API call <ko>사용자의 액션에 의해 이벤트가 발생하였으면 true, 스크립트나 API호출에 의해 발생하였을 경우에는 false를 반환한다.</ko> * * @example * ```js * const axes = new eg.Axes({ * "x": { * range: [0, 100] * }, * "zoom": { * range: [50, 30] * } * }).on("hold", function(event) { * // event.pos * // event.input * // event.inputEvent * // isTrusted * }); * ``` */ var __proto = EventManager.prototype; __proto.hold = function (pos, option) { var roundPos = this._getRoundPos(pos).roundPos; this._axes.trigger(new ComponentEvent("hold", { pos: roundPos, input: option.input || null, inputEvent: option.event || null, isTrusted: true })); }; /** * Specifies the coordinates to move after the 'change' event. It works when the holding value of the change event is true. * @ko 'change' 이벤트 이후 이동할 좌표를 지정한다. change이벤트의 holding 값이 true일 경우에 동작한다 * @param {Object.<string, number>} pos The coordinate to move to <ko>이동할 좌표</ko> * @example * ```js * const axes = new eg.Axes({ * "x": { * range: [0, 100] * }, * "zoom": { * range: [50, 30] * } * }).on("change", function(event) { * event.holding && event.set({x: 10}); * }); * ``` */ /** Specifies the animation coordinates to move after the 'release' or 'animationStart' events. * @ko 'release' 또는 'animationStart' 이벤트 이후 이동할 좌표를 지정한다. * @param {Object.<string, number>} pos The coordinate to move to <ko>이동할 좌표</ko> * @param {Number} [duration=0] Duration of the animation (unit: ms) <ko>애니메이션 진행 시간(단위: ms)</ko> * @example * ```js * const axes = new eg.Axes({ * "x": { * range: [0, 100] * }, * "zoom": { * range: [50, 30] * } * }).on("animationStart", function(event) { * event.setTo({x: 10}, 2000); * }); * ``` */ /** * This event is fired when a user release an element on the screen of the device. * @ko 사용자가 기기의 화면에서 손을 뗐을 때 발생하는 이벤트 * @event Axes#release * @type {object} * @property {Object.<string, number>} depaPos The coordinates when releasing an element<ko>손을 뗐을 때의 좌표 </ko> * @property {Object.<string, number>} destPos The coordinates to move to after releasing an element<ko>손을 뗀 뒤에 이동할 좌표</ko> * @property {Object.<string, number>} delta The movement variation of coordinate <ko>좌표의 변화량</ko> * @property {Object.<string, number>} bounceRatio If the coordinates at the time of release are in the bounce area, the current bounce value divided by the maximum bounce value <ko>손을 뗐을 때의 좌표가 bounce 영역에 있는 경우 현재 bounce된 값을 최대 bounce 값으로 나눈 수치.</ko> * @property {Object} inputEvent The event object received from inputType <ko>inputType으로 부터 받은 이벤트 객체</ko> * @property {Object} input The instance of inputType where the event occurred<ko>이벤트가 발생한 inputType 인스턴스</ko> * @property {setTo} setTo Specifies the animation coordinates to move after the event <ko>이벤트 이후 이동할 애니메이션 좌표를 지정한다</ko> * @property {Boolean} isTrusted Returns true if an event was generated by the user action, or false if it was caused by a script or API call <ko>사용자의 액션에 의해 이벤트가 발생하였으면 true, 스크립트나 API호출에 의해 발생하였을 경우에는 false를 반환한다.</ko> * * @example * ```js * const axes = new eg.Axes({ * "x": { * range: [0, 100] * }, * "zoom": { * range: [50, 30] * } * }).on("release", function(event) { * // event.depaPos * // event.destPos * // event.delta * // event.input * // event.inputEvent * // event.setTo * // event.isTrusted * * // if you want to change the animation coordinates to move after the 'release' event. * event.setTo({x: 10}, 2000); * }); * ``` */ __proto.triggerRelease = function (param) { var _a = this._getRoundPos(param.destPos, param.depaPos), roundPos = _a.roundPos, roundDepa = _a.roundDepa; param.destPos = roundPos; param.depaPos = roundDepa; param.setTo = this._createUserControll(param.destPos, param.duration); this._axes.trigger(new ComponentEvent("release", __assign(__assign({}, param), { bounceRatio: this._getBounceRatio(roundPos) }))); }; /** * This event is fired when coordinate changes. * @ko 좌표가 변경됐을 때 발생하는 이벤트 * @event Axes#change * @type {object} * @property {Object.<string, number>} pos The coordinate <ko>좌표</ko> * @property {Object.<string, number>} delta The movement variation of coordinate <ko>좌표의 변화량</ko> * @property {Object.<string, number>} bounceRatio If the current coordinates are in the bounce area, the current bounce value divided by the maximum bounce value <ko>현재 좌표가 bounce 영역에 있는 경우 현재 bounce된 값을 최대 bounce 값으로 나눈 수치.</ko> * @property {Boolean} holding Indicates whether a user holds an element on the screen of the device.<ko>사용자가 기기의 화면을 누르고 있는지 여부</ko> * @property {Object} input The instance of inputType where the event occurred. If the value is changed by animation, it returns 'null'.<ko>이벤트가 발생한 inputType 인스턴스. 애니메이션에 의해 값이 변경될 경우에는 'null'을 반환한다.</ko> * @property {Object} inputEvent The event object received from inputType. If the value is changed by animation, it returns 'null'.<ko>inputType으로 부터 받은 이벤트 객체. 애니메이션에 의해 값이 변경될 경우에는 'null'을 반환한다.</ko> * @property {set} set Specifies the coordinates to move after the event. It works when the holding value is true <ko>이벤트 이후 이동할 좌표를 지정한다. holding 값이 true일 경우에 동작한다.</ko> * @property {Boolean} isTrusted Returns true if an event was generated by the user action, or false if it was caused by a script or API call <ko>사용자의 액션에 의해 이벤트가 발생하였으면 true, 스크립트나 API호출에 의해 발생하였을 경우에는 false를 반환한다.</ko> * * @example * ```js * const axes = new eg.Axes({ * "x": { * range: [0, 100] * }, * "zoom": { * range: [50, 30] * } * }).on("change", function(event) { * // event.pos * // event.delta * // event.input * // event.inputEvent * // event.holding * // event.set * // event.isTrusted * * // if you want to change the coordinates to move after the 'change' event. * // it works when the holding value of the change event is true. * event.holding && event.set({x: 10}); * }); * ``` */ __proto.triggerChange = function (pos, depaPos, option, holding) { var _this = this; if (holding === void 0) { holding = false; } var animationManager = this.animationManager; var axisManager = animationManager.axisManager; var eventInfo = animationManager.getEventInfo(); var _a = this._getRoundPos(pos, depaPos), roundPos = _a.roundPos, roundDepa = _a.roundDepa; var moveTo = axisManager.moveTo(roundPos, roundDepa); var inputEvent = (option === null || option === void 0 ? void 0 : option.event) || (eventInfo === null || eventInfo === void 0 ? void 0 : eventInfo.event) || null; var param = { pos: moveTo.pos, delta: moveTo.delta, bounceRatio: this._getBounceRatio(moveTo.pos), holding: holding, inputEvent: inputEvent, isTrusted: !!inputEvent, input: (option === null || option === void 0 ? void 0 : option.input) || (eventInfo === null || eventInfo === void 0 ? void 0 : eventInfo.input) || null, set: inputEvent ? this._createUserControll(moveTo.pos) : function () {} // eslint-disable-line @typescript-eslint/no-empty-function }; var event = new ComponentEvent("change", param); this._axes.trigger(event); Object.keys(moveTo.pos).forEach(function (axis) { var p = moveTo.pos[axis]; getObserver(_this._axes, axis, p).current = p; }); if (inputEvent) { axisManager.set(param.set().destPos); } return !event.isCanceled(); }; /** * This event is fired when animation starts. * @ko 에니메이션이 시작할 때 발생한다. * @event Axes#animationStart * @type {object} * @property {Object.<string, number>} depaPos The coordinates when animation starts<ko>애니메이션이 시작 되었을 때의 좌표 </ko> * @property {Object.<string, number>} destPos The coordinates to move to. If you change this value, you can run the animation<ko>이동할 좌표. 이값을 변경하여 애니메이션을 동작시킬수 있다</ko> * @property {Object.<string, number>} delta The movement variation of coordinate <ko>좌표의 변화량</ko> * @property {Number} duration Duration of the animation (unit: ms). If you change this value, you can control the animation duration time.<ko>애니메이션 진행 시간(단위: ms). 이값을 변경하여 애니메이션의 이동시간을 조절할 수 있다.</ko> * @property {Object} input The instance of inputType where the event occurred. If the value is changed by animation, it returns 'null'.<ko>이벤트가 발생한 inputType 인스턴스. 애니메이션에 의해 값이 변경될 경우에는 'null'을 반환한다.</ko> * @property {Object} inputEvent The event object received from inputType <ko>inputType으로 부터 받은 이벤트 객체</ko> * @property {setTo} setTo Specifies the animation coordinates to move after the event <ko>이벤트 이후 이동할 애니메이션 좌표를 지정한다</ko> * @property {Boolean} isTrusted Returns true if an event was generated by the user action, or false if it was caused by a script or API call <ko>사용자의 액션에 의해 이벤트가 발생하였으면 true, 스크립트나 API호출에 의해 발생하였을 경우에는 false를 반환한다.</ko> * * @example * ```js * const axes = new eg.Axes({ * "x": { * range: [0, 100] * }, * "zoom": { * range: [50, 30] * } * }).on("release", function(event) { * // event.depaPos * // event.destPos * // event.delta * // event.input * // event.inputEvent * // event.setTo * // event.isTrusted * * // if you want to change the animation coordinates to move after the 'animationStart' event. * event.setTo({x: 10}, 2000); * }); * ``` */ __proto.triggerAnimationStart = function (param) { var _a = this._getRoundPos(param.destPos, param.depaPos), roundPos = _a.roundPos, roundDepa = _a.roundDepa; param.destPos = roundPos; param.depaPos = roundDepa; param.setTo = this._createUserControll(param.destPos, param.duration); var event = new ComponentEvent("animationStart", param); this._axes.trigger(event); return !event.isCanceled(); }; /** * This event is fired when animation ends. * @ko 에니메이션이 끝났을 때 발생한다. * @event Axes#animationEnd * @type {object} * @property {Boolean} isTrusted Returns true if an event was generated by the user action, or false if it was caused by a script or API call <ko>사용자의 액션에 의해 이벤트가 발생하였으면 true, 스크립트나 API호출에 의해 발생하였을 경우에는 false를 반환한다.</ko> * * @example * ```js * const axes = new eg.Axes({ * "x": { * range: [0, 100] * }, * "zoom": { * range: [50, 30] * } * }).on("animationEnd", function(event) { * // event.isTrusted * }); * ``` */ __proto.triggerAnimationEnd = function (isTrusted) { if (isTrusted === void 0) { isTrusted = false; } this._axes.trigger(new ComponentEvent("animationEnd", { isTrusted: isTrusted })); }; /** * This event is fired when all actions have been completed. * @ko 에니메이션이 끝났을 때 발생한다. * @event Axes#finish * @type {object} * @property {Boolean} isTrusted Returns true if an event was generated by the user action, or false if it was caused by a script or API call <ko>사용자의 액션에 의해 이벤트가 발생하였으면 true, 스크립트나 API호출에 의해 발생하였을 경우에는 false를 반환한다.</ko> * * @example * ```js * const axes = new eg.Axes({ * "x": { * range: [0, 100] * }, * "zoom": { * range: [50, 30] * } * }).on("finish", function(event) { * // event.isTrusted * }); * ``` */ __proto.triggerFinish = function (isTrusted) { if (isTrusted === void 0) { isTrusted = false; } this._axes.trigger(new ComponentEvent("finish", { isTrusted: isTrusted })); }; __proto.setAnimationManager = function (animationManager) { this.animationManager = animationManager; }; __proto.destroy = function () { this._axes.off(); }; __proto._createUserControll = function (pos, duration) { if (duration === void 0) { duration = 0; } // to controll var userControl = { destPos: __assign({}, pos), duration: duration }; return function (toPos, userDuration) { if (toPos) { userControl.destPos = __assign({}, toPos); } if (userDuration !== undefined) { userControl.duration = userDuration; } return userControl; }; }; __proto._getRoundPos = function (pos, depaPos) { // round value if round exist var roundUnit = this._axes.options.round; // if (round == null) { // return {pos, depaPos}; // undefined, undefined // } return { roundPos: roundNumbers(pos, roundUnit), roundDepa: roundNumbers(depaPos, roundUnit) }; }; __proto._getBounceRatio = function (pos) { return this._axes.axisManager.map(pos, function (v, opt) { if (v < opt.range[0] && opt.bounce[0] !== 0) { return (opt.range[0] - v) / opt.bounce[0]; } else if (v > opt.range[1] && opt.bounce[1] !== 0) { return (v - opt.range[1]) / opt.bounce[1]; } else { return 0; } }); }; __decorate([Observe], EventManager.prototype, "holdingCount", void 0); return EventManager; }(); var InterruptManager = /*#__PURE__*/function () { function InterruptManager(_options) { this._options = _options; this._prevented = false; // check whether the animation event was prevented } var __proto = InterruptManager.prototype; __proto.isInterrupting = function () { // when interruptable is 'true', return value is always 'true'. return this._options.interruptable || this._prevented; }; __proto.isInterrupted = function () { return !this._options.interruptable && this._prevented; }; __proto.setInterrupt = function (prevented) { if (!this._options.interruptable) { this._prevented = prevented; } }; return InterruptManager; }(); /* * Copyright (c) 2015 NAVER Corp. * egjs projects are licensed under the MIT license */ var getInsidePosition = function (destPos, range, circular, bounce) { var toDestPos = destPos; var targetRange = [circular[0] ? range[0] : bounce ? range[0] - bounce[0] : range[0], circular[1] ? range[1] : bounce ? range[1] + bounce[1] : range[1]]; toDestPos = Math.max(targetRange[0], toDestPos); toDestPos = Math.min(targetRange[1], toDestPos); return toDestPos; }; // determine outside var isOutside = function (pos, range) { return pos < range[0] || pos > range[1]; }; // determine whether position has reached the maximum moveable area var isEndofBounce = function (pos, range, bounce, circular) { return !circular[0] && pos === range[0] - bounce[0] || !circular[1] && pos === range[1] + bounce[1]; }; var getDuration = function (distance, deceleration) { var duration = Math.sqrt(distance / deceleration * 2); // when duration is under 100, then value is zero return duration < 100 ? 0 : duration; }; var isCircularable = function (destPos, range, circular) { return circular[1] && destPos > range[1] || circular[0] && destPos < range[0]; }; var getCirculatedPos = function (pos, range, circular) { var toPos = pos; var min = range[0]; var max = range[1]; var length = max - min; if (circular[1] && pos > max) { // right toPos = (toPos - max) % length + min; } if (circular[0] && pos < min) { // left toPos = (toPos - min) % length + max; } return toPos; }; var AxisManager = /*#__PURE__*/function () { function AxisManager(_axis) { var _this = this; this._axis = _axis; this._complementOptions(); this._pos = Object.keys(this._axis).reduce(function (pos, v) { pos[v] = _this._axis[v].startPos; return pos; }, {}); } var __proto = AxisManager.prototype; __proto.getDelta = function (depaPos, destPos) { var fullDepaPos = this.get(depaPos); return map(this.get(destPos), function (v, k) { return v - fullDepaPos[k]; }); }; __proto.get = function (axes) { var _this = this; if (axes && Array.isArray(axes)) { return axes.reduce(function (acc, v) { if (v && v in _this._pos) { acc[v] = _this._pos[v]; } return acc; }, {}); } else { return __assign(__assign({}, this._pos), axes || {}); } }; __proto.moveTo = function (pos, depaPos) { if (depaPos === void 0) { depaPos = this._pos; } var delta = map(this._pos, function (v, key) { return key in pos && key in depaPos ? pos[key] - depaPos[key] : 0; }); this.set(this.map(pos, function (v, opt) { return opt ? getCirculatedPos(v, opt.range, opt.circular) : 0; })); return { pos: __assign({}, this._pos), delta: delta }; }; __proto.set = function (pos) { for (var k in pos) { if (k && k in this._pos) { this._pos[k] = pos[k]; } } }; __proto.every = function (pos, callback) { var axisOptions = this._axis; return every(pos, function (value, key) { return callback(value, axisOptions[key], key); }); }; __proto.filter = function (pos, callback) { var axisOptions = this._axis; return filter(pos, function (value, key) { return callback(value, axisOptions[key], key); }); }; __proto.map = function (pos, callback) { var axisOptions = this._axis; return map(pos, function (value, key) { return callback(value, axisOptions[key], key); }); }; __proto.isOutside = function (axes) { return !this.every(axes ? this.get(axes) : this._pos, function (v, opt) { return !isOutside(v, opt.range); }); }; __proto.getAxisOptions = function (key) { return this._axis[key]; }; __proto.setAxis = function (axis) { var _this = this; Object.keys(axis).forEach(function (key) { if (!_this._axis[key]) { throw new Error("Axis ".concat(key, " does not exist in Axes instance")); } _this._axis[key] = __assign(__assign({}, _this._axis[key]), axis[key]); }); this._complementOptions(); }; /** * set up 'css' expression * @private */ __proto._complementOptions = function () { var _this = this; Object.keys(this._axis).forEach(function (axis) { _this._axis[axis] = __assign({ range: [0, 100], startPos: _this._axis[axis].range[0], bounce: [0, 0], circular: [false, false] }, _this._axis[axis]); ["bounce", "circular"].forEach(function (v) { var axisOption = _this._axis; var key = axisOption[axis][v]; if (/string|number|boolean/.test(typeof key)) { axisOption[axis][v] = [key, key]; } }); }); }; return AxisManager; }(); var SUPPORT_TOUCH = ("ontouchstart" in win); var SUPPORT_POINTER = ("PointerEvent" in win); var SUPPORT_MSPOINTER = ("MSPointerEvent" in win); var SUPPORT_POINTER_EVENTS = SUPPORT_POINTER || SUPPORT_MSPOINTER; var isValidKey = function (event, inputKey) { if (!inputKey || inputKey.indexOf(ANY) > -1 || inputKey.indexOf(NONE) > -1 && !event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey || inputKey.indexOf(SHIFT) > -1 && event.shiftKey || inputKey.indexOf(CTRL) > -1 && event.ctrlKey || inputKey.indexOf(ALT) > -1 && event.altKey || inputKey.indexOf(META) > -1 && event.metaKey) { return true; } return false; }; var EventInput = /*#__PURE__*/function () { function EventInput() { var _this = this; this._stopContextMenu = function (event) { event.preventDefault(); win.removeEventListener("contextmenu", _this._stopContextMenu); }; } var __proto = EventInput.prototype; __proto.extendEvent = function (event) { var _a; var prevEvent = this.prevEvent; var center = this._getCenter(event); var movement = prevEvent ? this._getMovement(event) : { x: 0, y: 0 }; var scale = prevEvent ? this._getScale(event) : 1; var angle = prevEvent ? getAngle(center.x - prevEvent.center.x, center.y - prevEvent.center.y) : 0; var deltaX = prevEvent ? prevEvent.deltaX + movement.x : movement.x; var deltaY = prevEvent ? prevEvent.deltaY + movement.y : movement.y; var offsetX = movement.x; var offsetY = movement.y; var latestInterval = this._latestInterval; var timeStamp = Date.now(); var deltaTime = latestInterval ? timeStamp - latestInterval.timestamp : 0; var velocityX = prevEvent ? prevEvent.velocityX : 0; var velocityY = prevEvent ? prevEvent.velocityY : 0; var directionX = prevEvent ? prevEvent.directionX : 1; var directionY = prevEvent ? prevEvent.directionY : 1; // If offset is 0, it inherits the direction of the previous event. if (offsetX > 0) { directionX = 1; } else if (offsetX < 0) { directionX = -1; } if (offsetY > 0) { directionY = 1; } else if (offsetY < 0) { directionY = -1; } if (!latestInterval || deltaTime >= VELOCITY_INTERVAL) { if (latestInterval) { _a = [(deltaX - latestInterval.deltaX) / deltaTime, (deltaY - latestInterval.deltaY) / deltaTime], velocityX = _a[0], velocityY = _a[1]; } this._latestInterval = { timestamp: timeStamp, deltaX: deltaX, deltaY: deltaY }; } return { srcEvent: event, scale: scale, angle: angle, center: center, deltaX: deltaX, deltaY: deltaY, offsetX: offsetX, offsetY: offsetY, directionX: directionX, directionY: directionY, velocityX: velocityX, velocityY: velocityY, preventSystemEvent: true }; }; __proto._getDistance = function (start, end) { var x = end.clientX - start.clientX; var y = end.clientY - start.clientY; return Math.sqrt(x * x + y * y); }; __proto._getButton = function (event) { var buttonCodeMap = { 1: MOUSE_LEFT, 2: MOUSE_RIGHT, 4: MOUSE_MIDDLE }; var button = this._isTouchEvent(event) ? MOUSE_LEFT : buttonCodeMap[event.buttons]; return button ? button : null; }; __proto._isTouchEvent = function (event) { return event.type && event.type.indexOf("touch") > -1; }; __proto._isValidButton = function (button, inputButton) { return inputButton.indexOf(button) > -1; }; __proto._isValidEvent = function (event, inputKey, inputButton) { return (!inputKey || isValidKey(event, inputKey)) && (!inputButton || this._isValidButton(this._getButton(event), inputButton)); }; __proto._preventMouseButton = function (event, button) { if (button === MOUSE_RIGHT) { win.addEventListener("contextmenu", this._stopContextMenu); } else if (button === MOUSE_MIDDLE) { event.preventDefault(); } }; return EventInput; }(); var MouseEventInput = /*#__PURE__*/function (_super) { __extends(MouseEventInput, _super); function MouseEventInput() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.start = ["mousedown"]; _this.move = ["mousemove"]; _this.end = ["mouseup"]; return _this; } var __proto = MouseEventInput.prototype; __proto.onEventStart = function (event, inputKey, inputButton) { var button = this._getButton(event); if (!this._isValidEvent(event, inputKey, inputButton)) { return null; } this._preventMouseButton(event, button); return this.extendEvent(event); }; __proto.onEventMove = function (event, inputKey, inputButton) { if (!this._isValidEvent(event, inputKey, inputButton)) { return null; } return this.extendEvent(event); }; __proto.onEventEnd = function () { return; }; __proto.onRelease = function () { this.prevEvent = null; return; }; __proto.getTouches = function (event, inputButton) { if (inputButton) { return this._isValidButton(MOUSE_BUTTON_CODE_MAP[event.which], inputButton) && this.end.indexOf(event.type) === -1 ? 1 : 0; } return 0; }; __proto._getScale = function () { return 1; }; __proto._getCenter = function (event) { return { x: event.clientX, y: event.clientY }; }; __proto._getMovement = function (event) { var prev = this.prevEvent.srcEvent; return { x: event.clientX - prev.clientX, y: event.clientY - prev.clientY }; }; return MouseEventInput; }(EventInput); var TouchEventInput = /*#__PURE__*/function (_super) { __extends(TouchEventInput, _super); function TouchEventInput() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.start = ["touchstart"]; _this.move = ["touchmove"]; _this.end = ["touchend", "touchcancel"]; return _this; } var __proto = TouchEventInput.prototype; __proto.onEventStart = function (event, inputKey) { this._baseTouches = event.touches; if (!this._isValidEvent(event, inputKey)) { return null; } return this.extendEvent(event); }; __proto.onEventMove = function (event, inputKey) { if (!this._isValidEvent(event, inputKey)) { return null; } return this.extendEvent(event); }; __proto.onEventEnd = function (event) { this._baseTouches = event.touches; return; }; __proto.onRelease = function () { this.prevEvent = null; this._baseTouches = null; return; }; __proto.getTouches = function (event) { return event.touches.length; }; __proto._getScale = function (event) { if (event.touches.length !== 2 || this._baseTouches.length < 2) { return null; // TODO: consider calculating non-pinch gesture scale } return this._getDistance(event.touches[0], event.touches[1]) / this._getDistance(this._baseTouches[0], this._baseTouches[1]); }; __proto._getCenter = function (event) { return { x: event.touches[0].clientX, y: event.touches[0].clientY }; }; __proto._getMovement = function (event) { var prev = this.prevEvent.srcEvent; if (event.touches[0].identifier !== prev.touches[0].identifier) { return { x: 0, y: 0 }; } return { x: event.touches[0].clientX - prev.touches[0].clientX, y: event.touches[0].clientY - prev.touches[0].clientY }; }; return TouchEventInput; }(EventInput); var PointerEventInput = /*#__PURE__*/function (_super) { __extends(PointerEventInput, _super); function PointerEventInput() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.start = SUPPORT_POINTER ? ["pointerdown"] : ["MSPointerDown"]; _this.move = SUPPORT_POINTER ? ["pointermove"] : ["MSPointerMove"]; _this.end = SUPPORT_POINTER ? ["pointerup", "pointercancel"] : ["MSPointerUp", "MSPointerCancel"]; // store first, recent inputs for each event id _this._firstInputs = []; _this._recentInputs = []; return _this; } var __proto = PointerEventInput.prototype; __proto.onEventStart = function (event, inputKey, inputButton) { var button = this._getButton(event); if (!this._isValidEvent(event, inputKey, inputButton)) { return null; } this._preventMouseButton(event, button); this._updatePointerEvent(event); return this.extendEvent(event); }; __proto.onEventMove = function (event, inputKey, inputButton) { if (!this._isValidEvent(event, inputKey, inputButton)) { return null; } this._updatePointerEvent(event); return this.extendEvent(event); }; __proto.onEventEnd = function (event) { this._removePointerEvent(event); }; __proto.onRelease = function () { this.prevEvent = null; this._firstInputs = []; this._recentInputs = []; return; }; __proto.getTouches = function () { return this._recentInputs.length; }; __proto._getScale = function () { if (this._recentInputs.length !== 2) { return null; // TODO: consider calculating non-pinch gesture scale } return this._getDistance(this._recentInputs[0], this._recentInputs[1]) / this._getDistance(this._firstInputs[0], this._firstInputs[1]); }; __proto._getCenter = function (event) { return { x: event.clientX, y: event.clientY }; }; __proto._getMovement = function (event) { var prev = this.prevEvent.srcEvent; if (event.pointerId !== prev.pointerId) { return { x: 0, y: 0 }; } return { x: event.clientX - prev.clientX, y: event.clientY - prev.clientY }; }; __proto._updatePointerEvent = function (event) { var _this = this; var addFlag = false; this._recentInputs.forEach(function (e, i) { if (e.pointerId === event.pointerId) { addFlag = true; _this._recentInputs[i] = event; } }); if (!addFlag) { this._firstInputs.push(event); this._recentInputs.push(event); } }; __proto._removePointerEvent = function (event) { this._firstInputs = this._firstInputs.filter(function (x) { return x.pointerId !== event.pointerId; }); this._recentInputs = this._recentInputs.filter(function (x) { return x.pointerId !== event.pointerId; }); }; return PointerEventInput; }(EventInput); var TouchMouseEventInput = /*#__PURE__*/function (_super) { __extends(TouchMouseEventInput, _super); function TouchMouseEventInput() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.start = ["mousedown", "touchstart"]; _this.move = ["mousemove", "touchmove"]; _this.end = ["mouseup", "touchend", "touchcancel"]; return _this; } var __proto = TouchMouseEventInput.prototype; __proto.onEventStart = function (event, inputKey, inputButton) { var button = this._getButton(event); if (this._isTouchEvent(event)) { this._baseTouches = event.touches; } if (!this._isValidEvent(event, inputKey, inputButton)) { return null; } this._preventMouseButton(event, button); return this.extendEvent(event); }; __proto.onEventMove = function (event, inputKey, inputButton) { if (!this._isValidEvent(event, inputKey, inputButton)) { return null; } return this.extendEvent(event); }; __proto.onEventEnd = function (event) { if (this._isTouchEvent(event)) { this._baseTouches = event.touches; } return; }; __proto.onRelease = function () { this.prevEvent = null; this._baseTouches = null; return; }; __proto.getTouches = function (event, inputButton) { if (this._isTouchEvent(event)) { return event.touches.length; } else { return this._isValidButton(MOUSE_BUTTON_CODE_MAP[event.which], inputButton) && this.end.indexOf(event.type) === -1 ? 1 : 0; } }; __proto._getScale = function (event) { if (this._isTouchEvent(event)) { if (event.touches.length !== 2 || this._baseTouches.length < 2) { return 1; // TODO: consider calculating non-pinch gesture scale } return this._getDistance(event.touches[0], event.touches[1]) / this._getDistance(this._baseTouches[0], this._baseTouches[1]); } return this.prevEvent.scale; }; __proto._getCenter = function (event) { if (this._isTouchEvent(event)) { return { x: event.touches[0].clientX, y: event.touches[0].clientY }; } return { x: event.clientX, y: event.clientY }; }; __proto._getMovement = function (event) { var _this = this; var prev = this.prevEvent.srcEvent; var _a = [event, prev].map(function (e) { if (_this._isTouchEvent(e)) { return { id: e.touches[0].identifier, x: e.touches[0].clientX, y: e.touches[0].clientY }; } return { id: null, x: e.clientX, y: e.clientY }; }), nextSpot = _a[0], prevSpot = _a[1]; return nextSpot.id === prevSpot.id ? { x: nextSpot.x - prevSpot.x, y: nextSpot.y - prevSpot.y } : { x: 0, y: 0 }; }; return TouchMouseEventInput; }(EventInput); var toAxis = function (source, offset) { return offset.reduce(function (acc, v, i) { if (source[i]) { acc[source[i]] = v; } return acc; }, {}); }; var convertInputType = function (inputType) { if (inputType === void 0) { inputType = []; } var hasTouch = false; var hasMouse = false; var hasPointer = false; inputType.forEach(function (v) { switch (v) { case "mouse": hasMouse = true; break; case "touch": hasTouch = SUPPORT_TOUCH; break; case "pointer": hasPointer = SUPPORT_POINTER_EVENTS; // no default } }); if (hasPointer) { return new PointerEventInput(); } else if (hasTouch && hasMouse) { return new TouchMouseEventInput(); } else if (hasTouch) { return new TouchEventInput(); } else if (hasMouse) { return new MouseEventInput(); } return null; }; function getAddEventOptions(eventName) { // The passive default value of the touch event is true. // If not a touch event, return false to support ie11 return eventName.indexOf("touch") > -1 ? { passive: false } : false; } var InputObserver = /*#__PURE__*/function () { function InputObserver(_a) { var options = _a.options, interruptManager = _a.interruptManager, eventManager = _a.eventManager, axisManager = _a.axisManager, animationManager = _a.animationManager; this._isOutside = false; this._moveDistance = null; this._isStopped = false; this.options = options; this._interruptManager = interruptManager; this._eventManager = eventManager; this._axisManager = axisManager; this._animationManager = animationManager; } var __proto = InputObserver.prototype; __proto.get = function (input) { return this._axisManager.get(input.axes); }; __proto.hold = function (input, event) { if (this._interruptManager.isInterrupted() || !input.axes.length) { return; } var changeOption = { input: input, event: event }; this._isStopped = false; this._interruptManager.setInterrupt(true); this._animationManager.stopAnimation(changeOption); ++this._eventManager.holdingCount; if (!this._moveDistance) { this._eventManager.hold(this._axisManager.get(), changeOption); } this._isOutside = this._axisManager.isOutside(input.axes); this._moveDistance = this._axisManager.get(input.axes); }; __proto.change = function (input, event, offset, useAnimation) { if (this._isStopped || !this._interruptManager.isInterrupting() || this._axisManager.every(offset, function (v) { return v === 0; })) { return; } var nativeEvent = event.srcEvent ? event.srcEvent : event; if (nativeEvent.__childrenAxesAlreadyChanged) { return; } var depaPos = this._moveDistance || this._axisManager.get(input.axes); var destPos; // for outside logic destPos = map(depaPos, function (v, k) { return v + (offset[k] || 0); }); if (this._moveDistance) { this._moveDistance = this._axisManager.map(destPos, function (v, _a) { var circular = _a.circular, range = _a.range; return circular && (circular[0] || circular[1]) ? getCirculatedPos(v, range, circular) : v; }); } // from outside to inside if (this._isOutside && this._axisManager.every(depaPos, function (v, opt) { return !isOutside(v, opt.range); })) { this._isOutside = false; } depaPos = this._atOutside(depaPos); destPos = this._atOutside(destPos); if (!this.options.nested || !this._isEndofAxis(offset, depaPos, destPos)) { nativeEvent.__childrenAxesAlreadyChanged = true; } var changeOption = { input: input, event: event }; if (useAnimation) { var duration = this._animationManager.getDuration(destPos, depaPos); this._animationManager.animateTo(destPos, duration, changeOption); } else { var isCanceled = !this._eventManager.triggerChange(destPos, depaPos, changeOption, true); if (isCanceled) { this._isStopped = true; this._moveDistance = null; this._animationManager.finish(false); } } }; __proto.release = function (input, event, velocity, inputDuration) { if (this._isStopped || !this._interruptManager.isInterrupting() || !this._moveDistance) { return; } var nativeEvent = event.srcEvent ? event.srcEvent : event; if (nativeEvent.__childrenAxesAlreadyReleased) { velocity = velocity.map(function () { return 0; }); } var pos = this._axisManager.get(input.axes); var depaPos = this._axisManager.get(); var displacement = this._animationManager.getDisplacement(velocity); var offset = toAxis(input.axes, displacement); var destPos = this._axisManager.get(this._axisManager.map(offset, function (v, opt, k) { if (opt.circular && (opt.circular[0] || opt.circular[1])) { return pos[k] + v; } else { return getInsidePosition(pos[k] + v, opt.range, opt.circular, opt.bounce); } })); nativeEvent.__childrenAxesAlreadyReleased = true; var duration = this._animationManager.getDuration(destPos, pos, inputDuration); if (duration === 0) { destPos = __assign({}, depaPos); } // prepare params var param = { depaPos: depaPos, destPos: destPos, duration: duration, delta: this._axisManager.getDelta(depaPos, destPos), inputEvent: event, input: input, isTrusted: true }; --this._eventManager.holdingCount; this._eventManager.triggerRelease(param); if (this._eventManager.holdingCount === 0) { this._moveDistance = null; } // to contol var userWish = this._animationManager.getUserControl(param); var isEqual = equal(userWish.destPos, depaPos); var changeOption = { input: input, event: event