• 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

89.58
/packages/infinitegrid/src/utils.ts
1
import { withClassMethods } from "@cfcs/core";
1✔
2
import Grid, { GRID_PROPERTY_TYPES } from "@egjs/grid";
1✔
3
import { diff } from "@egjs/list-differ";
1✔
4
import { GROUP_TYPE, IGNORE_PROPERITES_MAP, INFINITEGRID_METHODS, ITEM_INFO_PROPERTIES, ITEM_TYPE } from "./consts";
1✔
5
import { GroupManagerStatus, InfiniteGridGroupStatus } from "./GroupManager";
6
import InfiniteGrid from "./InfiniteGrid";
7
import { InfiniteGridItem, InfiniteGridItemStatus } from "./InfiniteGridItem";
1✔
8
import {
9
  CategorizedGroup, InfiniteGridGroup, InfiniteGridInsertedItems,
10
  InfiniteGridItemInfo,
11
  RenderingOptions,
12
} from "./types";
13

14
export function isWindow(el: Window | Element): el is Window {
2✔
15
  return el === window;
110✔
16
}
17

18
export function isNumber(val: any): val is number {
2✔
19
  return typeof val === "number";
1,260✔
20
}
21

22
export function isString(val: any): val is string {
2✔
23
  return typeof val === "string";
249✔
24
}
25
export function isObject(val: any): val is object {
2✔
26
  return typeof val === "object";
29✔
27
}
28

29
export function flat<T>(arr: T[][]): T[] {
2✔
30
  return arr.reduce((prev, cur) => {
349✔
31
    return [...prev, ...cur];
709✔
32
  }, []);
33
}
34
export function splitOptions(options: Record<string, any>) {
2✔
35
  const {
36
    gridOptions,
65✔
37
    ...otherOptions
38
  } = options;
39

40
  return {
65✔
41
    ...splitGridOptions(gridOptions),
42
    ...otherOptions,
43
  };
44
}
45
export function splitGridOptions(options: Record<string, any>) {
2✔
46
  const nextOptions: Record<string, any> = {};
67✔
47
  const gridOptions: Record<string, any> = {};
67✔
48
  const defaultOptions = Grid.defaultOptions;
67✔
49

50
  for (const name in options) {
67✔
51
    const value = options[name];
1,305✔
52

53
    if (!(name in IGNORE_PROPERITES_MAP)) {
1,305✔
54
      gridOptions[name] = value;
1,125✔
55
    }
56

57
    if (name in defaultOptions) {
1,305✔
58
      nextOptions[name] = value;
1,262✔
59
    }
60
  }
61
  return {
67✔
62
    ...nextOptions,
63
    gridOptions,
64
  };
65
}
66

67
export function categorize<Item extends InfiniteGridItemInfo = InfiniteGridItem>(items: Item[]) {
2✔
68
  const groups: Array<CategorizedGroup<Item>> = [];
89✔
69
  const groupKeys: Record<string | number, CategorizedGroup<Item>> = {};
89✔
70
  const registeredGroupKeys: Record<string | number, boolean> = {};
89✔
71

72
  items.filter((item) => item.groupKey != null).forEach(({ groupKey }) => {
747✔
73
    registeredGroupKeys[groupKey!] = true;
634✔
74
  });
75

76
  let generatedGroupKey: number | string;
89✔
77
  let isContinuousGroupKey = false;
89✔
78

79
  items.forEach((item) => {
89✔
80
    if (item.groupKey != null) {
747✔
81
      isContinuousGroupKey = false;
634✔
82
    } else {
83
      if (!isContinuousGroupKey) {
113✔
84
        generatedGroupKey = makeKey(registeredGroupKeys);
22✔
85
        isContinuousGroupKey = true;
22✔
86
        registeredGroupKeys[generatedGroupKey] = true;
22✔
87
      }
88
      item.groupKey = generatedGroupKey;
113✔
89
    }
90

91
    const groupKey = item.groupKey;
747✔
92
    let group = groupKeys[groupKey];
747✔
93

94
    if (!group) {
747✔
95
      group = {
218✔
96
        groupKey,
97
        items: [],
98
      };
99
      groupKeys[groupKey] = group;
218✔
100
      groups.push(group);
218✔
101
    }
102

103
    group.items.push(item);
747✔
104
  });
105
  return groups;
89✔
106
}
107

