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

naver / egjs-infinitegrid / 3869242514

pending completion
3869242514

Pull #527

github

GitHub
Merge 42f03d580 into 6db0bffc8
Pull Request #527: feat: upgrade to Angular 15 and enable partial compilation

523 of 649 branches covered (80.59%)

Branch coverage included in aggregate %.

1349 of 1446 relevant lines covered (93.29%)

114.11 hits per line

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

91.86
/packages/infinitegrid/src/InfiniteGrid.ts
1
import Component, { ComponentEvent } from "@egjs/component";
1✔
2
import {
1✔
3
  ContainerManager,
4
  DEFAULT_GRID_OPTIONS,
5
  Properties,
6
  RenderOptions,
7
  MOUNT_STATE,
8
  OnContentError,
9
  ItemRenderer,
10
  GridItem,
11
  ResizeWatcherResizeEvent,
12
  getUpdatedItems,
13
} from "@egjs/grid";
14
import {
1✔
15
  DIRECTION,
16
  GROUP_TYPE,
17
  INFINITEGRID_EVENTS, INFINITEGRID_PROPERTY_TYPES,
18
  ITEM_TYPE, STATUS_TYPE,
19
} from "./consts";
20
import { GroupManager } from "./GroupManager";
1✔
21
import {
1✔
22
  Infinite,
23
  OnInfiniteChange,
24
  OnInfiniteRequestAppend,
25
  OnInfiniteRequestPrepend,
26
} from "./Infinite";
27
import { InfiniteGridItem, InfiniteGridItemStatus } from "./InfiniteGridItem";
28
import { OnRendererUpdated } from "./Renderer/Renderer";
29
import { GridRendererItem, VanillaGridRenderer } from "./Renderer/VanillaGridRenderer";
1✔
30
import { ScrollManager } from "./ScrollManager";
1✔
31
import {
32
  InfiniteGridEvents, InfiniteGridGroup,
33
  InfiniteGridInsertedItems, InfiniteGridItemInfo,
34
  InfiniteGridOptions,
35
  InfiniteGridStatus,
36
  InsertedPlaceholdersResult,
37
  OnPickedRenderComplete,
38
  OnRequestInsert,
39
  OnChangeScroll,
40
} from "./types";
41
import {
1✔
42
  InfiniteGridGetterSetter, toArray, convertInsertedItems, findIndex,
43
  findLastIndex, isString,
44
} from "./utils";
45

46

47
/**
48
 * A module used to arrange items including content infinitely according to layout type. With this module, you can implement various layouts composed of different items whose sizes vary. It guarantees performance by maintaining the number of DOMs the module is handling under any circumstance
49
 * @ko 콘텐츠가 있는 아이템을 레이아웃 타입에 따라 무한으로 배치하는 모듈. 다양한 크기의 아이템을 다양한 레이아웃으로 배치할 수 있다. 아이템의 개수가 계속 늘어나도 모듈이 처리하는 DOM의 개수를 일정하게 유지해 최적의 성능을 보장한다
50
 * @extends Component
51
 * @support {"ie": "9+(with polyfill)", "ch" : "latest", "ff" : "latest",  "sf" : "latest", "edge" : "latest", "ios" : "7+", "an" : "4.X+"}
52
 * @example
53
```html
54
<ul id="grid">
55
  <li class="card">
56
    <div>test1</div>
57
  </li>
58
  <li class="card">
59
    <div>test2</div>
60
  </li>
61
  <li class="card">
62
    <div>test3</div>
63
  </li>
64
  <li class="card">
65
    <div>test4</div>
66
  </li>
67
  <li class="card">
68
    <div>test5</div>
69
  </li>
70
  <li class="card">
71
    <div>test6</div>
72
  </li>
73
</ul>
74
<script>
75
import { MasonryInfiniteGrid } from "@egjs/infinitegrid";
76
var some = new MasonryInfiniteGrid("#grid").on("renderComplete", function(e) {
77
  // ...
78
});
79
// If you already have items in the container, call "layout" method.
80
some.renderItems();
81
</script>
82
```
83
 */
84
@InfiniteGridGetterSetter
85
class InfiniteGrid<Options extends InfiniteGridOptions = InfiniteGridOptions> extends Component<InfiniteGridEvents> {
1✔
86
  public static defaultOptions = {
1✔
87
    ...DEFAULT_GRID_OPTIONS,
88
    container: false,
89
    containerTag: "div",
90
    renderer: null,
91
    threshold: 100,
92
    useRecycle: true,
93
    scrollContainer: null,
94
  } as Required<InfiniteGridOptions>;
95
  public static propertyTypes = INFINITEGRID_PROPERTY_TYPES;
1✔
96
  protected wrapperElement: HTMLElement;
97
  protected scrollManager: ScrollManager;
98
  protected itemRenderer: ItemRenderer;
99
  protected containerManager: ContainerManager;
100
  protected infinite: Infinite;
101
  protected groupManager: GroupManager;
102
  protected options: Required<Options>;
103
  private _waitType: "" | "start" | "end" = "";
60✔
104
  /**
105
   * @param - A base element for a module <ko>모듈을 적용할 기준 엘리먼트</ko>
106
   * @param - The option object of the InfiniteGrid module <ko>eg.InfiniteGrid 모듈의 옵션 객체</ko>
107
   */
108
  constructor(wrapper: HTMLElement | string, options: Options) {
61✔
109
    super();
120✔
110
    this.options = {
60✔
111
      ...((this.constructor as typeof InfiniteGrid).defaultOptions as Required<Options>),
112
      renderer: new VanillaGridRenderer().on("requestUpdate", () => this._render()),
108✔
113
      ...options,
114
    };
115

116
    const {
60✔
117
      gridConstructor,
118
      containerTag,
119
      container,
120
      renderer,
121
      threshold,
122
      useRecycle,
123
      scrollContainer,
124
      ...gridOptions
125
    } = this.options;
126
    // options.container === false, wrapper = container, scrollContainer = document.body
127
    // options.container === true, wrapper = scrollContainer, container = wrapper's child
128
    // options.container === string,
129
    const {
130
      horizontal,
60✔
131
      attributePrefix,
132
      useTransform,
133
      percentage,
134
      isConstantSize,
135
      isEqualSize,
136
      autoResize,
137
      useResizeObserver,
138
      resizeDebounce,
139
      maxResizeDebounce,
140
      defaultDirection,
141
    } = gridOptions;
142
    const wrapperElement = isString(wrapper) ? document.querySelector(wrapper) as HTMLElement : wrapper;
60!
143
    const scrollManager = new ScrollManager(wrapperElement, {
60✔
144
      scrollContainer,
145
      container,
146
      containerTag,
147
      horizontal,
148
    }).on({
149
      scroll: this._onScroll,
150
    });
151
    const containerElement = scrollManager.getContainer();
60✔
152
    const containerManager = new ContainerManager(containerElement, {
60✔
153
      horizontal,
154
      autoResize,
155
      resizeDebounce,
156
      maxResizeDebounce,
157
      useResizeObserver,
158
    }).on("resize", this._onResize);
159
    const itemRenderer = new ItemRenderer({
60✔
160
      attributePrefix,
161
      horizontal,
162
      useTransform,
163
      percentage,
164
      isEqualSize,
165
      isConstantSize,
166
    });
167
    const infinite = new Infinite({
60✔
168
      defaultDirection,
169
      useRecycle,
170
      threshold,
171
    }).on({
172
      "change": this._onChange,
173
      "requestAppend": this._onRequestAppend,
174
      "requestPrepend": this._onRequestPrepend,
175
    });
176

177
    infinite.setSize(scrollManager.getContentSize());
60✔
178
    const groupManager = new GroupManager(containerElement, {
60✔
179
      gridConstructor: gridConstructor!,
180
      externalItemRenderer: itemRenderer,
181
      externalContainerManager: containerManager,
182
      gridOptions,
183
    });
184

185
    groupManager.on({
60✔
186
      "renderComplete": this._onRenderComplete,
187
      "contentError": this._onContentError,
188
    });
189

190
    renderer!.setContainer(containerElement);
60✔
191
    renderer!.on("updated", this._onRendererUpdated);
60✔
192

193
    this.itemRenderer = itemRenderer;
60✔
194
    this.groupManager = groupManager;
60✔
195
    this.wrapperElement = wrapperElement;
60✔
196
    this.scrollManager = scrollManager;
60✔
197
    this.containerManager = containerManager;
60✔
198
    this.infinite = infinite;
60✔
199

200
    this.containerManager.resize();
60✔
201
  }
202
  /**
203
   * Rearrange items to fit the grid and render them. When rearrange is complete, the `renderComplete` event is fired.
204
   * @ko grid에 맞게 아이템을 재배치하고 렌더링을 한다. 배치가 완료되면 `renderComplete` 이벤트가 발생한다.
205
   * @param - Options for rendering. <ko>렌더링을 하기 위한 옵션.</ko>
206
   * @example
207
   * ```ts
208
   * import { MasonryInfiniteGrid } from "@egjs/infinitegrid";
209
   * const grid = new MasonryInfiniteGrid();
210
   *
211
   * grid.on("renderComplete", e => {
212
   *   console.log(e);
213
   * });
214
   * grid.renderItems();
215
   * ```
216
   */
217
  public renderItems(options: RenderOptions = {}) {
44!
218
    this._renderItems(options);
22✔
219
    return this;
22✔
220
  }
221
  /**
222
   * Returns the wrapper element specified by the user.
223
   * @ko 컨테이너 엘리먼트를 반환한다.
224
   */
225
  public getWrapperElement() {
1✔
226
    return this.scrollManager.getWrapper();
2✔
227
  }
228
  /**
229
   * Returns the container element corresponding to the scroll area.
230
   * @ko 스크롤 영역에 해당하는 컨테이너 엘리먼트를 반환한다.
231
   */
232
  public getScrollContainerElement() {
1✔
233
    return this.scrollManager.getScrollContainer();
21✔
234
  }
235
  /**
236
   * Returns the container element containing item elements.
237
   * @ko 아이템 엘리먼트들을 담긴 컨테이너 엘리먼트를 반환한다.
238
   */
239
  public getContainerElement() {
1✔
240
    return this.scrollManager.getContainer();
33✔
241
  }
242
  /**
243
   * When items change, it synchronizes and renders items.
244
   * @ko items가 바뀐 경우 동기화를 하고 렌더링을 한다.
245
   * @param - Options for rendering. <ko>렌더링을 하기 위한 옵션.</ko>
246
   */
247
  public syncItems(items: InfiniteGridItemInfo[]): this {
1✔
248
    this.groupManager.syncItems(items);
78✔
249
    this._syncGroups();
78✔
250

251
    return this;
78✔
252
  }
253
  /**
254
   * Change the currently visible groups.
255
   * @ko 현재 보이는 그룹들을 바꾼다.
256
   * @param - first index of visible groups. <ko>보이는 그룹의 첫번째 index.</ko>
257
   * @param - last index of visible groups. <ko>보이는 그룹의 마지막 index.</ko>
258
   * @param - Whether the first rendering has already been done. <ko>첫 렌더링이 이미 되어있는지 여부.</ko>
259
   */
260
  public setCursors(startCursor: number, endCursor: number, useFirstRender?: boolean): this {
1✔
261
    this.groupManager.setCursors(startCursor, endCursor);
134✔
262
    this.infinite.setCursors(startCursor, endCursor);
134✔
263

264
    if (useFirstRender) {
134✔
265
      this._syncItems();
4✔
266
    } else {
267
      this._update();
130✔
268
      this._checkEndLoading();
130✔
269
    }
270
    return this;
134✔
271
  }
272
  /**
273
   * Returns the first index of visible groups.
274
   * @ko 보이는 그룹들의 첫번째 index를 반환한다.
275
   */
276
  public getStartCursor(): number {
1✔
277
    return this.infinite.getStartCursor();
138✔
278
  }
279
  /**
280
   * Returns the last index of visible groups.
281
   * @ko 보이는 그룹들의 마지막 index를 반환한다.
282
   */
283
  public getEndCursor(): number {
1✔
284
    return this.infinite.getEndCursor();
137✔
285
  }
286
  /**
287
   * Add items at the bottom(right) of the grid.
288
   * @ko 아이템들을 grid 아래(오른쪽)에 추가한다.
289
   * @param - items to be added <ko>추가할 아이템들</ko>
290
   * @param - The group key to be configured in items. It is automatically generated by default. <ko>추가할 아이템에 설정할 그룹 키. 생략하면 값이 자동으로 생성된다.</ko>
291
   * @return - An instance of a module itself<ko>모듈 자신의 인스턴스</ko>
292
   * @example
293
   * ```js
294
   * ig.append(`<div class="item">test1</div><div class="item">test2</div>`);
295
   * ig.append([`<div class="item">test1</div>`, `<div class="item">test2</div>`]);
296
   * ig.append([HTMLElement1, HTMLElement2]);
297
   * ```
298
   */
299
  public append(items: InfiniteGridInsertedItems, groupKey?: string | number): this {
1✔
300
    return this.insert(-1, items, groupKey);
23✔
301
  }
302
  /**
303
   * Add items at the top(left) of the grid.
304
   * @ko 아이템들을 grid 위(왼쪽)에 추가한다.
305
   * @param - items to be added <ko>추가할 아이템들</ko>
306
   * @param - The group key to be configured in items. It is automatically generated by default. <ko>추가할 아이템에 설정할 그룹 키. 생략하면 값이 자동으로 생성된다.</ko>
307
   * @return - An instance of a module itself<ko>모듈 자신의 인스턴스</ko>
308
   * @example
309
   * ```ts
310
   * ig.prepend(`<div class="item">test1</div><div class="item">test2</div>`);
311
   * ig.prepend([`<div class="item">test1</div>`, `<div class="item">test2</div>`]);
312
   * ig.prepend([HTMLElement1, HTMLElement2]);
313
   * ```
314
   */
315
  public prepend(items: InfiniteGridInsertedItems, groupKey?: string | number): this {
1✔
316
    return this.insert(0, items, groupKey);
2✔
317
  }
318
  /**
319
   * Add items to a specific index.
320
   * @ko 아이템들을 특정 index에 추가한다.
321
   * @param - index to add <ko>추가하기 위한 index</ko>
322
   * @param - items to be added <ko>추가할 아이템들</ko>
323
   * @param - The group key to be configured in items. It is automatically generated by default. <ko>추가할 아이템에 설정할 그룹 키. 생략하면 값이 자동으로 생성된다.</ko>
324
   * @return - An instance of a module itself<ko>모듈 자신의 인스턴스</ko>
325
   * @example
326
   * ```ts
327
   * ig.insert(2, `<div class="item">test1</div><div class="item">test2</div>`);
328
   * ig.insert(3, [`<div class="item">test1</div>`, `<div class="item">test2</div>`]);
329
   * ig.insert(4, [HTMLElement1, HTMLElement2]);
330
   * ```
331
   */
332
  public insert(index: number, items: InfiniteGridInsertedItems, groupKey?: string | number): this {
1✔
333
    const nextItemInfos: InfiniteGridItemInfo[] = this.groupManager.getGroupItems();
27✔
334
    const itemInfos = convertInsertedItems(items, groupKey);
27✔
335

336
    if (index === -1) {
27✔
337
      nextItemInfos.push(...itemInfos);
23✔
338
    } else {
339
      nextItemInfos.splice(index, 0, ...itemInfos);
4✔
340
    }
341
    return this.syncItems(nextItemInfos);
27✔
342
  }
343
  /**
344
   * Add items based on group index.
345
   * @ko group의 index 기준으로 item들을 추가한다.
346
   * @param - group index to add <ko>추가하기 위한 group의 index</ko>
347
   * @param - items to be added <ko>추가할 아이템들</ko>
348
   * @param - The group key to be configured in items. It is automatically generated by default. <ko>추가할 아이템에 설정할 그룹 키. 생략하면 값이 자동으로 생성된다.</ko>
349
   * @return - An instance of a module itself<ko>모듈 자신의 인스턴스</ko>
350
   * @example
351
   * ```ts
352
   * ig.insertByGroupIndex(2, `<div class="item">test1</div><div class="item">test2</div>`);
353
   * ig.insertByGroupIndex(3, [`<div class="item">test1</div>`, `<div class="item">test2</div>`]);
354
   * ig.insertByGroupIndex(4, [HTMLElement1, HTMLElement2]);
355
   * ```
356
   */
357
  public insertByGroupIndex(groupIndex: number, items: InfiniteGridInsertedItems, groupKey?: string | number): this {
1✔
358
    const nextGroupInfos: InfiniteGridGroup[] = this.groupManager.getGroups();
1✔
359
    const rightGroup = nextGroupInfos[groupIndex];
1✔
360

361
    if (!rightGroup) {
1!
362
      return this.append(items, groupKey);
×
363
    }
364
    const nextItemInfos: InfiniteGridItemInfo[] = this.groupManager.getGroupItems();
1✔
365
    const rightGroupKey = rightGroup.groupKey;
1✔
366
    const rightItemIndex = findIndex(nextItemInfos, (item) => item.groupKey === rightGroupKey);
4✔
367

368
    return this.insert(rightItemIndex, items, groupKey);
1✔
369
  }
370
  /**
371
   * Returns the current state of a module such as location information. You can use the setStatus() method to restore the information returned through a call to this method.
372
   * @ko 아이템의 위치 정보 등 모듈의 현재 상태 정보를 반환한다. 이 메서드가 반환한 정보를 저장해 두었다가 setStatus() 메서드로 복원할 수 있다
373
   * @param - STATUS_TYPE.NOT_REMOVE = Get all information about items. STATUS_TYPE.REMOVE_INVISIBLE_ITEMS = Get information on visible items only. STATUS_TYPE.MINIMIZE_INVISIBLE_ITEMS = Compress invisible items. You can replace it with a placeholder. STATUS_TYPE.MINIMIZE_INVISIBLE_GROUPS = Compress invisible groups. <ko> STATUS_TYPE.NOT_REMOVE = 모든 아이템들의 정보를 가져온다. STATUS_TYPE.REMOVE_INVISIBLE_ITEMS = 보이는 아이템들의 정보만 가져온다. STATUS_TYPE.MINIMIZE_INVISIBLE_ITEMS = 안보이는 아이템들을 압축한다. placeholder로 대체가 가능하다. STATUS_TYPE.MINIMIZE_INVISIBLE_GROUPS = 안보이는 그룹을 압축한다.</ko>
374
   * @param - Whether to include items corresponding to placeholders. <ko>placeholder에 해당하는 아이템들을 포함할지 여부.</ko>
375
   */
376
  public getStatus(type?: STATUS_TYPE, includePlaceholders?: boolean): InfiniteGridStatus {
1✔
377
    return {
15✔
378
      containerManager: this.containerManager.getStatus(),
379
      itemRenderer: this.itemRenderer.getStatus(),
380
      groupManager: this.groupManager.getGroupStatus(type, includePlaceholders),
381
      scrollManager: this.scrollManager.getStatus(),
382
    };
383
  }
384

385
  /**
386
   * You can set placeholders to restore status or wait for items to be added.
387
   * @ko status 복구 또는 아이템 추가 대기를 위한 placeholder를 설정할 수 있다.
388
   * @param - The placeholder status. <ko>placeholder의 status</ko>
389
   */
390
  public setPlaceholder(info: Partial<InfiniteGridItemStatus> | null): this {
1✔
391
    this.groupManager.setPlaceholder(info);
8✔
392
    return this;
8✔
393
  }
394
  /**
395
   * You can set placeholders to restore status or wait for items to be added.
396
   * @ko status 복구 또는 아이템 추가 대기를 위한 placeholder를 설정할 수 있다.
397
   * @param - The placeholder status. <ko>placeholder의 status</ko>
398
   */
399
  public setLoading(info: Partial<InfiniteGridItemStatus> | null): this {
1✔
400
    this.groupManager.setLoading(info);
4✔
401
    return this;
4✔
402
  }
403
  /**
404
   * Add the placeholder at the end.
405
   * @ko placeholder들을 마지막에 추가한다.
406
   * @param - Items that correspond to placeholders. If it is a number, it duplicates the number of copies. <ko>placeholder에 해당하는 아이템들. 숫자면 갯수만큼 복제를 한다.</ko>
407
   * @param - The group key to be configured in items. It is automatically generated by default. <ko>추가할 아이템에 설정할 그룹 키. 생략하면 값이 자동으로 생성된다.</ko>
408
   */
409
  public appendPlaceholders(
3✔
410
    items: number | InfiniteGridItemStatus[],
411
    groupKey?: string | number,
412
  ): InsertedPlaceholdersResult {
413
    const result = this.groupManager.appendPlaceholders(items, groupKey);
3✔
414

415
    this._syncGroups(true);
3✔
416
    return {
3✔
417
      ...result,
418
      remove: () => {
419
        this.removePlaceholders({ groupKey: result.group.groupKey });
×
420
      },
421
    };
422
  }
423
  /**
424
   * Add the placeholder at the start.
425
   * @ko placeholder들을 처음에 추가한다.
426
   * @param - Items that correspond to placeholders. If it is a number, it duplicates the number of copies. <ko>placeholder에 해당하는 아이템들. 숫자면 갯수만큼 복제를 한다.</ko>
427
   * @param - The group key to be configured in items. It is automatically generated by default. <ko>추가할 아이템에 설정할 그룹 키. 생략하면 값이 자동으로 생성된다.</ko>
428
   */
429
  public prependPlaceholders(
1✔
430
    items: number | InfiniteGridItemStatus[],
431
    groupKey?: string | number,
432
  ): InsertedPlaceholdersResult {
433
    const result = this.groupManager.prependPlaceholders(items, groupKey);
×
434

435
    this._syncGroups(true);
×
436
    return {
×
437
      ...result,
438
      remove: () => {
439
        this.removePlaceholders({ groupKey: result.group.groupKey });
×
440
      },
441
    };
442
  }
443

444
  /**
445
   * Remove placeholders
446
   * @ko placeholder들을 삭제한다.
447
   * @param type - Remove the placeholders corresponding to the groupkey. When "start" or "end", remove all placeholders in that direction. <ko>groupkey에 해당하는 placeholder들을 삭제한다. "start" 또는 "end" 일 때 해당 방향의 모든 placeholder들을 삭제한다.</ko>
448
   */
449
  public removePlaceholders(type: "start" | "end" | { groupKey: string | number }) {
1✔
450
    this.groupManager.removePlaceholders(type);
1✔
451
    this._syncGroups(true);
1✔
452
  }
453

454
  /**
455
   * Sets the status of the InfiniteGrid module with the information returned through a call to the getStatus() method.
456
   * @ko getStatus() 메서드가 저장한 정보로 InfiniteGrid 모듈의 상태를 설정한다.
457
   * @param - status object of the InfiniteGrid module. <ko>InfiniteGrid 모듈의 status 객체.</ko>
458
   * @param - Whether the first rendering has already been done. <ko>첫 렌더링이 이미 되어있는지 여부.</ko>
459
   */
460
  public setStatus(status: InfiniteGridStatus, useFirstRender?: boolean): this {
1✔
461
    this.itemRenderer.setStatus(status.itemRenderer);
13✔
462
    this.containerManager.setStatus(status.containerManager);
13✔
463
    this.scrollManager.setStatus(status.scrollManager);
13✔
464
    const groupManager = this.groupManager;
13✔
465
    const prevInlineSize = this.containerManager.getInlineSize();
13✔
466

467
    groupManager.setGroupStatus(status.groupManager);
13✔
468
    this._syncInfinite();
13✔
469
    this.infinite.setCursors(groupManager.getStartCursor(), groupManager.getEndCursor());
13✔
470

471
    this._getRenderer().updateKey();
13✔
472

473
    const state = {
13✔
474
      isResize: this.containerManager.getInlineSize() !== prevInlineSize,
475
      isRestore: true,
476
    };
477
    if (useFirstRender) {
13✔
478
      this._syncItems(state);
1✔
479
    } else {
480
      this._update(state);
12✔
481
    }
482
    return this;
13✔
483
  }
484
  /**
485
   * Removes the group corresponding to index.
486
   * @ko index에 해당하는 그룹을 제거 한다.
487
   */
488
  public removeGroupByIndex(index: number): this {
1✔
489
    const nextGroups = this.getGroups();
2✔
490

491
    return this.removeGroupByKey(nextGroups[index].groupKey);
2✔
492
  }
493
  /**
494
   * Removes the group corresponding to key.
495
   * @ko key에 해당하는 그룹을 제거 한다.
496
   */
497
  public removeGroupByKey(key: number | string): this {
1✔
498
    const nextItemInfos = this.getItems();
3✔
499

500
    const firstIndex = findIndex(nextItemInfos, (item) => item.groupKey === key);
3✔
501
    const lastIndex = findLastIndex(nextItemInfos, (item) => item.groupKey === key);
18✔
502

503
    if (firstIndex === -1) {
3!
504
      return this;
×
505
    }
506
    nextItemInfos.splice(firstIndex, lastIndex - firstIndex + 1);
3✔
507
    return this.syncItems(nextItemInfos);
3✔
508
  }
509
  /**
510
   * Removes the item corresponding to index.
511
   * @ko index에 해당하는 아이템을 제거 한다.
512
   */
513
  public removeByIndex(index: number): this {
1✔
514
    const nextItemInfos = this.getItems(true);
4✔
515

516
    nextItemInfos.splice(index, 1);
4✔
517

518
    return this.syncItems(nextItemInfos);
4✔
519
  }
520
  /**
521
   * Removes the item corresponding to key.
522
   * @ko key에 해당하는 아이템을 제거 한다.
523
   */
524
  public removeByKey(key: string | number): this {
1✔
525
    const nextItemInfos = this.getItems(true);
2✔
526
    const index = findIndex(nextItemInfos, (item) => item.key === key);
6✔
527

528
    return this.removeByIndex(index);
2✔
529
  }
530
  /**
531
   * Update the size of the items and render them.
532
   * @ko 아이템들의 사이즈를 업데이트하고 렌더링을 한다.
533
   * @param - Items to be updated. <ko>업데이트할 아이템들.</ko>
534
   * @param - Options for rendering. <ko>렌더링을 하기 위한 옵션.</ko>
535
   */
536
  public updateItems(items?: InfiniteGridItem[], options: RenderOptions = {}) {
6!
537
    this.groupManager.updateItems(items, options);
3✔
538
    return this;
3✔
539
  }
540
  /**
541
   * Return all items of InfiniteGrid.
542
   * @ko InfiniteGrid의 모든 아이템들을 반환한다.
543
   * @param - Whether to include items corresponding to placeholders. <ko>placeholder에 해당하는 아이템들을 포함할지 여부.</ko>
544
   */
545
  public getItems(includePlaceholders?: boolean): InfiniteGridItem[] {
1✔
546
    return this.groupManager.getGroupItems(includePlaceholders);
39✔
547
  }
548
  /**
549
   * Return visible items of InfiniteGrid.
550
   * @ko InfiniteGrid의 보이는 아이템들을 반환한다.
551
   * @param - Whether to include items corresponding to placeholders. <ko>placeholder에 해당하는 아이템들을 포함할지 여부.</ko>
552
   */
553
  public getVisibleItems(includePlaceholders?: boolean): InfiniteGridItem[] {
1✔
554
    return this.groupManager.getVisibleItems(includePlaceholders);
143✔
555
  }
556

557
  /**
558
   * Return rendering items of InfiniteGrid.
559
   * @ko InfiniteGrid의 렌더링 아이템들을 반환한다.
560
   */
561
  public getRenderingItems(): InfiniteGridItem[] {
1✔
562
    return this.groupManager.getRenderingItems();
221✔
563
  }
564
  /**
565
   * Return all groups of InfiniteGrid.
566
   * @ko InfiniteGrid의 모든 그룹들을 반환한다.
567
   * @param - Whether to include groups corresponding to placeholders. <ko>placeholder에 해당하는 그룹들을 포함할지 여부.</ko>
568
   */
569
  public getGroups(includePlaceholders?: boolean): InfiniteGridGroup[] {
1✔
570
    return this.groupManager.getGroups(includePlaceholders);
241✔
571
  }
572
  /**
573
   * Return visible groups of InfiniteGrid.
574
   * @ko InfiniteGrid의 보이는 그룹들을 반환한다.
575
   * @param - Whether to include groups corresponding to placeholders. <ko>placeholder에 해당하는 그룹들을 포함할지 여부.</ko>
576
   */
577
  public getVisibleGroups(includePlaceholders?: boolean): InfiniteGridGroup[] {
1✔
578
    return this.groupManager.getVisibleGroups(includePlaceholders);
167✔
579
  }
580
  /**
581
   * Set to wait to request data.
582
   * @ko 데이터를 요청하기 위해 대기 상태로 설정한다.
583
   * @param direction - direction in which data will be added. <ko>데이터를 추가하기 위한 방향.</ko>
584
   */
585
  public wait(direction: "start" | "end" = DIRECTION.END) {
6✔
586
    this._waitType = direction;
4✔
587
    this._checkStartLoading(direction);
4✔
588
  }
589
  /**
590
   * When the data request is complete, it is set to ready state.
591
   * @ko 데이터 요청이 끝났다면 준비 상태로 설정한다.
592
   */
593
  public ready() {
1✔
594
    this._waitType = "";
1✔
595
  }
596
  /**
597
   * Returns whether it is set to wait to request data.
598
   * @ko 데이터를 요청하기 위해 대기 상태로 설정되어 있는지 여부를 반환한다.
599
   */
600
  public isWait() {
1✔
601
    return !!this._waitType;
×
602
  }
603
  /**
604
   * Releases the instnace and events and returns the CSS of the container and elements.
605
   * @ko 인스턴스와 이벤트를 해제하고 컨테이너와 엘리먼트들의 CSS를 되돌린다.
606
   */
607
  public destroy(): void {
1✔
608
    this.off();
60✔
609
    this._getRenderer().destroy();
60✔
610
    this.containerManager.destroy();
60✔
611
    this.groupManager.destroy();
60✔
612
    this.scrollManager.destroy();
60✔
613
    this.infinite.destroy();
60✔
614
  }
615

616
  private _getRenderer() {
1✔
617
    return this.options.renderer!;
418✔
618
  }
619
  private _getRendererItems() {
1✔
620
    return this.getRenderingItems().map((item) => {
191✔
621
      return {
1,131✔
622
        element: item.element,
623
        key: `${item.type}_${item.key}`,
624
        orgItem: item,
625
      };
626
    });
627
  }
628
  private _syncItems(state?: Record<string, any>): void {
1✔
629
    this._getRenderer().syncItems(this._getRendererItems(), state);
5✔
630
  }
631
  private _render(state?: Record<string, any>): void {
1✔
632
    this._getRenderer().render(this._getRendererItems(), state);
186✔
633
  }
634
  private _update(state: Record<string, any> = {}): void {
288✔
635
    this._getRenderer().update(state);
150✔
636
  }
637
  private _resizeScroll() {
1✔
638
    const scrollManager = this.scrollManager;
23✔
639

640
    scrollManager.resize();
23✔
641

642
    this.infinite.setSize(scrollManager.getContentSize());
23✔
643
  }
644
  private _syncGroups(isUpdate?: boolean) {
1✔
645
    const infinite = this.infinite;
82✔
646
    const scrollManager = this.scrollManager;
82✔
647

648
    if (!scrollManager.getContentSize()) {
82!
649
      this._resizeScroll();
×
650
    }
651
    this._syncInfinite();
82✔
652
    this.groupManager.setCursors(infinite.getStartCursor(), infinite.getEndCursor());
82✔
653
    if (isUpdate) {
82✔
654
      this._update();
4✔
655
    } else {
656
      this._render();
78✔
657
    }
658
  }
659
  private _syncInfinite() {
1✔
660
    this.infinite.syncItems(this.getGroups(true).map(({ groupKey, grid, type }) => {
227✔
661
      const outlines = grid.getOutlines();
729✔
662

663
      return {
729✔
664
        key: groupKey,
665
        isVirtual: type === GROUP_TYPE.VIRTUAL,
666
        startOutline: outlines.start,
667
        endOutline: outlines.end,
668
      };
669
    }));
670
  }
671
  private _scroll() {
1✔
672
    this.infinite.scroll(this.scrollManager.getRelativeScrollPos());
204✔
673
  }
674
  private _onScroll = ({ direction, scrollPos, relativeScrollPos }: OnChangeScroll): void => {
60✔
675
    this._scroll();
17✔
676
    /**
677
     * This event is fired when scrolling.
678
     * @ko 스크롤하면 발생하는 이벤트이다.
679
     * @event InfiniteGrid#changeScroll
680
     * @param {InfiniteGrid.OnChangeScroll} e - The object of data to be sent to an event <ko>이벤트에 전달되는 데이터 객체</ko>
681
     */
682
    this.trigger(new ComponentEvent(INFINITEGRID_EVENTS.CHANGE_SCROLL, {
17✔
683
      direction,
684
      scrollPos,
685
      relativeScrollPos,
686
    }));
687
  }
688

689
  private _onChange = (e: OnInfiniteChange): void => {
60✔
690
    this.setCursors(e.nextStartCursor, e.nextEndCursor);
91✔
691
  }
692
  private _onRendererUpdated = (e: OnRendererUpdated<GridRendererItem>): void => {
60✔
693
    if (!e.isChanged) {
190✔
694
      this._checkEndLoading();
61✔
695
      this._scroll();
61✔
696
      return;
61✔
697
    }
698
    const renderedItems = e.items;
129✔
699

700
    const {
129✔
701
      added,
702
      removed,
703
      prevList,
704
      list,
705
    } = e.diffResult;
706

707
    removed.forEach((index) => {
129✔
708
      const orgItem = prevList[index].orgItem;
288✔
709

710
      if (orgItem.mountState !== MOUNT_STATE.UNCHECKED) {
288✔
711
        orgItem.mountState = MOUNT_STATE.UNMOUNTED;
286✔
712
      }
713
    });
714

715
    renderedItems.forEach((item) => {
129✔
716
      // set grid element
717
      const gridItem = item.orgItem;
1,039✔
718

719
      gridItem.element = item.element as HTMLElement;
1,039✔
720
    });
721

722
    const horizontal = this.options.horizontal;
129✔
723
    const addedItems = added.map((index) => {
129✔
724
      const gridItem = list[index].orgItem;
700✔
725
      const element = gridItem.element!;
700✔
726

727
      if (gridItem.type === ITEM_TYPE.VIRTUAL) {
700✔
728
        const cssRect = { ...gridItem.cssRect };
17✔
729
        const rect = gridItem.rect;
17✔
730

731
        if (!cssRect.width && rect.width) {
17!
732
          cssRect.width = rect.width;
×
733
        }
734
        if (!cssRect.height && rect.height) {
17!
735
          cssRect.height = rect.height;
×
736
        }
737
        // virtual item
738
        return new GridItem(horizontal!, {
17✔
739
          element,
740
          cssRect,
741
        });
742
      }
743
      return gridItem;
683✔
744
    });
745

746
    const containerManager = this.containerManager;
129✔
747
    if (this.options.observeChildren) {
129✔
748
      containerManager.observeChildren(added.map((index) => list[index].element!));
21✔
749
      containerManager.unobserveChildren(removed.map((index) => prevList[index].element!));
9✔
750
    }
751

752
    const {
129✔
753
      isRestore,
754
      isResize,
755
    } = e.state;
756

757
    this.itemRenderer.renderItems(addedItems);
129✔
758

759
    if (isRestore) {
129✔
760
      this._onRenderComplete({
13✔
761
        mounted: added.map((index) => list[index].orgItem),
116✔
762
        updated: [],
763
        isResize: false,
764
        direction: this.defaultDirection,
765
      });
766
    }
767
    if (!isRestore || isResize || e.isItemChanged) {
129!
768
      this.groupManager.renderItems();
129✔
769
    }
770
  }
771

772
  private _onResize = (e: ResizeWatcherResizeEvent) => {
60✔
773
    if (e.isResizeContainer) {
3✔
774
      this._renderItems({ useResize: true }, true);
1✔
775
    } else {
776
      const updatedItems = getUpdatedItems(this.getVisibleItems(), e.childEntries) as InfiniteGridItem[];
2✔
777

778
      if (updatedItems.length > 0) {
2!
779
        this.updateItems(updatedItems);
2✔
780
      }
781
    }
782
  }
783

784
  private _onRequestAppend = (e: OnRequestInsert): void => {
60✔
785
    /**
786
     * The event is fired when scrolling reaches the end or when data for a virtual group is required.
787
     * @ko 스크롤이 끝에 도달하거나 virtual 그룹에 대한 데이터가 필요한 경우 이벤트가 발생한다.
788
     * @event InfiniteGrid#requestAppend
789
     * @param {InfiniteGrid.OnRequestAppend} e - The object of data to be sent to an event <ko>이벤트에 전달되는 데이터 객체</ko>
790
     */
791
    this._onRequestInsert(DIRECTION.END, INFINITEGRID_EVENTS.REQUEST_APPEND, e);
58✔
792
  }
793

794
  private _onRequestPrepend = (e: OnInfiniteRequestPrepend): void => {
60✔
795
    /**
796
     * The event is fired when scrolling reaches the start or when data for a virtual group is required.
797
     * @ko 스크롤이 끝에 도달하거나 virtual 그룹에 대한 데이터가 필요한 경우 이벤트가 발생한다.
798
     * @event InfiniteGrid#requestPrepend
799
     * @param {InfiniteGrid.OnRequestPrepend} e - The object of data to be sent to an event <ko>이벤트에 전달되는 데이터 객체</ko>
800
     */
801
    this._onRequestInsert(DIRECTION.START, INFINITEGRID_EVENTS.REQUEST_PREPEND, e);
53✔
802
  }
803

804
  private _onRequestInsert(
111✔
805
    direction: "start" | "end",
806
    eventType: "requestAppend" | "requestPrepend",
807
    e: OnInfiniteRequestAppend | OnInfiniteRequestPrepend,
808
  ) {
809
    if (this._waitType) {
111✔
810
      this._checkStartLoading(this._waitType);
7✔
811
      return;
7✔
812
    }
813
    this.trigger(new ComponentEvent(eventType, {
104✔
814
      groupKey: e.key,
815
      nextGroupKey: e.nextKey,
816
      nextGroupKeys: e.nextKeys || [],
197✔
817
      isVirtual: e.isVirtual,
818
      wait: () => {
819
        this.wait(direction);
×
820
      },
821
      ready: () => {
822
        this.ready();
×
823
      },
824
    }));
825
  }
826

827
  private _onContentError = ({ element, target, item, update }: OnContentError): void => {
60✔
828
    /**
829
     * The event is fired when scrolling reaches the start or when data for a virtual group is required.
830
     * @ko 스크롤이 끝에 도달하거나 virtual 그룹에 대한 데이터가 필요한 경우 이벤트가 발생한다.
831
     * @event InfiniteGrid#contentError
832
     * @param {InfiniteGrid.OnContentError} e - The object of data to be sent to an event <ko>이벤트에 전달되는 데이터 객체</ko>
833
     */
834
    this.trigger(new ComponentEvent(INFINITEGRID_EVENTS.CONTENT_ERROR, {
2✔
835
      element,
836
      target,
837
      item: item as InfiniteGridItem,
838
      update,
839
      remove: () => {
840
        this.removeByKey(item.key!);
1✔
841
      },
842
    }));
843
  }
844

845
  private _onRenderComplete = ({ isResize, mounted, updated, direction }: OnPickedRenderComplete): void => {
60✔
846
    const infinite = this.infinite;
128✔
847
    const prevRenderedGroups = infinite.getRenderedVisibleItems();
128✔
848
    const length = prevRenderedGroups.length;
128✔
849
    const isDirectionEnd = direction === DIRECTION.END;
128✔
850

851
    this._syncInfinite();
128✔
852

853
    if (length) {
128✔
854
      const prevStandardGroup = prevRenderedGroups[isDirectionEnd ? 0 : length - 1];
71!
855
      const nextStandardGroup = infinite.getItemByKey(prevStandardGroup.key);
71✔
856
      const offset = isDirectionEnd
71✔
857
        ? Math.min(...nextStandardGroup.startOutline) - Math.min(...prevStandardGroup.startOutline)
71!
858
        : Math.max(...nextStandardGroup.endOutline) - Math.max(...prevStandardGroup.endOutline);
859

860
      this.scrollManager.scrollBy(offset);
71✔
861
    }
862

863
    /**
864
     * This event is fired when the InfiniteGrid has completed rendering.
865
     * @ko InfiniteGrid가 렌더링이 완료됐을 때 이벤트가 발생한다.
866
     * @event InfiniteGrid#renderComplete
867
     * @param {InfiniteGrid.OnRenderComplete} e - The object of data to be sent to an event <ko>이벤트에 전달되는 데이터 객체</ko>
868
     */
869
    this.trigger(new ComponentEvent(INFINITEGRID_EVENTS.RENDER_COMPLETE, {
128✔
870
      isResize,
871
      direction,
872
      mounted: (mounted as InfiniteGridItem[]).filter((item) => item.type !== ITEM_TYPE.LOADING),
657✔
873
      updated: (updated as InfiniteGridItem[]).filter((item) => item.type !== ITEM_TYPE.LOADING),
538✔
874
      startCursor: this.getStartCursor(),
875
      endCursor: this.getEndCursor(),
876
      items: this.getVisibleItems(true),
877
      groups: this.getVisibleGroups(true),
878
    }));
879

880
    if (this.groupManager.shouldRerenderItems()) {
128✔
881
      this._update();
2✔
882
    } else {
883
      this._checkEndLoading();
126✔
884
      this._scroll();
126✔
885
    }
886
  }
887
  private _renderItems(options: RenderOptions = {}, isTrusted?: boolean) {
23!
888
    if (!isTrusted && options.useResize) {
23!
889
      this.containerManager.resize();
×
890
    }
891
    this._resizeScroll();
23✔
892
    if (!this.getRenderingItems().length) {
23✔
893
      const children = toArray(this.getContainerElement().children);
5✔
894
      if (children.length > 0) {
5✔
895
        // no items, but has children
896
        this.groupManager.syncItems(convertInsertedItems(children));
4✔
897
        this._syncInfinite();
4✔
898
        this.setCursors(0, 0, true);
4✔
899
        this._getRenderer().updated();
4✔
900
      } else {
901
        this.infinite.scroll(0);
1✔
902
      }
903
      return this;
5✔
904
    }
905
    if (!this.getVisibleGroups(true).length) {
18!
906
      this.setCursors(0, 0);
×
907
    } else {
908
      this.groupManager.renderItems(options);
18✔
909
    }
910
    return this;
18✔
911
  }
912
  private _checkStartLoading(direction: "start" | "end") {
1✔
913
    const groupManager = this.groupManager;
11✔
914
    const infinite = this.infinite;
11✔
915

916
    if (
11✔
917
      !groupManager.getLoadingType()
26✔
918
      && infinite.isLoading(direction)
919
      && groupManager.startLoading(direction)
920
      && groupManager.hasLoadingItem()
921
    ) {
922
      this._update();
2✔
923
    }
924
  }
925
  private _checkEndLoading() {
1✔
926
    const groupManager = this.groupManager;
317✔
927
    const loadingType = this.groupManager.getLoadingType();
317✔
928

929
    if (
317!
930
      loadingType
331✔
931
      && (!this._waitType || !this.infinite.isLoading(loadingType))
932
      && groupManager.endLoading()
933
      && groupManager.hasLoadingItem()
934
    ) {
935
      this._update();
×
936
    }
937
  }
938
}
1✔
939

940
interface InfiniteGrid extends Properties<typeof InfiniteGrid> { }
941

942
export default InfiniteGrid;
1✔
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