• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

naver / egjs-flicking / 10557177632

26 Aug 2024 09:22AM UTC coverage: 38.327% (-44.5%) from 82.855%
10557177632

Pull #886

github

daybrush
fix: recalculate camera offset
Pull Request #886: fix: recalculate camera offset

2039 of 7372 branches covered (27.66%)

Branch coverage included in aggregate %.

11 of 29 new or added lines in 2 files covered. (37.93%)

5575 existing lines in 46 files now uncovered.

5099 of 11252 relevant lines covered (45.32%)

10.91 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

54.85
/src/control/Control.ts
1
/*
1✔
2
 * Copyright (c) 2015 NAVER Corp.
1!
3
 * egjs projects are licensed under the MIT license
×
4
 */
5
import { OnRelease } from "@egjs/axes";
6
import { ComponentEvent } from "@egjs/component";
5!
7

8
import Flicking from "../Flicking";
9
import FlickingError from "../core/FlickingError";
6!
10
import Panel from "../core/panel/Panel";
11
import AxesController from "../control/AxesController";
5✔
12
import { DIRECTION, EVENTS } from "../const/external";
5✔
13
import * as ERROR from "../const/error";
5✔
14
import { getDirection, getFlickingAttached } from "../utils";
6✔
15
import { ValueOf } from "../type/internal";
1!
16

17
/**
×
18
 * A component that manages inputs and animation of Flicking
19
 * @ko Flicking의 입력 장치 & 애니메이션을 담당하는 컴포넌트
20
 */