108
export function getNextCursors(
2✔
109
  prevKeys: Array<string | number>,
110
  nextKeys: Array<string | number>,
111
  prevStartCursor: number,
112
  prevEndCursor: number,
113
) {
114
  const result = diff(prevKeys, nextKeys, (key) => key);
1,358✔
115
  let nextStartCursor = -1;
234✔
116
  let nextEndCursor = -1;
234✔
117

118
  // sync cursors
119
  result.maintained.forEach(([prevIndex, nextIndex]) => {
234✔
120
    if (prevStartCursor <= prevIndex && prevIndex <= prevEndCursor) {
595✔
121
      if (nextStartCursor === -1) {
424✔
122
        nextStartCursor = nextIndex;
177✔
123
        nextEndCursor = nextIndex;
177✔
124
      } else {
125
        nextStartCursor = Math.min(nextStartCursor, nextIndex);
247✔
126
        nextEndCursor = Math.max(nextEndCursor, nextIndex);
247✔
127
      }
128
    }
129
  });
130
  return {
234✔
131
    startCursor: nextStartCursor,
132
    endCursor: nextEndCursor,
133
  };
134
}
135
export function splitVirtualGroups<Group extends { type: GROUP_TYPE, groupKey: string | number }>(
2✔
136
  groups: Group[],
137
  direction: "start" | "end",
138
  nextGroups: CategorizedGroup<InfiniteGridItemStatus>[],
139
) {
140
  let virtualGroups: Group[] = [];
176✔
141

142
  if (direction === "start") {
176✔
143
    const index = findIndex(groups, (group) => group.type === GROUP_TYPE.NORMAL);
88✔
144

145
    if (index === -1) {
88✔
146
      return [];
60✔
147
    }
148
    // Get the virtual group maintained in the group from the next group.
149
    const endMaintainedIndex = findIndex(groups, (group) => {
28✔
150
      return findIndex(nextGroups, (nextGroup) => nextGroup.groupKey === group.groupKey) >= 0;
34✔
151
    });
152
    const endIndex = endMaintainedIndex >= 0 ? Math.min(index, endMaintainedIndex) : index;
28!
153

154
    virtualGroups = groups.slice(0, endIndex);
28✔
155
  } else {
156
    const index = findLastIndex(groups, (group) => group.type === GROUP_TYPE.NORMAL);
88✔
157

158
    if (index === -1) {
88✔
159
      return [];
60✔
160
    }
161
    const startMaintainedIndex = findLastIndex(groups, (group) => {
28✔
162
      return findIndex(nextGroups, (nextGroup) => nextGroup.groupKey === group.groupKey) >= 0;
66✔
163
    });
164
    const startIndex = startMaintainedIndex >= 0 ? Math.max(index, startMaintainedIndex) : index;
28!
165

166
    virtualGroups = groups.slice(startIndex + 1);
28✔
167
  }
168

169
  return virtualGroups;
56✔
170
}
171

172
export function getFirstRenderingItems(
2✔
173
  nextItems: InfiniteGridItemStatus[],
174
  horizontal: boolean,
175
) {
176
  const groups = categorize(nextItems);
1✔
177

178
  if (!groups[0]) {
1!
179
    return [];
×
180
  }
181
  return groups[0].items.map((item) => {
1✔
182
    return new InfiniteGridItem(horizontal, {
3✔
183
      ...item,
184
    });
185
  });
186
}
187
export function getRenderingItemsByStatus(
2✔
188
  groupManagerStatus: GroupManagerStatus,
189
  nextItems: InfiniteGridItemStatus[],
190
  usePlaceholder: boolean,
191
  horizontal: boolean,
192
) {
193
  const prevGroups = groupManagerStatus.groups;
1✔
194
  const groups = categorize(nextItems);
1✔
195

196
  const startVirtualGroups = splitVirtualGroups(prevGroups, "start", groups);
1✔
197
  const endVirtualGroups = splitVirtualGroups(prevGroups, "end", groups);
1✔
198
  const nextGroups = [
1✔
199
    ...startVirtualGroups,
200
    ...groups,
201
    ...endVirtualGroups,
202
  ] as Array<InfiniteGridGroupStatus | CategorizedGroup<InfiniteGridItemStatus>>;
203
  const {
1✔
204
    startCursor,
205
    endCursor,
206
  } = getNextCursors(
207
    prevGroups.map((group) => group.groupKey),
3✔
208
    nextGroups.map((group) => group.groupKey),
2✔
209
    groupManagerStatus.cursors[0],
210
    groupManagerStatus.cursors[1],
211
  );
212

213
  let nextVisibleItems = flat(nextGroups.slice(startCursor, endCursor + 1).map((group) => {
1✔
214
    return group.items.map((item) => {
2✔
215
      return new InfiniteGridItem(horizontal, { ...item });
6✔
216
    });
217
  }));
218

219
  if (!usePlaceholder) {
1!
220
    nextVisibleItems = nextVisibleItems.filter((item) => {
1✔
221
      return item.type !== ITEM_TYPE.VIRTUAL;
6✔
222
    });
223
  }
224

225
  return nextVisibleItems;
1✔
226
}
227

228
export function mountRenderingItems(items: InfiniteGridItemInfo[], options: RenderingOptions) {
2✔
229
  const {
230
    grid,
1✔
231
    usePlaceholder,
232
    useLoading,
233
    useFirstRender,
234
    status,
235
  } = options;
236
  if (!grid) {
1!
237
    return;
×
238
  }
239
  if (usePlaceholder) {
1!
240
    grid.setPlaceholder({});
×
241
  }
242
  if (useLoading) {
1!
243
    grid.setLoading({});
×
244
  }
245
  if (status) {
1!
246
    grid.setStatus(status, true);
1✔
247
  }
248

249
  grid.syncItems(items);
1✔
250

251
  if (useFirstRender && !status && grid.getGroups().length) {
1!
252
    grid.setCursors(0, 0, true);
×
253
  }
254
}
255
export function getRenderingItems(items: InfiniteGridItemInfo[], options: RenderingOptions) {
2✔
256
  const {
257
    status,
5✔
258
    usePlaceholder,
259
    useLoading,
260
    horizontal,
261
    useFirstRender,
262
    grid,
263
  } = options;
264
  let visibleItems: InfiniteGridItem[] = [];
5✔
265

266
  if (grid) {
5✔
267
    grid.setPlaceholder(usePlaceholder ? {} : null);
2✔
268
    grid.setLoading(useLoading ? {} : null);
2✔
269
    grid.syncItems(items);
2✔
270

271
    visibleItems = grid.getRenderingItems();
2✔
272
  } else if (status) {
3✔
273
    visibleItems = getRenderingItemsByStatus(status.groupManager, items, !!usePlaceholder, !!horizontal);
1✔
274
  } else if (useFirstRender) {
2✔
275
    visibleItems = getFirstRenderingItems(items, !!horizontal);
1✔
276
  }
277

278
  return visibleItems;
5✔
279
}
280

281
/* Class Decorator */
282
export function InfiniteGridGetterSetter(component: {
2✔
283
  prototype: InfiniteGrid<any>,
284
  propertyTypes: typeof GRID_PROPERTY_TYPES,
285
}) {
286
  const {
287
    prototype,
5✔
288
    propertyTypes,
289
  } = component;
290
  for (const name in propertyTypes) {
5✔
291
    const attributes: Record<string, any> = {
53✔
292
      enumerable: true,
293
      configurable: true,
294
      get(this: InfiniteGrid) {
295
        const options = this.groupManager.options;
13✔
296
        if (name in options) {
13!
297
          return options[name];
13✔
298
        } else {
299
          return options.gridOptions[name];
×
300
        }
301
      },
302
      set(this: InfiniteGrid, value: any) {
303
        const prevValue = this.groupManager[name];
1✔
304

305
        if (prevValue === value) {
1!
306
          return;
×
307
        }
308
        this.groupManager.gridOptions = {
1✔
309
          [name]: value,
310
        };
311
      },
312
    };
313
    Object.defineProperty(prototype, name, attributes);
53✔
314
  }
315
}
316

317
export function makeKey(
2✔
318
  registeredKeys: Record<string, any>,
319
  prefix = "",
168✔
320
) {
321
  let index = 0;
146✔
322
  // eslint-disable-next-line no-constant-condition
323
  while (true) {
146✔
324
    const key = `infinitegrid_${prefix}${index++}`;
599✔
325

326
    if (!(key in registeredKeys)) {
599✔
327
      return key;
146✔
328
    }
329
  }
330
}
331

332
export function convertHTMLtoElement(html: string) {
2✔
333
  const dummy = document.createElement("div");
581✔
334

335
  dummy.innerHTML = html;
581✔
336
  return toArray(dummy.children);
581✔
337
}
338

