diff --git a/packages/axes/src/Axes.ts b/packages/axes/src/Axes.ts index d3364538..09fd7b18 100644 --- a/packages/axes/src/Axes.ts +++ b/packages/axes/src/Axes.ts @@ -47,13 +47,13 @@ export interface AxesOption { * @param {Number[]} [range] The coordinate of range 좌표 범위 * @param {Number} [range[0]=0] The coordinate of the minimum 최소 좌표 * @param {Number} [range[1]=0] The coordinate of the maximum 최대 좌표 + * @param {Number} [startPos=range[0]] The coordinates to be moved when creating an instance 인스턴스 생성시 이동할 좌표 * @param {Number[]} [bounce] The size of bouncing area. The coordinates can exceed the coordinate area as much as the bouncing area based on user action. If the coordinates does not exceed the bouncing area when an element is dragged, the coordinates where bouncing effects are applied are retuned back into the coordinate area바운스 영역의 크기. 사용자의 동작에 따라 좌표가 좌표 영역을 넘어 바운스 영역의 크기만큼 더 이동할 수 있다. 사용자가 끌어다 놓는 동작을 했을 때 좌표가 바운스 영역에 있으면, 바운스 효과가 적용된 좌표가 다시 좌표 영역 안으로 들어온다 * @param {Number} [bounce[0]=0] The size of coordinate of the minimum area 최소 좌표 바운스 영역의 크기 * @param {Number} [bounce[1]=0] The size of coordinate of the maximum area 최대 좌표 바운스 영역의 크기 * @param {Boolean[]} [circular] Indicates whether a circular element is available. If it is set to "true" and an element is dragged outside the coordinate area, the element will appear on the other side.순환 여부. 'true'로 설정한 방향의 좌표 영역 밖으로 엘리먼트가 이동하면 반대 방향에서 엘리먼트가 나타난다 * @param {Boolean} [circular[0]=false] Indicates whether to circulate to the coordinate of the minimum 최소 좌표 방향의 순환 여부 * @param {Boolean} [circular[1]=false] Indicates whether to circulate to the coordinate of the maximum 최대 좌표 방향의 순환 여부 - * @param {Number} [startPos=range[0]] The coordinates to be moved when creating an instance 인스턴스 생성시 이동할 좌표 **/ /** diff --git a/packages/axes/src/AxisManager.ts b/packages/axes/src/AxisManager.ts index 4f9070af..1b830136 100644 --- a/packages/axes/src/AxisManager.ts +++ b/packages/axes/src/AxisManager.ts @@ -118,9 +118,9 @@ export class AxisManager { this._axis[axis] = { ...{ range: [0, 100], + startPos: this._axis[axis].range[0], bounce: [0, 0], circular: [false, false], - startPos: this._axis[axis].range[0], }, ...this._axis[axis], }; diff --git a/packages/axes/src/inputType/PanInput.ts b/packages/axes/src/inputType/PanInput.ts index 4b75c554..277c8da3 100644 --- a/packages/axes/src/inputType/PanInput.ts +++ b/packages/axes/src/inputType/PanInput.ts @@ -87,8 +87,8 @@ export const getDirectionByAngle = ( * @example * ```js * const pan = new eg.Axes.PanInput("#area", { - * inputType: ["touch"], - * scale: [1, 1.3], + * inputType: ["touch"], + * scale: [1, 1.3], * }); * * // Connect the 'something2' axis to the mouse or touchscreen x position when the mouse or touchscreen is down and moved. @@ -197,20 +197,33 @@ export class PanInput implements InputType { /** * Returns whether to use an input device - * @ko 입력 장치를 사용 여부를 반환한다. + * @ko 입력 장치 사용 여부를 반환한다. * @return {Boolean} Whether to use an input device 입력장치 사용여부 */ public isEnabled() { return this._enabled; } + /** + * Releases current user input. + * @ko 사용자의 입력을 강제로 중단시킨다. + * @return {PanInput} An instance of a module itself 모듈 자신의 인스턴스 + */ + public release() { + const activeEvent = this._activeEvent; + const prevEvent = activeEvent.prevEvent; + activeEvent.onRelease(); + this._observer.release(this, prevEvent, [0, 0]); + this._detachWindowEvent(activeEvent); + return this; + } + protected _onPanstart(event: InputEventType) { const activeEvent = this._activeEvent; const panEvent = activeEvent.onEventStart(event, this.options.inputButton); if (!panEvent || !this._enabled || activeEvent.getTouches(event) > 1) { return; } - if (panEvent.srcEvent.cancelable !== false) { const edgeThreshold = this.options.iOSEdgeSwipeThreshold; @@ -245,7 +258,7 @@ export class PanInput implements InputType { if (swipeLeftToRight) { // iOS swipe left => right - this._forceRelease(); + this.release(); return; } else if (this._atRightEdge) { clearTimeout(this._rightEdgeTimer); @@ -257,10 +270,7 @@ export class PanInput implements InputType { this._atRightEdge = false; } else { // iOS swipe right => left - this._rightEdgeTimer = window.setTimeout( - () => this._forceRelease(), - 100 - ); + this._rightEdgeTimer = window.setTimeout(() => this.release(), 100); } } } @@ -364,13 +374,5 @@ export class PanInput implements InputType { this._observer = null; } - private _forceRelease = () => { - const activeEvent = this._activeEvent; - const prevEvent = activeEvent.prevEvent; - activeEvent.onRelease(); - this._observer.release(this, prevEvent, [0, 0]); - this._detachWindowEvent(activeEvent); - }; - private _voidFunction = () => {}; } diff --git a/packages/axes/test/unit/inputType/PanInput.spec.js b/packages/axes/test/unit/inputType/PanInput.spec.js index a7201d10..143f6726 100644 --- a/packages/axes/test/unit/inputType/PanInput.spec.js +++ b/packages/axes/test/unit/inputType/PanInput.spec.js @@ -13,7 +13,7 @@ describe("PanInput", () => { let inst; let observer; - describe("instance method", () => { + describe("Methods", () => { beforeEach(() => { inst = new PanInput(sandbox()); }); @@ -24,246 +24,171 @@ describe("PanInput", () => { } cleanup(); }); - it("should check 'mapAxes' method", () => { - // when - inst.mapAxes(["x"]); - // then - expect(inst.axes).to.be.eql(["x"]); - expect(inst._direction).to.be.equal(DIRECTION_HORIZONTAL); + describe("mapAxes", () => { + it("should initialize correct axes name with its direction", () => { + // when + inst.mapAxes(["x"]); - // when - inst.mapAxes(["", "y"]); + // then + expect(inst.axes).to.be.eql(["x"]); + expect(inst._direction).to.be.equal(DIRECTION_HORIZONTAL); - // then - expect(inst.axes).to.be.eql(["", "y"]); - expect(inst._direction).to.be.equal(DIRECTION_VERTICAL); + // when + inst.mapAxes(["", "y"]); - // when - inst.mapAxes(["x", "y"]); + // then + expect(inst.axes).to.be.eql(["", "y"]); + expect(inst._direction).to.be.equal(DIRECTION_VERTICAL); - // then - expect(inst.axes).to.be.eql(["x", "y"]); - expect(inst._direction).to.be.equal(DIRECTION_ALL); + // when + inst.mapAxes(["x", "y"]); - // when - inst.mapAxes(["x", "y", "z"]); + // then + expect(inst.axes).to.be.eql(["x", "y"]); + expect(inst._direction).to.be.equal(DIRECTION_ALL); - // then - expect(inst.axes).to.be.eql(["x", "y", "z"]); - expect(inst._direction).to.be.equal(DIRECTION_ALL); - }); - it("should check status after disconnect", () => { - // Given - inst.connect({}); - - // When - inst.disconnect(); + // when + inst.mapAxes(["x", "y", "z"]); - // Then - expect(observer).to.be.not.exist; - expect(inst.element).to.be.exist; - expect(inst._direction).to.be.equal(DIRECTION_NONE); + // then + expect(inst.axes).to.be.eql(["x", "y", "z"]); + expect(inst._direction).to.be.equal(DIRECTION_ALL); + }); }); - it("should check status after destroy", () => { - // Given - inst.connect({}); - const beforeEl = inst.element; - // When - inst.destroy(); - - // Then - expect(inst.element).to.be.not.exist; - expect(observer).to.be.not.exist; - expect(inst._direction).to.be.equal(DIRECTION_NONE); + describe("disconnect", () => { + it("should check status is completely empty after disconnect", () => { + // Given + inst.connect({}); - inst = null; - }); - }); + // When + inst.disconnect(); - describe("enable/disable", () => { - beforeEach(() => { - el = sandbox(); - input = new PanInput(el, { - inputType: ["touch", "mouse"], - }); - inst = new Axes({ - x: { - range: [0, 200], - }, - y: { - range: [0, 200], - }, + // Then + expect(observer).to.be.not.exist; + expect(inst.element).to.be.exist; + expect(inst._direction).to.be.equal(DIRECTION_NONE); }); }); - afterEach(() => { - if (inst) { - inst.destroy(); - inst = null; - } - if (input) { - input.destroy(); - input = null; - } - cleanup(); - }); - - it("should check value of `enable/disable` methods", () => { - // Given - // When - // Then - expect(input.isEnabled()).to.be.false; - // When - input.enable(); + describe("destroy", () => { + it("should check status is completely empty after destroy", () => { + // Given + inst.connect({}); - // Then - expect(input.isEnabled()).to.be.true; + // When + inst.destroy(); - // When - input.disable(); + // Then + expect(inst.element).to.be.not.exist; + expect(observer).to.be.not.exist; + expect(inst._direction).to.be.equal(DIRECTION_NONE); - // Then - expect(input.isEnabled()).to.be.false; + inst = null; + }); }); - it("should check event when enable method is called", (done) => { - // Given - const hold = sinon.spy(); - const change = sinon.spy(); - const release = sinon.spy(); - inst.connect(["x", "y"], input); - inst.on("hold", hold); - inst.on("change", change); - inst.on("release", release); - - // When - expect(input.isEnabled()).to.be.true; - - // When - Simulator.gestures.pan( - el, - { + + describe("release", () => { + it("should release current input when method is called", (done) => { + // Given + el = sandbox(); + input = new PanInput(el, { + inputType: ["touch", "mouse"], + }); + inst = new Axes({ + x: { + range: [0, 200], + }, + }); + const hold = sinon.spy(); + const release = sinon.spy(); + inst.connect(["x", ""], input); + inst.on("hold", hold); + inst.on("release", release); + + // When + Simulator.gestures.pan(el, { pos: [0, 0], - deltaX: 50, - deltaY: 50, - duration: 200, + deltaX: 100, + duration: 2000, easing: "linear", - }, - () => { - // Then + }); + setTimeout(() => { + input.release(); + }, 1000); + + // Then + setTimeout(() => { expect(hold.calledOnce).to.be.true; - expect(change.called).to.be.true; expect(release.calledOnce).to.be.true; + }, 1500); + setTimeout(() => { + expect(inst.get().x).to.be.lessThan(50); done(); - } - ); + }, 2000); + }); }); - it("should check event when disable method is called", (done) => { - // Given - const hold = sinon.spy(); - const change = sinon.spy(); - const release = sinon.spy(); - inst.connect(["x", "y"], input); - inst.on("hold", hold); - inst.on("change", change); - inst.on("release", release); - - // When - expect(input.isEnabled()).to.be.true; - input.disable(); - - // When - Simulator.gestures.pan( - el, - { - pos: [0, 0], - deltaX: 50, - deltaY: 50, - duration: 200, - easing: "linear", - }, - () => { - // Then - expect(hold.called).to.be.false; - expect(change.called).to.be.false; - expect(release.called).to.be.false; - done(); + + describe("enable/disable", () => { + beforeEach(() => { + el = sandbox(); + input = new PanInput(el, { + inputType: ["touch", "mouse"], + }); + inst = new Axes({ + x: { + range: [0, 200], + }, + y: { + range: [0, 200], + }, + }); + }); + afterEach(() => { + if (inst) { + inst.destroy(); + inst = null; } - ); - }); - }); + if (input) { + input.destroy(); + input = null; + } + cleanup(); + }); - describe("static method", () => { - it("should check user's direction", () => { - // Given - // When thresholdAngle = 45 - // Then - expect(getDirectionByAngle(0, 45)).to.be.equal(DIRECTION_HORIZONTAL); - expect(getDirectionByAngle(20, 45)).to.be.equal(DIRECTION_HORIZONTAL); - expect(getDirectionByAngle(45, 45)).to.be.equal(DIRECTION_HORIZONTAL); - expect(getDirectionByAngle(100, 45)).to.be.equal(DIRECTION_VERTICAL); - expect(getDirectionByAngle(134, 45)).to.be.equal(DIRECTION_VERTICAL); - expect(getDirectionByAngle(135, 45)).to.be.equal(DIRECTION_HORIZONTAL); - expect(getDirectionByAngle(136, 45)).to.be.equal(DIRECTION_HORIZONTAL); - expect(getDirectionByAngle(180, 45)).to.be.equal(DIRECTION_HORIZONTAL); - - // When thresholdAngle = 20 - // Then - expect(getDirectionByAngle(0, 20)).to.be.equal(DIRECTION_HORIZONTAL); - expect(getDirectionByAngle(10, 20)).to.be.equal(DIRECTION_HORIZONTAL); - expect(getDirectionByAngle(20, 20)).to.be.equal(DIRECTION_HORIZONTAL); - expect(getDirectionByAngle(30, 20)).to.be.equal(DIRECTION_VERTICAL); - expect(getDirectionByAngle(50, 20)).to.be.equal(DIRECTION_VERTICAL); - expect(getDirectionByAngle(160, 20)).to.be.equal(DIRECTION_HORIZONTAL); - expect(getDirectionByAngle(161, 20)).to.be.equal(DIRECTION_HORIZONTAL); - expect(getDirectionByAngle(180, 20)).to.be.equal(DIRECTION_HORIZONTAL); - - // When thresholdAngle = -10, 100 - expect(getDirectionByAngle(0, -10)).to.be.equal(DIRECTION_NONE); - expect(getDirectionByAngle(0, 100)).to.be.equal(DIRECTION_NONE); - }); - }); + it("should check value of `isEnabled` matches correctly after enable/disable", () => { + // Given + // When + // Then + expect(input.isEnabled()).to.be.false; - describe("Options", () => { - beforeEach(() => { - el = sandbox(); - inst = new Axes({ - x: { - range: [0, 200], - }, - y: { - range: [0, 200], - }, + // When + input.enable(); + + // Then + expect(input.isEnabled()).to.be.true; + + // When + input.disable(); + + // Then + expect(input.isEnabled()).to.be.false; }); - }); - afterEach(() => { - if (inst) { - inst.destroy(); - inst = null; - } - if (input) { - input.destroy(); - input = null; - } - cleanup(); - }); - ["left", "middle", "right"].forEach((button) => { - it("should check 'inputButton' option", (done) => { + it("should check events can be triggered after enable method is called", (done) => { // Given const hold = sinon.spy(); const change = sinon.spy(); const release = sinon.spy(); - input = new PanInput(el, { - inputType: ["touch", "mouse"], - inputButton: [button], - }); inst.connect(["x", "y"], input); inst.on("hold", hold); inst.on("change", change); inst.on("release", release); + // When + expect(input.isEnabled()).to.be.true; + // When Simulator.gestures.pan( el, @@ -276,29 +201,159 @@ describe("PanInput", () => { }, () => { // Then - expect(hold.called).to.be.equals(button === "left"); - expect(change.called).to.be.equals(button === "left"); - expect(release.called).to.be.equals(button === "left"); + expect(hold.calledOnce).to.be.true; + expect(change.called).to.be.true; + expect(release.calledOnce).to.be.true; + done(); + } + ); + }); + + it("should check events cannot be triggered after disable method is called", (done) => { + // Given + const hold = sinon.spy(); + const change = sinon.spy(); + const release = sinon.spy(); + inst.connect(["x", "y"], input); + inst.on("hold", hold); + inst.on("change", change); + inst.on("release", release); + + // When + expect(input.isEnabled()).to.be.true; + input.disable(); + + // When + Simulator.gestures.pan( + el, + { + pos: [0, 0], + deltaX: 50, + deltaY: 50, + duration: 200, + easing: "linear", + }, + () => { + // Then + expect(hold.called).to.be.false; + expect(change.called).to.be.false; + expect(release.called).to.be.false; done(); } ); }); }); - ["auto", "none", "manipulation", "pan-x", "pan-y"].forEach( - (touchAction) => { - it(`should check 'touchAction' option (${touchAction})`, () => { + describe("getDirectionByAngle", () => { + it("should check correct direction is calculated from the given angle", () => { + // Given + // When thresholdAngle = 45 + // Then + expect(getDirectionByAngle(0, 45)).to.be.equal(DIRECTION_HORIZONTAL); + expect(getDirectionByAngle(20, 45)).to.be.equal(DIRECTION_HORIZONTAL); + expect(getDirectionByAngle(45, 45)).to.be.equal(DIRECTION_HORIZONTAL); + expect(getDirectionByAngle(100, 45)).to.be.equal(DIRECTION_VERTICAL); + expect(getDirectionByAngle(134, 45)).to.be.equal(DIRECTION_VERTICAL); + expect(getDirectionByAngle(135, 45)).to.be.equal(DIRECTION_HORIZONTAL); + expect(getDirectionByAngle(136, 45)).to.be.equal(DIRECTION_HORIZONTAL); + expect(getDirectionByAngle(180, 45)).to.be.equal(DIRECTION_HORIZONTAL); + + // When thresholdAngle = 20 + // Then + expect(getDirectionByAngle(0, 20)).to.be.equal(DIRECTION_HORIZONTAL); + expect(getDirectionByAngle(10, 20)).to.be.equal(DIRECTION_HORIZONTAL); + expect(getDirectionByAngle(20, 20)).to.be.equal(DIRECTION_HORIZONTAL); + expect(getDirectionByAngle(30, 20)).to.be.equal(DIRECTION_VERTICAL); + expect(getDirectionByAngle(50, 20)).to.be.equal(DIRECTION_VERTICAL); + expect(getDirectionByAngle(160, 20)).to.be.equal(DIRECTION_HORIZONTAL); + expect(getDirectionByAngle(161, 20)).to.be.equal(DIRECTION_HORIZONTAL); + expect(getDirectionByAngle(180, 20)).to.be.equal(DIRECTION_HORIZONTAL); + + // When thresholdAngle = -10, 100 + expect(getDirectionByAngle(0, -10)).to.be.equal(DIRECTION_NONE); + expect(getDirectionByAngle(0, 100)).to.be.equal(DIRECTION_NONE); + }); + }); + }); + + describe("Options", () => { + beforeEach(() => { + el = sandbox(); + inst = new Axes({ + x: { + range: [0, 200], + }, + y: { + range: [0, 200], + }, + }); + }); + afterEach(() => { + if (inst) { + inst.destroy(); + inst = null; + } + if (input) { + input.destroy(); + input = null; + } + cleanup(); + }); + + describe("inputButton", () => { + ["left", "middle", "right"].forEach((button) => { + it("should check only the button set in inputButton is available", (done) => { // Given + const hold = sinon.spy(); + const change = sinon.spy(); + const release = sinon.spy(); input = new PanInput(el, { - touchAction, + inputType: ["touch", "mouse"], + inputButton: [button], }); inst.connect(["x", "y"], input); - - // Then - expect(el.style.touchAction).to.be.equal(touchAction); - expect(el.style.userSelect).to.be.equal("none"); + inst.on("hold", hold); + inst.on("change", change); + inst.on("release", release); + + // When + Simulator.gestures.pan( + el, + { + pos: [0, 0], + deltaX: 50, + deltaY: 50, + duration: 200, + easing: "linear", + }, + () => { + // Then + expect(hold.called).to.be.equals(button === "left"); + expect(change.called).to.be.equals(button === "left"); + expect(release.called).to.be.equals(button === "left"); + done(); + } + ); }); - } - ); + }); + }); + + describe("touchAction", () => { + ["auto", "none", "manipulation", "pan-x", "pan-y"].forEach( + (touchAction) => { + it(`should check whether the style set in touchAction is applied correctly (touchAction: ${touchAction})`, () => { + // Given + input = new PanInput(el, { + touchAction, + }); + inst.connect(["x", "y"], input); + + // Then + expect(el.style.touchAction).to.be.equal(touchAction); + expect(el.style.userSelect).to.be.equal("none"); + }); + } + ); + }); }); });