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

naver / egjs-flicking / 4550193631

pending completion
4550193631

Pull #794

github

GitHub
Merge f8b6de8d9 into 761aa929d
Pull Request #794: docs: fix misspelled path in document

4448 of 7210 branches covered (61.69%)

Branch coverage included in aggregate %.

8413 of 9000 relevant lines covered (93.48%)

142.07 hits per line

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

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

×
7
import Panel from "../core/panel/Panel";
1✔
8
import FlickingError from "../core/FlickingError";
5✔
9
import { clamp, getFlickingAttached, getMinusCompensatedIndex, isBetween } from "../utils";
6✔
10
import * as ERROR from "../const/error";
6!
11

12
import Control from "./Control";
6✔
13
/**
1✔
14
 * An options for the {@link StrictControl}
1!
15
 * @ko {@link StrictControl} 생성시 사용되는 옵션
16
 * @interface
17
 * @property {number} count Maximum number of panels that can be moved at a time<ko>최대로 움직일 수 있는 패널의 개수</ko>
1!
18
 */
×
19
export interface StrictControlOptions {
20
  count: number;
21
}
×
22

23
/**
24
 * A {@link Control} that allow you to select the maximum number of panels to move at a time
1!
25
 * @ko 한번에 최대로 이동할 패널의 개수를 선택 가능한 {@link Control}
26
 */