339
export function convertInsertedItems(
2✔
340
  items: InfiniteGridInsertedItems,
341
  groupKey?: string | number,
342
): InfiniteGridItemInfo[] {
343
  let insertedItems: Array<string | HTMLElement | InfiniteGridItemInfo>;
31✔
344

345
  if (isString(items)) {
31!
346
    insertedItems = convertHTMLtoElement(items);
×
347
  } else {
348
    insertedItems = items;
31✔
349
  }
350
  return insertedItems.map((item) => {
31✔
351
    let element!: HTMLElement;
158✔
352
    let html = "";
158✔
353
    let key!: string | number;
158✔
354

355
    if (isString(item)) {
158✔
356
      html = item;
3✔
357
    } else if ("parentNode" in item) {
155✔
358
      element = item;
18✔
359
      html = item.outerHTML;
18✔
360
    } else {
361
      return { groupKey, ...item };
137✔
362
    }
363

364
    return {
21✔
365
      key,
366
      groupKey,
367
      html,
368
      element,
369
    };
370
  });
371
}
372
export function toArray(nodes: HTMLCollection): HTMLElement[];
373
export function toArray<T>(nodes: { length: number, [key: number]: T }): T[];
374
export function toArray<T>(nodes: { length: number, [key: number]: T }): T[] {
2✔
375
  const array: T[] = [];
1,036✔
376

377
  if (nodes) {
1,036!
378
    const length = nodes.length;
1,036✔
379

380
    for (let i = 0; i < length; i++) {
1,036✔
381
      array.push(nodes[i]);
2,456✔
382
    }
383
  }
384
  return array;
1,036✔
385
}
386

387

388
export function find<T>(arr: T[], callback: (value: T, index: number) => boolean): T | null {
2✔
389
  const length = arr.length;
×
390

391
  for (let i = 0; i < length; ++i) {
×
392
    const value = arr[i];
×
393

394
    if (callback(value, i)) {
×
395
      return value;
×
396
    }
397
  }
398

399
  return null;
×
400
}
401

402
export function findIndex<T>(arr: T[], callback: (value: T, index: number) => boolean) {
2✔
403
  const length = arr.length;
188✔
404
  for (let i = 0; i < length; ++i) {
188✔
405
    if (callback(arr[i], i)) {
178✔
406
      return i;
119✔
407
    }
408
  }
409

410
  return -1;
69✔
411
}
412

413
export function findLastIndex<T>(arr: T[], callback: (value: T, index: number) => boolean) {
2✔
414
  const length = arr.length;
134✔
415
  for (let i = length - 1; i >= 0; --i) {
134✔
416
    if (callback(arr[i], i)) {
130✔
417
      return i;
74✔
418
    }
419
  }
420

421
  return -1;
60✔
422
}
423

424
export function getItemInfo(info: InfiniteGridItemInfo) {
2✔
425
  const nextInfo: InfiniteGridItemInfo = {};
735✔
426

427
  for (const name in info) {
735✔
428
    if (name in ITEM_INFO_PROPERTIES) {
5,171✔
429
      nextInfo[name] = info[name];
2,689✔
430
    }
431
  }
432

433
  return nextInfo;
735✔
434
}
435

436
export function setPlaceholder(item: InfiniteGridItem, info: InfiniteGridItemStatus) {
2✔
437
  for (const name in info) {
34✔
438
    const value = info[name];
29✔
439

440
    if (isObject(value)) {
29!
441
      item[name] = {
×
442
        ...item[name],
443
        ...value,
444
      };
445
    } else {
446
      item[name] = info[name];
29✔
447
    }
448
  }
449
}
450

451
export function isFlatOutline(start: number[], end: number[]) {
2✔
452
  return start.length === end.length && start.every((pos, i) => end[i] === pos);
1✔
453
}
454

455
export function range(length: number): number[] {
2✔
456
  const arr: number[] = [];
3✔
457
  for (let i = 0; i < length; ++i) {
3✔
458
    arr.push(i);
11✔
459
  }
460
  return arr;
3✔
461
}
462

463
export function flatGroups(groups: InfiniteGridGroup[]) {
2✔
464
  return flat(groups.map(({ grid }) => grid.getItems() as InfiniteGridItem[]));
286✔
465
}
466

467

468
export function filterVirtuals<T extends InfiniteGridItem | InfiniteGridGroup>(
2✔
469
  items: T[],
470
  includePlaceholders?: boolean
471
): T[] {
472
  if (includePlaceholders) {
1,130✔
473
    return [...items];
868✔
474
  } else {
475
    return items.filter((item) => item.type !== ITEM_TYPE.VIRTUAL);
1,103✔
476
  }
477
}
478

479
/**
480
 * Decorator that makes the method of InfiniteGrid available in the framework.
481
 * @ko 프레임워크에서 InfiniteGrid의 메소드를 사용할 수 있게 하는 데코레이터.
482
 * @private
483
 * @example
484
 * ```js
485
 * import { withInfiniteGridMethods } from "@egjs/infinitegrid";
486
 *
487
 * class Grid extends React.Component<Partial<InfiniteGridProps & InfiniteGridOptions>> {
488
 *   &#64;withInfiniteGridMethods
489
 *   private grid: NativeGrid;
490
 * }
491
 * ```
492
 */
493
export const withInfiniteGridMethods = withClassMethods(INFINITEGRID_METHODS);
1✔
494

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