/*
* Copyright (c) 2015 NAVER Corp.
* egjs projects are licensed under the MIT license
*/
import { $, getDirection, useDirection } from "../utils";
import { ANY, DIRECTION_HORIZONTAL, DIRECTION_VERTICAL } from "../const";
import { ElementType } from "../types";
import { toAxis, InputType, InputTypeObserver } from "./InputType";
import { isValidKey } from "../eventInput/EventInput";
export interface WheelInputOption {
inputKey?: string[];
scale?: number;
releaseDelay?: number;
useNormalized?: boolean;
useAnimation?: boolean;
}
/**
* @typedef {Object} WheelInputOption The option object of the eg.Axes.WheelInput module
* @ko eg.Axes.WheelInput 모듈의 옵션 객체
* @param {String[]} [inputKey=["any"]] List of key combinations to allow input
* - any: any key
* - shift: shift key
* - ctrl: ctrl key and pinch gesture on the trackpad
* - alt: alt key
* - meta: meta key
* - none: none of these keys are pressed 입력을 허용할 키 조합 목록
* - any: 아무 키
* - shift: shift 키
* - ctrl: ctrl 키 및 트랙패드의 pinch 제스쳐
* - alt: alt 키
* - meta: meta 키
* - none: 아무 키도 눌리지 않은 상태
* @param {Number} [scale=1] Coordinate scale that a user can move사용자의 동작으로 이동하는 좌표의 배율
* @param {Number} [releaseDelay=300] Millisecond that trigger release event after last input마지막 입력 이후 release 이벤트가 트리거되기까지의 밀리초
* @param {Boolean} [useNormalized=true] Whether to calculate scroll speed the same in all browsers모든 브라우저에서 스크롤 속도를 동일하게 처리할지 여부
* @param {Boolean} [useAnimation=false] Whether to process coordinate changes through the mouse wheel as a continuous animation마우스 휠을 통한 좌표 변화를 연속적인 애니메이션으로 처리할지 여부
**/
/**
* A module that passes the amount of change to eg.Axes when the mouse wheel is moved. use one axis.
* @ko 마우스 휠이 움직일때의 변화량을 eg.Axes에 전달하는 모듈. 두개 이하의 축을 사용한다.
*
* @example
* ```js
* const wheel = new eg.Axes.WheelInput("#area", {
* scale: 1
* });
*
* // Connect only one 'something1' axis to the vertical mouse wheel.
* axes.connect(["something1"], wheel); // or axes.connect("something1", wheel);
*
* // Connect only one 'something2' axis to the horizontal mouse wheel.
* axes.connect(["", "something2"], wheel); // or axes.connect(" something2", pan);
*
* // Connect the 'something1' axis to the vertical mouse wheel.
* // Connect the 'something2' axis to the horizontal mouse wheel.
* axes.connect(["something1", "something2"], wheel);
* ```
* @param {HTMLElement|String|jQuery} element An element to use the eg.Axes.WheelInput module eg.Axes.WheelInput 모듈을 사용할 엘리먼트
* @param {WheelInputOption} [options] The option object of the eg.Axes.WheelInput moduleeg.Axes.WheelInput 모듈의 옵션 객체
*/
export class WheelInput implements InputType {
public options: WheelInputOption;
public axes: string[] = [];
public element: HTMLElement = null;
private _observer: InputTypeObserver;
private _direction: number;
private _enabled = false;
private _holding = false;
private _timer: NodeJS.Timeout = null;
/**
*
*/
public constructor(el: ElementType, options?: WheelInputOption) {
this.element = $(el);
this.options = {
inputKey: [ANY],
scale: 1,
releaseDelay: 300,
useNormalized: true,
useAnimation: false,
...options,
};
this._onWheel = this._onWheel.bind(this);
}
public mapAxes(axes: string[]) {
// vertical mouse wheel is mapped into axes[0]
this._direction = getDirection(!!axes[1], !!axes[0]);
this.axes = axes;
}
public connect(observer: InputTypeObserver): InputType {
this._detachEvent();
this._attachEvent(observer);
return this;
}
public disconnect() {
this._detachEvent();
return this;
}
/**
* Destroys elements, properties, and events used in a module.
* @ko 모듈에 사용한 엘리먼트와 속성, 이벤트를 해제한다.
*/
public destroy() {
this.disconnect();
this.element = null;
}
/**
* Enables input devices
* @ko 입력 장치를 사용할 수 있게 한다
* @return {WheelInput} An instance of a module itself 모듈 자신의 인스턴스
*/
public enable() {
this._enabled = true;
return this;
}
/**
* Disables input devices
* @ko 입력 장치를 사용할 수 없게 한다.
* @return {WheelInput} An instance of a module itself 모듈 자신의 인스턴스
*/
public disable() {
this._enabled = false;
return this;
}
/**
* Returns whether to use an input device
* @ko 입력 장치를 사용 여부를 반환한다.
* @return {Boolean} Whether to use an input device 입력장치 사용여부
*/
public isEnabled() {
return this._enabled;
}
private _onWheel(event: WheelEvent) {
if (!this._enabled || !isValidKey(event, this.options.inputKey)) {
return;
}
const offset = this._getOffset(
[event.deltaY, event.deltaX],
[
useDirection(DIRECTION_VERTICAL, this._direction),
useDirection(DIRECTION_HORIZONTAL, this._direction),
]
);
if (offset[0] === 0 && offset[1] === 0) {
return;
}
event.preventDefault();
if (!this._holding) {
this._observer.hold(this, event);
this._holding = true;
}
this._observer.change(
this,
event,
toAxis(this.axes, offset),
this.options.useAnimation
);
clearTimeout(this._timer);
this._timer = setTimeout(() => {
if (this._holding) {
this._holding = false;
this._observer.release(this, event, [0]);
}
}, this.options.releaseDelay);
}
private _getOffset(properties: number[], direction: boolean[]): number[] {
const scale = this.options.scale;
const useNormalized = this.options.useNormalized;
return [
direction[0] && properties[0]
? (properties[0] > 0 ? -1 : 1) *
(useNormalized ? 1 : Math.abs(properties[0])) *
scale
: 0,
direction[1] && properties[1]
? (properties[1] > 0 ? -1 : 1) *
(useNormalized ? 1 : Math.abs(properties[1])) *
scale
: 0,
];
}
private _attachEvent(observer: InputTypeObserver) {
const element = this.element;
if (!element) {
throw new Error("Element to connect input does not exist.");
}
this._observer = observer;
element.addEventListener("wheel", this._onWheel);
this._enabled = true;
}
private _detachEvent() {
const element = this.element;
if (element) {
this.element.removeEventListener("wheel", this._onWheel);
}
this._enabled = false;
this._observer = null;
if (this._timer) {
clearTimeout(this._timer);
this._timer = null;
}
}
}