21
abstract class Control {
6✔
22
  // Internal States
×
23
  protected _flicking: Flicking | null;
×
24
  protected _controller: AxesController;
25
  protected _activePanel: Panel | null;
26
  protected _nextPanel: Panel | null;
×
27

×
28
  /**
29
   * A controller that handles the {@link https://naver.github.io/egjs-axes/ @egjs/axes} events
30
   * @ko {@link https://naver.github.io/egjs-axes/ @egjs/axes}의 이벤트를 처리하는 컨트롤러 컴포넌트
1✔
31
   * @type {AxesController}
×
32
   * @readonly
×
33
   */
34
  public get controller() { return this._controller; }
18✔
35
  /**
×
36
   * Index number of the {@link Flicking#currentPanel currentPanel}
37
   * @ko {@link Flicking#currentPanel currentPanel}의 인덱스 번호
×
38
   * @type {number}
×
39
   * @default 0
×
40
   * @readonly
41
   */
42
  public get activeIndex() { return this._activePanel?.index ?? -1; }
5!
43
  /**
44
   * An active panel
45
   * @ko 현재 선택된 패널
×
46
   * @type {Panel | null}
×
47
   * @readonly
×
48
   */
×
49
  public get activePanel() { return this._activePanel; }
22!
50
  /**
51
   * Whether Flicking's animating
52
   * @ko 현재 애니메이션 동작 여부
53
   * @type {boolean}
54
   * @readonly
×
55
   */
56
  public get animating() { return this._controller.state.animating; }
13✔
57
  /**
1✔
58
   * Whether user is clicking or touching
2!
59
   * @ko 현재 사용자가 클릭/터치중인지 여부
60
   * @type {boolean}
1✔
61
   * @readonly
1✔
62
   */
1✔
63
  public get holding() { return this._controller.state.holding; }
7✔
64

1✔
65
  /** */
1✔
66
  public constructor() {
1✔
67
    this._flicking = null;
14✔
68
    this._controller = new AxesController();
14✔
69
    this._activePanel = null;
14✔
70
  }
71

1✔
72
  /**
73
   * Move {@link Camera} to the given position
74
   * @ko {@link Camera}를 주어진 좌표로 이동합니다
1✔
75
   * @method
1✔
76
   * @abstract
1✔
77
   * @memberof Control
78
   * @instance
1✔
79
   * @name moveToPosition
80
   * @param {number} position The target position to move<ko>이동할 좌표</ko>
81
   * @param {number} duration Duration of the panel movement animation (unit: ms).<ko>패널 이동 애니메이션 진행 시간 (단위: ms)</ko>
82
   * @param {object} [axesEvent] {@link https://naver.github.io/egjs-axes/release/latest/doc/eg.Axes.html#event:release release} event of {@link https://naver.github.io/egjs-axes/ Axes}
83
   * <ko>{@link https://naver.github.io/egjs-axes/ Axes}의 {@link https://naver.github.io/egjs-axes/release/latest/doc/eg.Axes.html#event:release release} 이벤트</ko>
84
   * @fires Flicking#moveStart
85
   * @fires Flicking#move
1✔
86
   * @fires Flicking#moveEnd
87
   * @fires Flicking#willChange
88
   * @fires Flicking#changed
89
   * @fires Flicking#willRestore
1✔
90
   * @fires Flicking#restored
91
   * @fires Flicking#needPanel
92
   * @fires Flicking#visibleChange
93
   * @fires Flicking#reachEdge
94
   * @throws {FlickingError}
95
   * |code|condition|
96
   * |---|---|
97
   * |{@link ERROR_CODE POSITION_NOT_REACHABLE}|When the given panel is already removed or not in the Camera's {@link Camera#range range}|
×
98
   * |{@link ERROR_CODE NOT_ATTACHED_TO_FLICKING}|When {@link Control#init init} is not called before|
99
   * |{@link ERROR_CODE ANIMATION_INTERRUPTED}|When the animation is interrupted by user input|
100
   * |{@link ERROR_CODE STOP_CALLED_BY_USER}|When the animation is interrupted by user input|
101
   * <ko>
1✔
102
   *
103
   * |code|condition|
104
   * |---|---|
105
   * |{@link ERROR_CODE POSITION_NOT_REACHABLE}|주어진 패널이 제거되었거나, Camera의 {@link Camera#range range} 밖에 있을 경우|
106
   * |{@link ERROR_CODE NOT_ATTACHED_TO_FLICKING}|{@link Control#init init}이 이전에 호출되지 않은 경우|
107
   * |{@link ERROR_CODE ANIMATION_INTERRUPTED}|사용자 입력에 의해 애니메이션이 중단된 경우|
108
   * |{@link ERROR_CODE STOP_CALLED_BY_USER}|발생된 이벤트들 중 하나라도 `stop()`이 호출된 경우|
3✔
109
   *
110
   * </ko>
111
   * @return {Promise<void>} A Promise which will be resolved after reaching the target position<ko>해당 좌표 도달시에 resolve되는 Promise</ko>
112
   */
1✔
113
  public abstract moveToPosition(position: number, duration: number, axesEvent?: OnRelease): Promise<void>;
114

115
  /**
116
   * Initialize Control
117
   * @ko Control을 초기화합니다
118
   * @param {Flicking} flicking An instance of {@link Flicking}<ko>Flicking의 인스턴스</ko>
119
   * @chainable
1✔
120
   * @return {this}
121
   */
122
  public init(flicking: Flicking): this {
5✔
123
    this._flicking = flicking;
15✔
124
    this._controller.init(flicking);
14✔
125

126
    return this;
14✔
127
  }
128

129
  /**
130
   * Destroy Control and return to initial state
12✔
131
   * @ko Control을 초기 상태로 되돌립니다
132
   * @return {void}
133
   */
134
  public destroy(): void {
5✔
135
    this._controller.destroy();
136

137
    this._flicking = null;
138
    this._activePanel = null;
139
  }
140

141
  /**
1✔
142
   * Enable input from the user (mouse/touch)
1✔
143
   * @ko 사용자의 입력(마우스/터치)를 활성화합니다
1✔
144
   * @chainable
1✔
145
   * @return {this}
146
   */
147
  public enable(): this {
5✔
148
    this._controller.enable();
149

150
    return this;
151
  }
1✔
152

1✔
153
  /**
1✔
154
   * Disable input from the user (mouse/touch)
1✔
155
   * @ko 사용자의 입력(마우스/터치)를 막습니다
156
   * @chainable
157
   * @return {this}
158
   */
159
  public disable(): this {
5✔
160
    this._controller.disable();
161

162
    return this;
1✔
163
  }
164

165
  /**
166
   * Releases ongoing user input (mouse/touch)
167
   * @ko 사용자의 현재 입력(마우스/터치)를 중단시킵니다
168
   * @chainable
169
   * @return {this}
170
   */
171
  public release(): this {
5✔
172
    this._controller.release();
1✔
173

UNCOV
174
    return this;
×
175
  }
176

177
  /**
178
   * Change the destination and duration of the animation currently playing
179
   * @ko 재생 중인 애니메이션의 목적지와 재생 시간을 변경합니다
180
   * @param {Panel} panel The target panel to move<ko>이동할 패널</ko>
181
   * @param {number} duration Duration of the animation (unit: ms)<ko>애니메이션 진행 시간 (단위: ms)</ko>
182
   * @param {DIRECTION} direction Direction to move, only available in the {@link Flicking#circular circular} mode<ko>이동할 방향. {@link Flicking#circular circular} 옵션 활성화시에만 사용 가능합니다</ko>
1✔
183
   * @chainable
184
   * @throws {FlickingError}
185
   * {@link ERROR_CODE POSITION_NOT_REACHABLE} When the given panel is already removed or not in the Camera's {@link Camera#range range}
186
   * <ko>{@link ERROR_CODE POSITION_NOT_REACHABLE} 주어진 패널이 제거되었거나, Camera의 {@link Camera#range range} 밖에 있을 경우</ko>
187
   * @return {this}
188
   */
189
  public updateAnimation(panel: Panel, duration?: number, direction?: ValueOf<typeof DIRECTION>): this {
5✔
190
    const state = this._controller.state;
191
    const position = this._getPosition(panel, direction ?? DIRECTION.NONE);
×
192

193
    state.targetPanel = panel;
194
    this._controller.updateAnimation(position, duration);
195

196
    return this;
197
  }
198

1✔
199
  /**
200
   * Stops the animation currently playing
×
201
   * @ko 재생 중인 애니메이션을 중단시킵니다
202
   * @chainable
203
   * @return {this}
204
   */
205
  public stopAnimation(): this {
5✔
206
    const state = this._controller.state;
207

208
    state.targetPanel = null;
209
    this._controller.stopAnimation();
210

211
    return this;
1✔
212
  }
213

214
  /**
215
   * Update position after resizing
216
   * @ko resize 이후에 position을 업데이트합니다
217
   * @param {number} progressInPanel Previous camera's progress in active panel before resize<ko>Resize 이전 현재 선택된 패널 내에서의 카메라 progress 값</ko>
218
   * @throws {FlickingError}
219
   * {@link ERROR_CODE NOT_ATTACHED_TO_FLICKING} When {@link Camera#init init} is not called before
220
   * <ko>{@link ERROR_CODE NOT_ATTACHED_TO_FLICKING} {@link Camera#init init}이 이전에 호출되지 않은 경우</ko>
221
   * @chainable
222
   * @return {Promise<void>}
223
   */
224
  public updatePosition(progressInPanel: number): void { // eslint-disable-line @typescript-eslint/no-unused-vars
5✔
225
    const flicking = getFlickingAttached(this._flicking);
226
    const camera = flicking.camera;
227
    const activePanel = this._activePanel;
1✔
228

1✔
229
    if (activePanel) {
1!
230
      camera.lookAt(camera.clampToReachablePosition(activePanel.position));
1✔
231
    }
1!
232
  }
1✔
233

234
  /**
235
   * Update {@link Control#controller controller}'s state
236
   * @ko {@link Control#controller controller}의 내부 상태를 갱신합니다
237
   * @chainable
238
   * @return {this}
239
   */
240
  public updateInput(): this {
5✔
241
    const flicking = getFlickingAttached(this._flicking);
29✔
242
    const camera = flicking.camera;
31✔
243

3✔
244
    this._controller.update(camera.controlParams);
31✔
245

3✔
246
    return this;
28✔
247
  }
248

249
  /**
250
   * Reset {@link Control#activePanel activePanel} to `null`
251
   * @ko {@link Control#activePanel activePanel}을 `null`로 초기화합니다
252
   * @chainable
253
   * @return {this}
1✔
254
   */
255
  public resetActive(): this {
5✔
256
    this._activePanel = null;
257

258
    return this;
259
  }
260

261
  /**
262
   * Move {@link Camera} to the given panel
263
   * @ko {@link Camera}를 해당 패널 위로 이동합니다
264
   * @param {Panel} panel The target panel to move<ko>이동할 패널</ko>
265
   * @param {object} options An options object<ko>옵션 오브젝트</ko>
266
   * @param {number} duration Duration of the animation (unit: ms)<ko>애니메이션 진행 시간 (단위: ms)</ko>
267
   * @param {object} [axesEvent] {@link https://naver.github.io/egjs-axes/release/latest/doc/eg.Axes.html#event:release release} event of {@link https://naver.github.io/egjs-axes/ Axes}
268
   * <ko>{@link https://naver.github.io/egjs-axes/ Axes}의 {@link https://naver.github.io/egjs-axes/release/latest/doc/eg.Axes.html#event:release release} 이벤트</ko>
269
   * @param {DIRECTION} [direction=DIRECTION.NONE] Direction to move, only available in the {@link Flicking#circular circular} mode<ko>이동할 방향. {@link Flicking#circular circular} 옵션 활성화시에만 사용 가능합니다</ko>
270
   * @fires Flicking#moveStart
271
   * @fires Flicking#move
272
   * @fires Flicking#moveEnd
273
   * @fires Flicking#willChange
274
   * @fires Flicking#changed
275
   * @fires Flicking#willRestore
276
   * @fires Flicking#restored
277
   * @fires Flicking#needPanel
278
   * @fires Flicking#visibleChange
279
   * @fires Flicking#reachEdge
280
   * @throws {FlickingError}
281
   * |code|condition|
282
   * |---|---|
283
   * |{@link ERROR_CODE POSITION_NOT_REACHABLE}|When the given panel is already removed or not in the Camera's {@link Camera#range range}|
284
   * |{@link ERROR_CODE NOT_ATTACHED_TO_FLICKING}|When {@link Control#init init} is not called before|
285
   * |{@link ERROR_CODE ANIMATION_INTERRUPTED}|When the animation is interrupted by user input|
286
   * |{@link ERROR_CODE STOP_CALLED_BY_USER}|When the animation is interrupted by user input|
287
   * <ko>
288
   *
289
   * |code|condition|
290
   * |---|---|
291
   * |{@link ERROR_CODE POSITION_NOT_REACHABLE}|주어진 패널이 제거되었거나, Camera의 {@link Camera#range range} 밖에 있을 경우|
292
   * |{@link ERROR_CODE NOT_ATTACHED_TO_FLICKING}|{@link Control#init init}이 이전에 호출되지 않은 경우|
293
   * |{@link ERROR_CODE ANIMATION_INTERRUPTED}|사용자 입력에 의해 애니메이션이 중단된 경우|
294
   * |{@link ERROR_CODE STOP_CALLED_BY_USER}|발생된 이벤트들 중 하나라도 `stop()`이 호출된 경우|
295
   *
1✔
296
   * </ko>
×
297
   * @return {Promise<void>} A Promise which will be resolved after reaching the target panel<ko>해당 패널 도달시에 resolve되는 Promise</ko>
298
   */
299
  public async moveToPanel(panel: Panel, {
5✔
300
    duration,
3✔
301
    direction = DIRECTION.NONE,
3✔
302
    axesEvent
3✔
303
  }: {
304
    duration: number;
305
    direction?: ValueOf<typeof DIRECTION>;
306
    axesEvent?: OnRelease;
307
  }) {
308
    const position = this._getPosition(panel, direction);
3✔
309
    this._triggerIndexChangeEvent(panel, panel.position, axesEvent, direction);
4✔
310

311
    return this._animateToPosition({ position, duration, newActivePanel: panel, axesEvent });
4✔
312
  }
1✔
313

1✔
314
  /**
1✔
315
   * @internal
1!
316
   */
1✔
317
  public setActive(newActivePanel: Panel, prevActivePanel: Panel | null, isTrusted: boolean) {
5✔
318
    const flicking = getFlickingAttached(this._flicking);
16✔
319

5!
320
    this._activePanel = newActivePanel;
16✔
321
    this._nextPanel = null;
16✔
322

1!
323
    flicking.camera.updateAdaptiveHeight();
16✔
324

325
    if (newActivePanel !== prevActivePanel) {
16✔
326
      flicking.trigger(new ComponentEvent(EVENTS.CHANGED, {
15✔
327
        index: newActivePanel.index,
328
        panel: newActivePanel,
329
        prevIndex: prevActivePanel?.index ?? -1,
76✔
330
        prevPanel: prevActivePanel,
331
        isTrusted,
332
        direction: prevActivePanel ? getDirection(prevActivePanel.position, newActivePanel.position) : DIRECTION.NONE
15✔
333
      }));
334
    } else {
1✔
335
      flicking.trigger(new ComponentEvent(EVENTS.RESTORED, {
1✔
336
        isTrusted
337
      }));
338
    }
339
  }
1✔
340

341
  /**
342
   * @internal
×
343
   */
344
  public copy(control: Control) {
5✔
UNCOV
345
    this._flicking = control._flicking;
×
346
    this._activePanel = control._activePanel;
347
    this._controller = control._controller;
348
  }
×
349

×
350
  protected _triggerIndexChangeEvent(panel: Panel, position: number, axesEvent?: OnRelease, direction?: ValueOf<typeof DIRECTION>) {
5✔
351
    const flicking = getFlickingAttached(this._flicking);
4✔
352
    const triggeringEvent = panel !== this._activePanel ? EVENTS.WILL_CHANGE : EVENTS.WILL_RESTORE;
4✔
353
    const camera = flicking.camera;
4!
354
    const activePanel = this._activePanel;
4✔
355

356
    const event = new ComponentEvent(triggeringEvent, {
4✔
357
      index: panel.index,
1✔
358
      panel,
359
      isTrusted: axesEvent?.isTrusted || false,
18✔
360
      direction: direction ?? getDirection(activePanel?.position ?? camera.position, position)
23!
361
    });
362

363
    this._nextPanel = panel;
4✔
364
    flicking.trigger(event);
4✔
365

366
    if (event.isCanceled()) {
4!
UNCOV
367
      throw new FlickingError(ERROR.MESSAGE.STOP_CALLED_BY_USER, ERROR.CODE.STOP_CALLED_BY_USER);
×
368
    }
369
  }
370

371
  protected async _animateToPosition({
5✔
372
    position,
4✔
373
    duration,
4!
374
    newActivePanel,
4✔
375
    axesEvent
4✔
376
  }: {
377
    position: number;
378
    duration: number;
379
    newActivePanel: Panel;
380
    axesEvent?: OnRelease;
381
  }) {
×
382
    const flicking = getFlickingAttached(this._flicking);
4✔
383
    const animate = () => this._controller.animateTo(position, duration, axesEvent);
4✔
384
    const state = this._controller.state;
4✔
385

386
    state.targetPanel = newActivePanel;
4✔
387

388
    if (duration <= 0) {
4!
389
      return animate();
390
    } else {
1✔
391
      return animate().then(async () => {
4!
392
        await flicking.renderer.render();
4✔
393
      }).catch(err => {
UNCOV
394
        if (axesEvent && err instanceof FlickingError && err.code === ERROR.CODE.ANIMATION_INTERRUPTED) return;
×
UNCOV
395
        throw err;
×
396
      });
×
397
    }
398
  }
399

×
400
  private _getPosition(panel: Panel, direction: ValueOf<typeof DIRECTION> = DIRECTION.NONE) {
5!
401
    const flicking = getFlickingAttached(this._flicking);
3✔
402
    const camera = flicking.camera;
3✔
403

404
    let position = panel.position;
3!
405
    const nearestAnchor = camera.findNearestAnchor(position);
3✔
406

407
    if (panel.removed || !nearestAnchor) {
3!
UNCOV
408
      throw new FlickingError(ERROR.MESSAGE.POSITION_NOT_REACHABLE(panel.position), ERROR.CODE.POSITION_NOT_REACHABLE);
×
409
    }
410
    if (!camera.canReach(panel)) {
3!
411
      // Override position & panel if that panel is not reachable
UNCOV
412
      position = nearestAnchor.position;
×
413
      panel = nearestAnchor.panel;
414
    } else if (flicking.circularEnabled) {
3!
415
      // Circular mode is enabled, find nearest distance to panel
UNCOV
416
      const camPos = this._controller.position; // Actual position of the Axes
×
UNCOV
417
      const camRangeDiff = camera.rangeDiff;
×
UNCOV
418
      const possiblePositions = [position, position + camRangeDiff, position - camRangeDiff]
×
419
        .filter(pos => {
420
          if (direction === DIRECTION.NONE) return true;
×
421

422
          return direction === DIRECTION.PREV
×
423
            ? pos <= camPos
424
            : pos >= camPos;
425
        });
426

427
      position = possiblePositions.reduce((nearestPosition, pos) => {
1✔
428
        if (Math.abs(camPos - pos) < Math.abs(camPos - nearestPosition)) {
×
429
          return pos;
1✔
430
        } else {
431
          return nearestPosition;
432
        }
433
      }, Infinity);
434
    }
435

436
    return position;
3✔
437
  }
438
}
5✔
439

440
export default Control;
5✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc