• 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

88.36
/packages/infinitegrid/src/GroupManager.ts
1
import Grid, {
1✔
2
  GetterSetter,
3
  GridFunction, GridOptions,
4
  GridOutlines, MOUNT_STATE, Properties, PROPERTY_TYPE,
5
  RenderOptions, UPDATE_STATE,
6
} from "@egjs/grid";
7
import { GROUP_TYPE, ITEM_TYPE, STATUS_TYPE } from "./consts";
1✔
8
import { InfiniteGridItem, InfiniteGridItemStatus } from "./InfiniteGridItem";
1✔
9
import { LoadingGrid, LOADING_GROUP_KEY } from "./LoadingGrid";
1✔
10
import { CategorizedGroup, InfiniteGridGroup, InfiniteGridItemInfo } from "./types";
11
import {
1✔
12
  categorize, filterVirtuals, findIndex, findLastIndex,
13
  flat,
14
  flatGroups, getItemInfo, isNumber, makeKey,
15
  range,
16
  setPlaceholder,
17
  splitGridOptions, splitOptions, splitVirtualGroups,
18
} from "./utils";
19

20
export interface InfiniteGridGroupStatus {
21
  type: GROUP_TYPE;
22
  groupKey: string | number;
23
  items: InfiniteGridItemStatus[];
24
  outlines: GridOutlines;
25
}
26

27
export interface GroupManagerOptions extends GridOptions {
28
  gridConstructor: GridFunction | null;
29
  gridOptions: Record<string, any>;
30
}
31

32
export interface GroupManagerStatus {
33
  cursors: [number, number];
34
  orgCursors: [number, number];
35
  itemCursors: [number, number];
36
  startGroupKey: number | string;
37
  endGroupKey: number | string;
38
  groups: InfiniteGridGroupStatus[];
39
  outlines: GridOutlines;
40
}
41

42
@GetterSetter
43
export class GroupManager extends Grid<GroupManagerOptions> {
1✔
44
  public static defaultOptions: Required<GroupManagerOptions> = {
1✔
45
    ...Grid.defaultOptions,
46
    gridConstructor: null,
47
    gridOptions: {},
48
  };
49
  public static propertyTypes = {
1✔
50
    ...Grid.propertyTypes,
51
    gridConstructor: PROPERTY_TYPE.PROPERTY,
52
    gridOptions: PROPERTY_TYPE.PROPERTY,
53
  } as const;
54
  protected items: InfiniteGridItem[];
55
  protected groupItems: InfiniteGridItem[] = [];
65✔
56
  protected groups: InfiniteGridGroup[] = [];
65✔
57
  protected itemKeys: Record<string | number, InfiniteGridItem> = {};
65✔
58
  protected groupKeys: Record<string | number, InfiniteGridGroup> = {};
65✔
59
  protected startCursor = 0;
65✔
60
  protected endCursor = 0;
65✔
61
  private _placeholder: Partial<InfiniteGridItemStatus> | null = null;
65✔
62
  private _loadingGrid!: LoadingGrid;
63
  private _mainGrid!: Grid;
64

65
  constructor(container: HTMLElement, options: GroupManagerOptions) {
66✔
66
    super(container, splitOptions(options));
65!
67

68
    this._loadingGrid = new LoadingGrid(container, {
65✔
69
      externalContainerManager: this.containerManager,
70
      useFit: false,
71
      autoResize: false,
72
      renderOnPropertyChange: false,
73
      gap: this.gap,
74
    });
75
    this._mainGrid = this._makeGrid();
65✔
76
  }
77
  public set gridOptions(options: Record<string, any>) {
1✔
78
    const {
2✔
79
      gridOptions,
80
      ...otherOptions
81
    } = splitGridOptions(options);
82

83
    const shouldRender = this._checkShouldRender(options);
2✔
84
    this.options.gridOptions = {
2✔
85
      ...this.options.gridOptions,
86
      ...gridOptions,
87
    };
88
    [this._mainGrid, ...this.groups.map(({ grid }) => grid)].forEach((grid) => {
4✔
89
      for (const name in options) {
6✔
90
        (grid as any)[name] = options[name];
6✔
91
      }
92
    });
93
    for (const name in otherOptions) {
2✔
94
      this[name] = otherOptions[name];
2✔
95
    }
96

97
    this._loadingGrid.gap = this.gap;
2✔
98
    if (shouldRender) {
2!
99
      this.scheduleRender();
2✔
100
    }
101
  }
102

103
  public getItemByKey(key: string | number): InfiniteGridItem | null {
1✔
104
    return this.itemKeys[key] || null;
×
105
  }
106

107
  public getGroupItems(includePlaceholders?: boolean) {
1✔
108
    return filterVirtuals(this.groupItems, includePlaceholders);
82✔
109
  }
110
  public getVisibleItems(includePlaceholders?: boolean) {
1✔
111
    return filterVirtuals(this.items, includePlaceholders);
143✔
112
  }
113

114
  public getRenderingItems() {
1✔
115
    if (this.hasPlaceholder()) {
221✔
116
      return this.items;
25✔
117
    } else {
118
      return this.items.filter((item) => item.type !== ITEM_TYPE.VIRTUAL);
1,157✔
119
    }
120
  }
121

122
  public getGroups(includePlaceholders?: boolean): InfiniteGridGroup[] {
1✔
123
    return filterVirtuals(this.groups, includePlaceholders);
360✔
124
  }
125

126
  public hasVisibleVirtualGroups() {
1✔
127
    return this.getVisibleGroups(true).some((group) => group.type === GROUP_TYPE.VIRTUAL);
×
128
  }
129
  public hasPlaceholder() {
1✔
130
    return !!this._placeholder;
221✔
131
  }
132
  public hasLoadingItem() {
1✔
133
    return !!this._getLoadingItem();
6✔
134
  }
135

136
  public updateItems(items = this.groupItems, options?: RenderOptions) {
4✔
137
    return super.updateItems(items, options);
3✔
138
  }
139
  public setPlaceholder(placeholder: Partial<InfiniteGridItemStatus> | null) {
1✔
140
    this._placeholder = placeholder;
8✔
141
    this._updatePlaceholder();
8✔
142
  }
143

144
  public getLoadingType() {
1✔
145
    return this._loadingGrid.type;
328✔
146
  }
147

148
  public startLoading(type: "start" | "end") {
1✔
149
    this._loadingGrid.type = type;
5✔
150
    this.items = this._getRenderingItems();
5✔
151

152
    return true;
5✔
153
  }
154

155
  public endLoading() {
1✔
156
    const prevType = this._loadingGrid.type;
1✔
157

158
    this._loadingGrid.type = "";
1✔
159
    this.items = this._getRenderingItems();
1✔
160
    return !!prevType;
1✔
161
  }
162

163
  public setLoading(loading: Partial<InfiniteGridItemStatus> | null) {
1✔
164
    this._loadingGrid.setLoadingItem(loading);
4✔
165
    this.items = this._getRenderingItems();
4✔
166
  }
167

168
  public getVisibleGroups(includePlaceholders?: boolean): InfiniteGridGroup[] {
1✔
169
    const groups = this.groups.slice(this.startCursor, this.endCursor + 1);
545✔
170

171
    return filterVirtuals(groups, includePlaceholders);
545✔
172
  }
173

174
  public getComputedOutlineLength(items = this.items) {
121!
175
    return this._mainGrid.getComputedOutlineLength(items);
121✔
176
  }
177
  public getComputedOutlineSize(items = this.items) {
121!
178
    return this._mainGrid.getComputedOutlineSize(items);
121✔
179
  }
180

181
  public applyGrid(items: InfiniteGridItem[], direction: "end" | "start", outline: number[]): GridOutlines {
121✔
182
    const renderingGroups = this.groups.slice();
121✔
183

184
    if (!renderingGroups.length) {
121!
185
      return {
×
186
        start: [],
187
        end: [],
188
      };
189
    }
190

191

192
    const loadingGrid = this._loadingGrid;
121✔
193

194
    if (loadingGrid.getLoadingItem()) {
121✔
195
      if (loadingGrid.type === "start") {
3!
196
        renderingGroups.unshift(this._getLoadingGroup());
×
197
      } else if (loadingGrid.type === "end") {
3✔
198
        renderingGroups.push(this._getLoadingGroup());
2✔
199
      }
200
    }
201

202
    const groups = renderingGroups.slice();
121✔
203

204
    let nextOutline = outline;
121✔
205

206
    if (direction === "start") {
121✔
207
      groups.reverse();
1✔
208
    }
209

210
    const groupItems = this.groupItems;
121✔
211
    const outlineLength = this.getComputedOutlineLength(groupItems);
121✔
212
    const outlineSize = this.getComputedOutlineSize(groupItems);
121✔
213

214
    groups.forEach((group) => {
121✔
215
      const grid = group.grid;
396✔
216
      const gridItems = grid.getItems();
396✔
217
      const isVirtual = group.type === GROUP_TYPE.VIRTUAL && !gridItems[0];
396✔
218
      const appliedItems = gridItems.filter((item) => item.mountState !== MOUNT_STATE.UNCHECKED && item.rect.width);
1,259✔
219
      let gridOutlines: GridOutlines;
396✔
220

221
      grid.outlineLength = outlineLength;
396✔
222
      grid.outlineSize = outlineSize;
396✔
223

224
      if (isVirtual) {
396✔
225
        gridOutlines = this._applyVirtualGrid(grid, direction, nextOutline);
2✔
226
      } else if (appliedItems.length) {
394✔
227
        gridOutlines = grid.applyGrid(appliedItems, direction, nextOutline);
366✔
228
      } else {
229
        gridOutlines = {
28✔
230
          start: [...nextOutline],
231
          end: [...nextOutline],
232
        };
233
      }
234
      grid.setOutlines(gridOutlines);
396✔
235
      nextOutline = gridOutlines[direction];
396✔
236
    });
237

238
    return {
121✔
239
      start: renderingGroups[0].grid.getOutlines().start,
240
      end: renderingGroups[renderingGroups.length - 1].grid.getOutlines().end,
241
    };
242
  }
243

244
  public syncItems(nextItemInfos: InfiniteGridItemInfo[]) {
87✔
245
    const prevItemKeys = this.itemKeys;
87✔
246

247
    this.itemKeys = {};
87✔
248
    const nextItems = this._syncItemInfos(nextItemInfos.map((info) => getItemInfo(info)), prevItemKeys);
735✔
249
    const prevGroupKeys = this.groupKeys;
87✔
250
    let nextManagerGroups = categorize(nextItems);
87✔
251

252
    const startVirtualGroups = this._splitVirtualGroups("start", nextManagerGroups);
87✔
253
    const endVirtualGroups = this._splitVirtualGroups("end", nextManagerGroups);
87✔
254
    nextManagerGroups = [...startVirtualGroups, ...this._mergeVirtualGroups(nextManagerGroups), ...endVirtualGroups];
87✔
255

256
    const nextGroups: InfiniteGridGroup[] = nextManagerGroups.map(({ groupKey, items }) => {
87✔
257
      const isVirtual = !items[0] || items[0].type === ITEM_TYPE.VIRTUAL;
217✔
258
      const grid = prevGroupKeys[groupKey]?.grid ?? this._makeGrid();
217✔
259
      const gridItems = isVirtual ? items : items.filter(({ type }) => type === ITEM_TYPE.NORMAL);
741✔
260

261
      grid.setItems(gridItems);
217✔
262

263
      return {
217✔
264
        type: isVirtual ? GROUP_TYPE.VIRTUAL : GROUP_TYPE.NORMAL,
217✔
265
        groupKey,
266
        grid,
267
        items: gridItems,
268
        renderItems: items,
269
      };
270
    });
271

272
    this._registerGroups(nextGroups);
87✔
273
  }
274

275
  public renderItems(options: RenderOptions = {}) {
288✔
276
    if (options.useResize) {
153!
277
      this.groupItems.forEach((item) => {
×
278
        item.updateState = UPDATE_STATE.NEED_UPDATE;
×
279
      });
280
      const loadingItem = this._getLoadingItem();
×
281

282
      if (loadingItem) {
×
283
        loadingItem.updateState = UPDATE_STATE.NEED_UPDATE;
×
284
      }
285
    }
286
    return super.renderItems(options);
153✔
287
  }
288

289
  public setCursors(startCursor: number, endCursor: number) {
1✔
290
    this.startCursor = startCursor;
234✔
291
    this.endCursor = endCursor;
234✔
292
    this.items = this._getRenderingItems();
234✔
293
  }
294

295
  public getStartCursor() {
1✔
296
    return this.startCursor;
13✔
297
  }
298

299
  public getEndCursor() {
1✔
300
    return this.endCursor;
13✔
301
  }
302

303
  public getGroupStatus(type?: STATUS_TYPE, includePlaceholders?: boolean): GroupManagerStatus {
1✔
304
    const orgStartCursor = this.startCursor;
15✔
305
    const orgEndCursor = this.endCursor;
15✔
306
    const orgGroups = this.groups;
15✔
307
    const startGroup = orgGroups[orgStartCursor];
15✔
308
    const endGroup = orgGroups[orgEndCursor];
15✔
309

310
    let startCursor = orgStartCursor;
15✔
311
    let endCursor = orgEndCursor;
15✔
312

313
    const isMinimizeItems = type === STATUS_TYPE.MINIMIZE_INVISIBLE_ITEMS;
15✔
314
    const isMinimizeGroups = type === STATUS_TYPE.MINIMIZE_INVISIBLE_GROUPS;
15✔
315
    let groups: InfiniteGridGroup[];
15✔
316

317
    if (type === STATUS_TYPE.REMOVE_INVISIBLE_GROUPS) {
15✔
318
      groups = this.getVisibleGroups(includePlaceholders);
1✔
319
      endCursor = groups.length - 1;
1✔
320
      startCursor = 0;
1✔
321
    } else {
322
      groups = this.getGroups(includePlaceholders);
14✔
323

324
      if (!includePlaceholders) {
14✔
325
        startCursor = -1;
12✔
326
        endCursor = -1;
12✔
327

328
        for (let orgIndex = orgStartCursor; orgIndex <= orgEndCursor; ++orgIndex) {
12✔
329
          const orgGroup = orgGroups[orgIndex];
12✔
330

331
          if (orgGroup && orgGroup.type !== GROUP_TYPE.VIRTUAL) {
12!
332
            startCursor = groups.indexOf(orgGroup);
12✔
333
            break;
12✔
334
          }
335
        }
336
        for (let orgIndex = orgEndCursor; orgIndex >= orgStartCursor; --orgIndex) {
12✔
337
          const orgGroup = orgGroups[orgIndex];
12✔
338

339
          if (orgGroup && orgGroup.type !== GROUP_TYPE.VIRTUAL) {
12!
340
            endCursor = groups.lastIndexOf(orgGroup);
12✔
341
            break;
12✔
342
          }
343
        }
344
      }
345
    }
346

347
    const groupStatus: InfiniteGridGroupStatus[] = groups.map(({ grid, groupKey }, i) => {
15✔
348
      const isOutsideCursor = i < startCursor || endCursor < i;
74✔
349
      const isVirtualItems = isMinimizeItems && isOutsideCursor;
74✔
350
      const isVirtualGroup = isMinimizeGroups && isOutsideCursor;
74✔
351
      const gridItems = grid.getItems() as InfiniteGridItem[];
74✔
352
      const items = isVirtualGroup
74✔
353
        ? []
74✔
354
        : gridItems.map((item) => isVirtualItems ? item.getVirtualStatus() : item.getMinimizedStatus());
215✔
355

356
      return {
74✔
357
        type: isVirtualGroup || isVirtualItems ? GROUP_TYPE.VIRTUAL : GROUP_TYPE.NORMAL,
218✔
358
        groupKey: groupKey,
359
        outlines: grid.getOutlines(),
360
        items,
361
      };
362
    });
363

364

365
    const totalItems = this.getGroupItems();
15✔
366

367
    const itemStartCursor = totalItems.indexOf(startGroup?.items[0]);
15!
368
    const itemEndCursor = totalItems.indexOf(endGroup?.items.slice().reverse()[0]);
15!
369

370
    return {
15✔
371
      cursors: [startCursor, endCursor],
372
      orgCursors: [orgStartCursor, orgEndCursor],
373
      itemCursors: [itemStartCursor, itemEndCursor],
374
      startGroupKey: startGroup?.groupKey,
45!
375
      endGroupKey: endGroup?.groupKey,
45!
376
      groups: groupStatus,
377
      outlines: this.outlines,
378
    };
379
  }
380
  protected fitOutlines(useFit = this.useFit) {
242!
381
    const groups = this.groups;
121✔
382
    const firstGroup = groups[0];
121✔
383

384
    if (!firstGroup) {
121!
385
      return;
×
386
    }
387
    const outlines = firstGroup.grid.getOutlines();
121✔
388
    const startOutline = outlines.start;
121✔
389
    const outlineOffset = startOutline.length ? Math.min(...startOutline) : 0;
121✔
390

391
    // If the outline is less than 0, a fit occurs forcibly.
392
    if (!useFit && outlineOffset > 0) {
121!
393
      return;
×
394
    }
395

396
    groups.forEach(({ grid }) => {
121✔
397
      const { start, end } = grid.getOutlines();
394✔
398

399
      grid.setOutlines({
394✔
400
        start: start.map((point) => point - outlineOffset),
445✔
401
        end: end.map((point) => point - outlineOffset),
445✔
402
      });
403
    });
404

405
    this.groupItems.forEach((item) => {
121✔
406
      const contentPos = item.cssContentPos;
1,257✔
407

408
      if (!isNumber(contentPos)) {
1,257!
409
        return;
×
410
      }
411
      item.cssContentPos = contentPos - outlineOffset;
1,257✔
412
    });
413
  }
414
  public setGroupStatus(status: GroupManagerStatus) {
13✔
415
    this.itemKeys = {};
13✔
416
    this.groupItems = [];
13✔
417
    this.items = [];
13✔
418
    const prevGroupKeys = this.groupKeys;
13✔
419

420
    const nextGroups: InfiniteGridGroup[] = status.groups.map(({
13✔
421
      type,
422
      groupKey,
423
      items,
424
      outlines,
425
    }) => {
426
      const nextItems = this._syncItemInfos(items);
66✔
427
      const grid = prevGroupKeys[groupKey]?.grid ?? this._makeGrid();
66✔
428

429
      grid.setOutlines(outlines);
66✔
430
      grid.setItems(nextItems);
66✔
431

432
      return {
66✔
433
        type,
434
        groupKey,
435
        grid,
436
        items: nextItems,
437
        renderItems: nextItems,
438
      };
439
    });
440

441
    this.setOutlines(status.outlines);
13✔
442
    this._registerGroups(nextGroups);
13✔
443
    this._updatePlaceholder();
13✔
444
    this.setCursors(status.cursors[0], status.cursors[1]);
13✔
445
  }
446
  public appendPlaceholders(items: number | InfiniteGridItemStatus[], groupKey?: string | number) {
1✔
447
    return this.insertPlaceholders("end", items, groupKey);
3✔
448
  }
449
  public prependPlaceholders(items: number | InfiniteGridItemStatus[], groupKey?: string | number) {
1✔
450
    return this.insertPlaceholders("start", items, groupKey);
×
451
  }
452
  public removePlaceholders(type: "start" | "end" | { groupKey: string | number }) {
1✔
453
    const groups = this.groups;
1✔
454
    const length = groups.length;
1✔
455

456
    if (type === "start") {
1!
457
      const index = findIndex(groups, (group) => group.type === GROUP_TYPE.NORMAL);
×
458

459
      groups.splice(0, index);
×
460

461
    } else if (type === "end") {
1!
462
      const index = findLastIndex(groups, (group) => group.type === GROUP_TYPE.NORMAL);
×
463

464
      groups.splice(index + 1, length - index - 1);
×
465
    } else {
466
      const groupKey = type.groupKey;
1✔
467

468
      const index = findIndex(groups, (group) => group.groupKey === groupKey);
4✔
469

470
      if (index > -1) {
1!
471
        groups.splice(index, 1);
1✔
472
      }
473
    }
474

475
    this.syncItems(flatGroups(this.getGroups()));
1✔
476
  }
477
  public insertPlaceholders(
1✔
478
    direction: "start" | "end",
479
    items: number | InfiniteGridItemStatus[],
480
    groupKey: string | number = makeKey(this.groupKeys, "virtual_"),
3!
481
  ) {
482

483
    let infos: InfiniteGridItemInfo[] = [];
3✔
484

485
    if (isNumber(items)) {
3!
486
      infos = range(items).map(() => ({ type: ITEM_TYPE.VIRTUAL, groupKey }));
11✔
487
    } else if (Array.isArray(items)) {
×
488
      infos = items.map((status) => ({
×
489
        groupKey,
490
        ...status,
491
        type: ITEM_TYPE.VIRTUAL,
492
      }));
493
    }
494
    const grid = this._makeGrid();
3✔
495
    const nextItems = this._syncItemInfos(infos, this.itemKeys);
3✔
496

497
    this._updatePlaceholder(nextItems);
3✔
498
    grid.setItems(nextItems);
3✔
499

500
    const group = {
3✔
501
      type: GROUP_TYPE.VIRTUAL,
502
      groupKey,
503
      grid,
504
      items: nextItems,
505
      renderItems: nextItems,
506
    };
507

508
    this.groupKeys[groupKey] = group;
3✔
509

510
    if (direction === "end") {
3!
511
      this.groups.push(group);
3✔
512
      this.groupItems.push(...nextItems);
3✔
513
    } else {
514
      this.groups.splice(0, 0, group);
×
515
      this.groupItems.splice(0, 0, ...nextItems);
×
516
      if (this.startCursor > -1) {
×
517
        ++this.startCursor;
×
518
        ++this.endCursor;
×
519
      }
520
    }
521

522

523
    return {
3✔
524
      group,
525
      items: nextItems,
526
    };
527
  }
528

529
  public shouldRerenderItems() {
1✔
530
    let isRerender = false;
128✔
531

532
    this.getVisibleGroups().forEach((group) => {
128✔
533
      const items = group.items;
314✔
534

535
      if (
314✔
536
        items.length === group.renderItems.length
316✔
537
        || items.every((item) => item.mountState === MOUNT_STATE.UNCHECKED)
2✔
538
      ) {
539
        return;
312✔
540
      }
541
      isRerender = true;
2✔
542
      group.renderItems = [...items];
2✔
543
    });
544
    if (isRerender) {
128✔
545
      this.items = this._getRenderingItems();
2✔
546
    }
547
    return isRerender;
128✔
548
  }
549

550
  private _getGroupItems() {
1✔
551
    return flatGroups(this.getGroups(true));
100✔
552
  }
553

554
  private _getRenderingItems() {
1✔
555
    const items = flat(this.getVisibleGroups(true).map((item) => item.renderItems));
414✔
556

557

558
    const loadingGrid = this._loadingGrid;
246✔
559
    const loadingItem = loadingGrid.getLoadingItem();
246✔
560

561
    if (loadingItem) {
246✔
562
      if (loadingGrid.type === "end") {
7✔
563
        items.push(loadingItem);
4✔
564
      } else if (loadingGrid.type === "start") {
3!
565
        items.unshift(loadingItem);
×
566
      }
567
    }
568

569
    return items;
246✔
570
  }
571

572
  private _checkShouldRender(options: Record<string, any>) {
1✔
573
    const GridConstructor = this.options.gridConstructor!;
2✔
574
    const prevOptions = this.gridOptions;
2✔
575
    const propertyTypes = GridConstructor.propertyTypes;
2✔
576

577
    for (const name in prevOptions) {
2✔
578
      if (!(name in options) && propertyTypes[name] === PROPERTY_TYPE.RENDER_PROPERTY) {
14✔
579
        return true;
1✔
580
      }
581
    }
582
    for (const name in options) {
1✔
583
      if (prevOptions[name] !== options[name] && propertyTypes[name] === PROPERTY_TYPE.RENDER_PROPERTY) {
1!
584
        return true;
1✔
585
      }
586
    }
587
    return false;
×
588
  }
589
  private _applyVirtualGrid(grid: Grid, direction: "start" | "end", outline: number[]) {
1✔
590
    const startOutline = outline.length ? [...outline] : [0];
2!
591
    const prevOutlines = grid.getOutlines();
2✔
592
    const prevOutline = prevOutlines[direction === "end" ? "start" : "end"];
2!
593

594
    if (
2!
595
      prevOutline.length !== startOutline.length
4✔
596
      || prevOutline.some((value, i) => value !== startOutline[i])
2✔
597
    ) {
598
      return {
×
599
        start: [...startOutline],
600
        end: [...startOutline],
601
      };
602
    }
603
    return prevOutlines;
2✔
604
  }
605
  private _syncItemInfos(
1✔
606
    nextItemInfos: InfiniteGridItemStatus[],
607
    prevItemKeys: Record<string | number, InfiniteGridItem> = {},
222✔
608
  ) {
609
    const horizontal = this.options.horizontal;
156✔
610
    const nextItemKeys = this.itemKeys;
156✔
611

612
    nextItemInfos.filter((info) => info.key != null).forEach((info) => {
937✔
613
      const key = info.key!;
813✔
614
      const prevItem = prevItemKeys[key];
813✔
615

616
      if (!prevItem) {
813✔
617
        nextItemKeys[key] = new InfiniteGridItem(horizontal, {
645✔
618
          ...info,
619
        });
620
      } else if (prevItem.type === ITEM_TYPE.VIRTUAL && info.type !== ITEM_TYPE.VIRTUAL) {
168✔
621
        nextItemKeys[key] = new InfiniteGridItem(horizontal, {
23✔
622
          orgRect: prevItem.orgRect,
623
          rect: prevItem.rect,
624
          ...info,
625
        });
626
      } else {
627
        if (info.data) {
145✔
628
          prevItem.data = info.data;
122✔
629
        }
630
        if (info.groupKey != null) {
145✔
631
          prevItem.groupKey = info.groupKey!;
137✔
632
        }
633
        nextItemKeys[key] = prevItem;
145✔
634
      }
635
    });
636
    const nextItems = nextItemInfos.map((info) => {
156✔
637
      let key = info.key!;
937✔
638

639
      if (info.key == null) {
937✔
640
        key = makeKey(nextItemKeys, info.type === ITEM_TYPE.VIRTUAL ? "virtual_" : "");
124✔
641
      }
642
      let item = nextItemKeys[key];
937✔
643

644
      if (!item) {
937✔
645
        const prevItem = prevItemKeys[key];
124✔
646

647
        if (prevItem) {
124✔
648
          item = prevItem;
6✔
649

650
          if (info.data) {
6!
651
            item.data = info.data;
×
652
          }
653
        } else {
654
          item = new InfiniteGridItem(horizontal, {
118✔
655
            ...info,
656
            key,
657
          });
658
        }
659
        nextItemKeys[key] = item;
124✔
660
      }
661
      return item;
937✔
662
    });
663
    return nextItems;
156✔
664
  }
665
  private _registerGroups(groups: InfiniteGridGroup[]) {
1✔
666
    const nextGroupKeys: Record<string | number, InfiniteGridGroup> = {};
100✔
667

668
    groups.forEach((group) => {
100✔
669
      nextGroupKeys[group.groupKey] = group;
283✔
670
    });
671

672
    this.groups = groups;
100✔
673
    this.groupKeys = nextGroupKeys;
100✔
674
    this.groupItems = this._getGroupItems();
100✔
675
  }
676
  private _splitVirtualGroups(direction: "start" | "end", nextGroups: CategorizedGroup[]) {
1✔
677
    const groups = splitVirtualGroups(this.groups, direction, nextGroups);
174✔
678
    const itemKeys = this.itemKeys;
174✔
679

680
    groups.forEach(({ renderItems }) => {
174✔
681
      renderItems.forEach((item) => {
3✔
682
        itemKeys[item.key] = item;
11✔
683
      });
684
    });
685

686
    return groups;
174✔
687
  }
688
  private _mergeVirtualGroups(groups: Array<CategorizedGroup<InfiniteGridItem>>) {
1✔
689
    const itemKeys = this.itemKeys;
87✔
690
    const groupKeys = this.groupKeys;
87✔
691

692
    groups.forEach((group) => {
87✔
693
      const prevGroup = groupKeys[group.groupKey];
214✔
694

695
      if (!prevGroup) {
214✔
696
        return;
163✔
697
      }
698
      const items = group.items;
51✔
699

700
      if (items.every((item) => item.mountState === MOUNT_STATE.UNCHECKED)) {
116✔
701
        prevGroup.renderItems.forEach((item) => {
22✔
702
          if (item.type === ITEM_TYPE.VIRTUAL && !itemKeys[item.key]) {
87✔
703
            items.push(item);
6✔
704
            itemKeys[item.key] = item;
6✔
705
          }
706
        });
707
      }
708
    });
709
    return groups;
87✔
710
  }
711

712
  private _updatePlaceholder(items = this.groupItems) {
45✔
713
    const placeholder = this._placeholder;
24✔
714

715
    if (!placeholder) {
24✔
716
      return;
11✔
717
    }
718

719
    items.filter((item) => item.type === ITEM_TYPE.VIRTUAL).forEach((item) => {
121✔
720
      setPlaceholder(item, placeholder);
34✔
721
    });
722
  }
723
  private _makeGrid() {
1✔
724
    const GridConstructor = this.options.gridConstructor!;
232✔
725
    const gridOptions = this.gridOptions;
232✔
726
    const container = this.containerElement;
232✔
727

728
    return new GridConstructor(container, {
232✔
729
      ...gridOptions,
730
      useFit: false,
731
      autoResize: false,
732
      useResizeObserver: false,
733
      observeChildren: false,
734
      renderOnPropertyChange: false,
735
      externalContainerManager: this.containerManager,
736
      externalItemRenderer: this.itemRenderer,
737
    });
738
  }
739
  private _getLoadingGroup(): InfiniteGridGroup {
1✔
740
    const loadingGrid = this._loadingGrid;
2✔
741
    const items = loadingGrid.getItems() as InfiniteGridItem[];
2✔
742

743
    return {
2✔
744
      groupKey: LOADING_GROUP_KEY,
745
      type: GROUP_TYPE.NORMAL,
746
      grid: loadingGrid,
747
      items,
748
      renderItems: items,
749
    };
750
  }
751
  private _getLoadingItem() {
1✔
752
    return this._loadingGrid.getLoadingItem();
6✔
753
  }
754
}
1✔
755

756
export interface GroupManager extends Properties<typeof GroupManager> {
757
  getItems(): InfiniteGridItem[];
758
}
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