27
class StrictControl extends Control {
5✔
28
  private _count: number;
29
  private _indexRange: { min: number; max: number };
1✔
30

1!
31
  /**
32
   * Maximum number of panels that can be moved at a time
×
33
   * @ko 최대로 움직일 수 있는 패널의 개수
34
   * @type {number}
35
   * @default 1
36
   */
1✔
37
  public get count() { return this._count; }
5!
38

14✔
39
  public set count(val: StrictControlOptions["count"]) { this._count = val; }
×
40

41
  /** */
14!
42
  public constructor({
14!
43
    count = 1
×
44
  }: Partial<StrictControlOptions> = {}) {
45
    super();
1!
46

14!
47
    this._count = count;
14✔
48
    this._resetIndexRange();
42✔
49
  }
50

14!
51
  /**
28✔
52
   * Destroy Control and return to initial state
28!
53
   * @ko Control을 초기 상태로 되돌립니다
28!
54
   * @return {void}
28!
55
   */
14✔
56
  public destroy() {
5✔
57
    super.destroy();
×
58

59
    this._resetIndexRange();
60
  }
14!
61

×
62
  /**
×
63
   * Update {@link Control#controller controller}'s state
×
64
   * @ko {@link Control#controller controller}의 내부 상태를 갱신합니다
×
65
   * @chainable
66
   * @return {this}
67
   */
14✔
68
  public updateInput(): this {
33✔
69
    const flicking = getFlickingAttached(this._flicking);
14!
70
    const camera = flicking.camera;
71
    const renderer = flicking.renderer;
72
    const controller = this._controller;
1✔
73
    const controlParams = camera.controlParams;
2!
74
    const count = this._count;
75

1✔
76
    const activePanel = controller.state.animating
1!
77
      ? camera.findNearestAnchor(camera.position)?.panel
1!
78
      : this._activePanel;
1✔
79

1✔
80
    if (!activePanel) {
×
81
      controller.update(controlParams);
82
      this._resetIndexRange();
83
      return this;
84
    }
1✔
85

1✔
86
    const cameraRange = controlParams.range;
87
    const currentPos = activePanel.position;
88
    const currentIndex = activePanel.index;
23✔
89
    const panelCount = renderer.panelCount;
23✔
90

23✔
91
    let prevPanelIndex = currentIndex - count;
40✔
92
    let nextPanelIndex = currentIndex + count;
40✔
93

94
    if (prevPanelIndex < 0) {
23!
95
      prevPanelIndex = flicking.circularEnabled
23!
96
        ? getMinusCompensatedIndex((prevPanelIndex + 1) % panelCount - 1, panelCount)
23✔
97
        : clamp(prevPanelIndex, 0, panelCount - 1);
98
    }
1✔
99
    if (nextPanelIndex >= panelCount) {
×
100
      nextPanelIndex = flicking.circularEnabled
×
101
        ? nextPanelIndex % panelCount
102
        : clamp(nextPanelIndex, 0, panelCount - 1);
103
    }
104

105
    const prevPanel = renderer.panels[prevPanelIndex];
×
106
    const nextPanel = renderer.panels[nextPanelIndex];
×
107

108
    let prevPos = Math.max(prevPanel.position, cameraRange.min);
109
    let nextPos = Math.min(nextPanel.position, cameraRange.max);
110

111
    if (prevPos > currentPos) {
×
112
      prevPos -= camera.rangeDiff;
113
    }
114
    if (nextPos < currentPos) {
×
115
      nextPos += camera.rangeDiff;
1✔
116
    }
22✔
117

22✔
118
    controlParams.range = {
119
      min: prevPos,
120
      max: nextPos
121
    };
122

123
    if (controlParams.circular) {
×
124
      if (controlParams.position < prevPos) {
×
125
        controlParams.position += camera.rangeDiff;
1✔
126
      }
127

89✔
128
      if (controlParams.position > nextPos) {
89!
129
        controlParams.position -= camera.rangeDiff;
89✔
130
      }
89✔
131
    }
89✔
132

89✔
133
    controlParams.circular = false;
89✔
134
    controller.update(controlParams);
12!
135

136
    this._indexRange = {
89✔
137
      min: prevPanel.index,
21✔
138
      max: nextPanel.index
21✔
139
    };
21✔
140

141
    return this;
68✔
142
  }
68✔
143

68✔
144
  public async moveToPanel(panel: Panel, options: Parameters<Control["moveToPanel"]>[1]): Promise<void> {
73✔
145
    const flicking = getFlickingAttached(this._flicking);
68✔
146
    const camera = flicking.camera;
68✔
147
    const controller = this._controller;
68✔
148

41✔
149
    controller.update(camera.controlParams);
150

151
    return super.moveToPanel(panel, options);
152
  }
68✔
153

14✔
154
  /**
155
   * Move {@link Camera} to the given position
156
   * @ko {@link Camera}를 주어진 좌표로 이동합니다
157
   * @param {number} position The target position to move<ko>이동할 좌표</ko>
68✔
158
   * @param {number} duration Duration of the panel movement animation (unit: ms).<ko>패널 이동 애니메이션 진행 시간 (단위: ms)</ko>
68✔
159
   * @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}
68✔
160
   * <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>
68✔
161
   * @fires Flicking#moveStart
68✔
162
   * @fires Flicking#move
10✔
163
   * @fires Flicking#moveEnd
164
   * @fires Flicking#willChange
68✔
165
   * @fires Flicking#changed
3✔
166
   * @fires Flicking#willRestore
167
   * @fires Flicking#restored
68✔
168
   * @fires Flicking#needPanel
169
   * @fires Flicking#visibleChange
170
   * @fires Flicking#reachEdge
171
   * @throws {FlickingError}
68✔
172
   * |code|condition|
12!
173
   * |---|---|
174
   * |{@link ERROR_CODE POSITION_NOT_REACHABLE}|When the given panel is already removed or not in the Camera's {@link Camera#range range}|
175
   * |{@link ERROR_CODE NOT_ATTACHED_TO_FLICKING}|When {@link Control#init init} is not called before|
12!
176
   * |{@link ERROR_CODE ANIMATION_INTERRUPTED}|When the animation is interrupted by user input|
177
   * |{@link ERROR_CODE STOP_CALLED_BY_USER}|When the animation is interrupted by user input|
178
   * <ko>
179
   *
68✔
180
   * |code|condition|
68✔
181
   * |---|---|
68✔
182
   * |{@link ERROR_CODE POSITION_NOT_REACHABLE}|주어진 패널이 제거되었거나, Camera의 {@link Camera#range range} 밖에 있을 경우|
183
   * |{@link ERROR_CODE NOT_ATTACHED_TO_FLICKING}|{@link Control#init init}이 이전에 호출되지 않은 경우|
184
   * |{@link ERROR_CODE ANIMATION_INTERRUPTED}|사용자 입력에 의해 애니메이션이 중단된 경우|
185
   * |{@link ERROR_CODE STOP_CALLED_BY_USER}|발생된 이벤트들 중 하나라도 `stop()`이 호출된 경우|
68✔
186
   *
187
   * </ko>
1✔
188
   * @return {Promise<void>} A Promise which will be resolved after reaching the target position<ko>해당 좌표 도달시에 resolve되는 Promise</ko>
14✔
189
   */
190
  public moveToPosition(position: number, duration: number, axesEvent?: OnRelease) {
19✔
191
    const flicking = getFlickingAttached(this._flicking);
14✔
192
    const camera = flicking.camera;
14✔
193
    const activePanel = this._activePanel;
14✔
194
    const axesRange = this._controller.range;
14✔
195
    const indexRange = this._indexRange;
14✔
196
    const cameraRange = camera.range;
197
    const state = this._controller.state;
198

199
    const clampedPosition = clamp(camera.clampToReachablePosition(position), axesRange[0], axesRange[1]);
200
    const anchorAtPosition = camera.findAnchorIncludePosition(clampedPosition);
201

202
    if (!anchorAtPosition || !activePanel) {
×
203
      return Promise.reject(new FlickingError(ERROR.MESSAGE.POSITION_NOT_REACHABLE(position), ERROR.CODE.POSITION_NOT_REACHABLE));
204
    }
205

206
    const prevPos = activePanel.position;
207
    const posDelta = flicking.animating
×
208
      ? state.delta
209
      : position - camera.position;
210

211
    const isOverThreshold = Math.abs(posDelta) >= flicking.threshold;
212
    const adjacentAnchor = (position > prevPos)
×
213
      ? camera.getNextAnchor(anchorAtPosition)
214
      : camera.getPrevAnchor(anchorAtPosition);
215

216
    let targetPos: number;
217
    let targetPanel: Panel;
218

219
    const anchors = camera.anchorPoints;
220
    const firstAnchor = anchors[0];
221
    const lastAnchor = anchors[anchors.length - 1];
222

223
    const shouldBounceToFirst = position <= cameraRange.min && isBetween(firstAnchor.panel.index, indexRange.min, indexRange.max);
×
224
    const shouldBounceToLast = position >= cameraRange.max && isBetween(lastAnchor.panel.index, indexRange.min, indexRange.max);
×
225

226
    const isAdjacent = adjacentAnchor && (indexRange.min <= indexRange.max
×
227
      ? isBetween(adjacentAnchor.index, indexRange.min, indexRange.max)
228
      : adjacentAnchor.index >= indexRange.min || adjacentAnchor.index <= indexRange.max);
×
229

230
    if (shouldBounceToFirst || shouldBounceToLast) {
×
231
      // In bounce area
232
      const targetAnchor = position < cameraRange.min ? firstAnchor : lastAnchor;
×
233

234
      targetPanel = targetAnchor.panel;
235
      targetPos = targetAnchor.position;
1✔
236
    } else if (isOverThreshold && anchorAtPosition.position !== activePanel.position) {
10!
237
      // Move to anchor at position
9✔
238
      targetPanel = anchorAtPosition.panel;
9✔
239
      targetPos = anchorAtPosition.position;
9✔
240
    } else if (isOverThreshold && isAdjacent) {
9!
241
      // Move to adjacent anchor
9✔
242
      targetPanel = adjacentAnchor!.panel;
9✔
243
      targetPos = adjacentAnchor!.position;
9✔
244
    } else {
9✔
245
      // Fallback to nearest panel from current camera
9✔
246
      const anchorAtCamera = camera.findNearestAnchor(camera.position);
1✔
247
      if (!anchorAtCamera) {
×
248
        return Promise.reject(new FlickingError(ERROR.MESSAGE.POSITION_NOT_REACHABLE(position), ERROR.CODE.POSITION_NOT_REACHABLE));
8✔
249
      }
8✔
250
      return this.moveToPanel(anchorAtCamera.panel, {
251
        duration,
252
        axesEvent
8✔
253
      });
8✔
254
    }
255

256
    this._triggerIndexChangeEvent(targetPanel, position, axesEvent);
257

258
    return this._animateToPosition({
8✔
259
      position: targetPos,
8✔
260
      duration,
8✔
261
      newActivePanel: targetPanel,
8✔
262
      axesEvent
8✔
263
    });
8✔
264
  }
265

2✔
266
  public setActive = (newActivePanel: Panel, prevActivePanel: Panel | null, isTrusted: boolean) => {
8✔
267
    super.setActive(newActivePanel, prevActivePanel, isTrusted);
268
    this.updateInput();
4✔
269
  };
4✔
270

4✔
271
  private _resetIndexRange() {
5✔
272
    this._indexRange = { min: 0, max: 0 };
4✔
273
  }
274
}
8✔
275

3✔
276
export default StrictControl;
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

© 2025 Coveralls, Inc