1 | import Grid, {
|
2 | GetterSetter,
|
3 | GridFunction, GridItem, 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";
|
8 | import { InfiniteGridItem, InfiniteGridItemStatus } from "./InfiniteGridItem";
|
9 | import { LoadingGrid, LOADING_GROUP_KEY } from "./LoadingGrid";
|
10 | import { CategorizedGroup, InfiniteGridGroup, InfiniteGridItemInfo } from "./types";
|
11 | import {
|
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 | appliedItemChecker?: (item: InfiniteGridItem, grid: Grid) => boolean;
|
29 | gridConstructor: GridFunction | null;
|
30 | gridOptions: Record<string, any>;
|
31 | }
|
32 |
|
33 | export interface GroupManagerStatus {
|
34 | cursors: [number, number];
|
35 | orgCursors: [number, number];
|
36 | itemCursors: [number, number];
|
37 | startGroupKey: number | string;
|
38 | endGroupKey: number | string;
|
39 | groups: InfiniteGridGroupStatus[];
|
40 | outlines: GridOutlines;
|
41 | }
|
42 |
|
43 | @GetterSetter
|
44 | export class GroupManager extends Grid<GroupManagerOptions> {
|
45 | public static defaultOptions: Required<GroupManagerOptions> = {
|
46 | ...Grid.defaultOptions,
|
47 | appliedItemChecker: () => false,
|
48 | gridConstructor: null,
|
49 | gridOptions: {},
|
50 | };
|
51 | public static propertyTypes = {
|
52 | ...Grid.propertyTypes,
|
53 | gridConstructor: PROPERTY_TYPE.PROPERTY,
|
54 | gridOptions: PROPERTY_TYPE.PROPERTY,
|
55 | } as const;
|
56 | protected items: InfiniteGridItem[];
|
57 | protected groupItems: InfiniteGridItem[] = [];
|
58 | protected groups: InfiniteGridGroup[] = [];
|
59 | protected itemKeys: Record<string | number, InfiniteGridItem> = {};
|
60 | protected groupKeys: Record<string | number, InfiniteGridGroup> = {};
|
61 | protected startCursor = 0;
|
62 | protected endCursor = 0;
|
63 | private _placeholder: Partial<InfiniteGridItemStatus> | null = null;
|
64 | private _loadingGrid!: LoadingGrid;
|
65 | private _mainGrid!: Grid;
|
66 |
|
67 | constructor(container: HTMLElement, options: GroupManagerOptions) {
|
68 | super(container, splitOptions(options));
|
69 |
|
70 | this._loadingGrid = new LoadingGrid(container, {
|
71 | externalContainerManager: this.containerManager,
|
72 | useFit: false,
|
73 | autoResize: false,
|
74 | renderOnPropertyChange: false,
|
75 | gap: this.gap,
|
76 | });
|
77 | this._mainGrid = this._makeGrid();
|
78 | }
|
79 | public set gridOptions(options: Record<string, any>) {
|
80 | const {
|
81 | gridOptions,
|
82 | ...otherOptions
|
83 | } = splitGridOptions(options);
|
84 |
|
85 | const shouldRender = this._checkShouldRender(options);
|
86 | this.options.gridOptions = {
|
87 | ...this.options.gridOptions,
|
88 | ...gridOptions,
|
89 | };
|
90 | [this._mainGrid, ...this.groups.map(({ grid }) => grid)].forEach((grid) => {
|
91 | for (const name in options) {
|
92 | (grid as any)[name] = options[name];
|
93 | }
|
94 | });
|
95 | for (const name in otherOptions) {
|
96 | this[name] = otherOptions[name];
|
97 | }
|
98 |
|
99 | this._loadingGrid.gap = this.gap;
|
100 | if (shouldRender) {
|
101 | this.scheduleRender();
|
102 | }
|
103 | }
|
104 |
|
105 | public getItemByKey(key: string | number): InfiniteGridItem | null {
|
106 | return this.itemKeys[key] || null;
|
107 | }
|
108 |
|
109 | public getGroupItems(includePlaceholders?: boolean) {
|
110 | return filterVirtuals(this.groupItems, includePlaceholders);
|
111 | }
|
112 | public getVisibleItems(includePlaceholders?: boolean) {
|
113 | return filterVirtuals(this.items, includePlaceholders);
|
114 | }
|
115 |
|
116 | public getRenderingItems() {
|
117 | if (this.hasPlaceholder()) {
|
118 | return this.items;
|
119 | } else {
|
120 | return this.items.filter((item) => item.type !== ITEM_TYPE.VIRTUAL);
|
121 | }
|
122 | }
|
123 |
|
124 | public getGroups(includePlaceholders?: boolean): InfiniteGridGroup[] {
|
125 | return filterVirtuals(this.groups, includePlaceholders);
|
126 | }
|
127 |
|
128 | public hasVisibleVirtualGroups() {
|
129 | return this.getVisibleGroups(true).some((group) => group.type === GROUP_TYPE.VIRTUAL);
|
130 | }
|
131 | public hasPlaceholder() {
|
132 | return !!this._placeholder;
|
133 | }
|
134 | public hasLoadingItem() {
|
135 | return !!this._getLoadingItem();
|
136 | }
|
137 |
|
138 | public updateItems(items = this.groupItems, options?: RenderOptions) {
|
139 | return super.updateItems(items, options);
|
140 | }
|
141 | public setPlaceholder(placeholder: Partial<InfiniteGridItemStatus> | null) {
|
142 | this._placeholder = placeholder;
|
143 | this._updatePlaceholder();
|
144 | }
|
145 |
|
146 | public getLoadingType() {
|
147 | return this._loadingGrid.type;
|
148 | }
|
149 |
|
150 | public startLoading(type: "start" | "end") {
|
151 | this._loadingGrid.type = type;
|
152 | this.items = this._getRenderingItems();
|
153 |
|
154 | return true;
|
155 | }
|
156 |
|
157 | public endLoading() {
|
158 | const prevType = this._loadingGrid.type;
|
159 |
|
160 | this._loadingGrid.type = "";
|
161 | this.items = this._getRenderingItems();
|
162 | return !!prevType;
|
163 | }
|
164 |
|
165 | public setLoading(loading: Partial<InfiniteGridItemStatus> | null) {
|
166 | this._loadingGrid.setLoadingItem(loading);
|
167 | this.items = this._getRenderingItems();
|
168 | }
|
169 |
|
170 | public getVisibleGroups(includePlaceholders?: boolean): InfiniteGridGroup[] {
|
171 | const groups = this.groups.slice(this.startCursor, this.endCursor + 1);
|
172 |
|
173 | return filterVirtuals(groups, includePlaceholders);
|
174 | }
|
175 |
|
176 | public getComputedOutlineLength(items = this.items) {
|
177 | return this._mainGrid.getComputedOutlineLength(items);
|
178 | }
|
179 | public getComputedOutlineSize(items = this.items) {
|
180 | return this._mainGrid.getComputedOutlineSize(items);
|
181 | }
|
182 |
|
183 | public applyGrid(items: InfiniteGridItem[], direction: "end" | "start", outline: number[]): GridOutlines {
|
184 | const renderingGroups = this.groups.slice();
|
185 |
|
186 | if (!renderingGroups.length) {
|
187 | return {
|
188 | start: [],
|
189 | end: [],
|
190 | };
|
191 | }
|
192 |
|
193 |
|
194 | const loadingGrid = this._loadingGrid;
|
195 |
|
196 | if (loadingGrid.getLoadingItem()) {
|
197 | if (loadingGrid.type === "start") {
|
198 | renderingGroups.unshift(this._getLoadingGroup());
|
199 | } else if (loadingGrid.type === "end") {
|
200 | renderingGroups.push(this._getLoadingGroup());
|
201 | }
|
202 | }
|
203 |
|
204 | const groups = renderingGroups.slice();
|
205 |
|
206 | let nextOutline = outline;
|
207 |
|
208 | if (direction === "start") {
|
209 | groups.reverse();
|
210 | }
|
211 |
|
212 | const appliedItemChecker = this.options.appliedItemChecker;
|
213 | const groupItems = this.groupItems;
|
214 | const outlineLength = this.getComputedOutlineLength(groupItems);
|
215 | const outlineSize = this.getComputedOutlineSize(groupItems);
|
216 | const itemRenderer = this.itemRenderer;
|
217 |
|
218 | groups.forEach((group) => {
|
219 | const grid = group.grid;
|
220 | const gridItems = grid.getItems() as InfiniteGridItem[];
|
221 | const isVirtual = group.type === GROUP_TYPE.VIRTUAL && !gridItems[0];
|
222 |
|
223 | grid.outlineLength = outlineLength;
|
224 | grid.outlineSize = outlineSize;
|
225 |
|
226 | const appliedItems = gridItems.filter((item) => {
|
227 | if (item.mountState === MOUNT_STATE.UNCHECKED || !item.rect.width) {
|
228 | itemRenderer.updateItem(item, true);
|
229 | }
|
230 | return (item.orgRect.width && item.rect.width) || appliedItemChecker(item, grid);
|
231 | });
|
232 | let gridOutlines: GridOutlines;
|
233 |
|
234 | if (isVirtual) {
|
235 | gridOutlines = this._applyVirtualGrid(grid, direction, nextOutline);
|
236 | } else if (appliedItems.length) {
|
237 | gridOutlines = grid.applyGrid(appliedItems, direction, nextOutline);
|
238 | } else {
|
239 | gridOutlines = {
|
240 | start: [...nextOutline],
|
241 | end: [...nextOutline],
|
242 | };
|
243 | }
|
244 | grid.setOutlines(gridOutlines);
|
245 | nextOutline = gridOutlines[direction];
|
246 | });
|
247 |
|
248 | return {
|
249 | start: renderingGroups[0].grid.getOutlines().start,
|
250 | end: renderingGroups[renderingGroups.length - 1].grid.getOutlines().end,
|
251 | };
|
252 | }
|
253 |
|
254 | public syncItems(nextItemInfos: InfiniteGridItemInfo[]) {
|
255 | const prevItemKeys = this.itemKeys;
|
256 |
|
257 | this.itemKeys = {};
|
258 | const nextItems = this._syncItemInfos(nextItemInfos.map((info) => getItemInfo(info)), prevItemKeys);
|
259 | const prevGroupKeys = this.groupKeys;
|
260 | let nextManagerGroups = categorize(nextItems);
|
261 |
|
262 | const startVirtualGroups = this._splitVirtualGroups("start", nextManagerGroups);
|
263 | const endVirtualGroups = this._splitVirtualGroups("end", nextManagerGroups);
|
264 | nextManagerGroups = [...startVirtualGroups, ...this._mergeVirtualGroups(nextManagerGroups), ...endVirtualGroups];
|
265 |
|
266 | const nextGroups: InfiniteGridGroup[] = nextManagerGroups.map(({ groupKey, items }) => {
|
267 | const isVirtual = !items[0] || items[0].type === ITEM_TYPE.VIRTUAL;
|
268 | const grid = prevGroupKeys[groupKey]?.grid ?? this._makeGrid();
|
269 | const gridItems = isVirtual ? items : items.filter(({ type }) => type === ITEM_TYPE.NORMAL);
|
270 |
|
271 | grid.setItems(gridItems);
|
272 |
|
273 | return {
|
274 | type: isVirtual ? GROUP_TYPE.VIRTUAL : GROUP_TYPE.NORMAL,
|
275 | groupKey,
|
276 | grid,
|
277 | items: gridItems,
|
278 | renderItems: items,
|
279 | };
|
280 | });
|
281 |
|
282 | this._registerGroups(nextGroups);
|
283 | }
|
284 |
|
285 | public renderItems(options: RenderOptions = {}) {
|
286 | if (options.useResize) {
|
287 | this.groupItems.forEach((item) => {
|
288 | item.updateState = UPDATE_STATE.NEED_UPDATE;
|
289 | });
|
290 | const loadingItem = this._getLoadingItem();
|
291 |
|
292 | if (loadingItem) {
|
293 | loadingItem.updateState = UPDATE_STATE.NEED_UPDATE;
|
294 | }
|
295 | }
|
296 | return super.renderItems(options);
|
297 | }
|
298 |
|
299 | public setCursors(startCursor: number, endCursor: number) {
|
300 | this.startCursor = startCursor;
|
301 | this.endCursor = endCursor;
|
302 | this.items = this._getRenderingItems();
|
303 | }
|
304 |
|
305 | public getStartCursor() {
|
306 | return this.startCursor;
|
307 | }
|
308 |
|
309 | public getEndCursor() {
|
310 | return this.endCursor;
|
311 | }
|
312 |
|
313 | public getGroupStatus(type?: STATUS_TYPE, includePlaceholders?: boolean): GroupManagerStatus {
|
314 | const orgStartCursor = this.startCursor;
|
315 | const orgEndCursor = this.endCursor;
|
316 | const orgGroups = this.groups;
|
317 | const startGroup = orgGroups[orgStartCursor];
|
318 | const endGroup = orgGroups[orgEndCursor];
|
319 |
|
320 | let startCursor = orgStartCursor;
|
321 | let endCursor = orgEndCursor;
|
322 |
|
323 | const isMinimizeItems = type === STATUS_TYPE.MINIMIZE_INVISIBLE_ITEMS;
|
324 | const isMinimizeGroups = type === STATUS_TYPE.MINIMIZE_INVISIBLE_GROUPS;
|
325 | let groups: InfiniteGridGroup[];
|
326 |
|
327 | if (type === STATUS_TYPE.REMOVE_INVISIBLE_GROUPS) {
|
328 | groups = this.getVisibleGroups(includePlaceholders);
|
329 | endCursor = groups.length - 1;
|
330 | startCursor = 0;
|
331 | } else {
|
332 | groups = this.getGroups(includePlaceholders);
|
333 |
|
334 | if (!includePlaceholders) {
|
335 | startCursor = -1;
|
336 | endCursor = -1;
|
337 |
|
338 | for (let orgIndex = orgStartCursor; orgIndex <= orgEndCursor; ++orgIndex) {
|
339 | const orgGroup = orgGroups[orgIndex];
|
340 |
|
341 | if (orgGroup && orgGroup.type !== GROUP_TYPE.VIRTUAL) {
|
342 | startCursor = groups.indexOf(orgGroup);
|
343 | break;
|
344 | }
|
345 | }
|
346 | for (let orgIndex = orgEndCursor; orgIndex >= orgStartCursor; --orgIndex) {
|
347 | const orgGroup = orgGroups[orgIndex];
|
348 |
|
349 | if (orgGroup && orgGroup.type !== GROUP_TYPE.VIRTUAL) {
|
350 | endCursor = groups.lastIndexOf(orgGroup);
|
351 | break;
|
352 | }
|
353 | }
|
354 | }
|
355 | }
|
356 |
|
357 | const groupStatus: InfiniteGridGroupStatus[] = groups.map(({ grid, groupKey }, i) => {
|
358 | const isOutsideCursor = i < startCursor || endCursor < i;
|
359 | const isVirtualItems = isMinimizeItems && isOutsideCursor;
|
360 | const isVirtualGroup = isMinimizeGroups && isOutsideCursor;
|
361 | const gridItems = grid.getItems() as InfiniteGridItem[];
|
362 | const items = isVirtualGroup
|
363 | ? []
|
364 | : gridItems.map((item) => isVirtualItems ? item.getVirtualStatus() : item.getMinimizedStatus());
|
365 |
|
366 | return {
|
367 | type: isVirtualGroup || isVirtualItems ? GROUP_TYPE.VIRTUAL : GROUP_TYPE.NORMAL,
|
368 | groupKey: groupKey,
|
369 | outlines: grid.getOutlines(),
|
370 | items,
|
371 | };
|
372 | });
|
373 |
|
374 |
|
375 | const totalItems = this.getGroupItems();
|
376 |
|
377 | const itemStartCursor = totalItems.indexOf(startGroup?.items[0]);
|
378 | const itemEndCursor = totalItems.indexOf(endGroup?.items.slice().reverse()[0]);
|
379 |
|
380 | return {
|
381 | cursors: [startCursor, endCursor],
|
382 | orgCursors: [orgStartCursor, orgEndCursor],
|
383 | itemCursors: [itemStartCursor, itemEndCursor],
|
384 | startGroupKey: startGroup?.groupKey,
|
385 | endGroupKey: endGroup?.groupKey,
|
386 | groups: groupStatus,
|
387 | outlines: this.outlines,
|
388 | };
|
389 | }
|
390 | protected fitOutlines(useFit = this.useFit) {
|
391 | const groups = this.groups;
|
392 | const firstGroup = groups[0];
|
393 |
|
394 | if (!firstGroup) {
|
395 | return;
|
396 | }
|
397 | const outlines = firstGroup.grid.getOutlines();
|
398 | const startOutline = outlines.start;
|
399 | const outlineOffset = startOutline.length ? Math.min(...startOutline) : 0;
|
400 |
|
401 |
|
402 | if (!useFit && outlineOffset > 0) {
|
403 | return;
|
404 | }
|
405 |
|
406 | groups.forEach(({ grid }) => {
|
407 | const { start, end } = grid.getOutlines();
|
408 |
|
409 | grid.setOutlines({
|
410 | start: start.map((point) => point - outlineOffset),
|
411 | end: end.map((point) => point - outlineOffset),
|
412 | });
|
413 | });
|
414 |
|
415 | this.groupItems.forEach((item) => {
|
416 | const contentPos = item.cssContentPos;
|
417 |
|
418 | if (!isNumber(contentPos)) {
|
419 | return;
|
420 | }
|
421 | item.cssContentPos = contentPos - outlineOffset;
|
422 | });
|
423 | }
|
424 | public setGroupStatus(status: GroupManagerStatus) {
|
425 | this.itemKeys = {};
|
426 | this.groupItems = [];
|
427 | this.items = [];
|
428 | const prevGroupKeys = this.groupKeys;
|
429 |
|
430 | const nextGroups: InfiniteGridGroup[] = status.groups.map(({
|
431 | type,
|
432 | groupKey,
|
433 | items,
|
434 | outlines,
|
435 | }) => {
|
436 | const nextItems = this._syncItemInfos(items);
|
437 | const grid = prevGroupKeys[groupKey]?.grid ?? this._makeGrid();
|
438 |
|
439 | grid.setOutlines(outlines);
|
440 | grid.setItems(nextItems);
|
441 |
|
442 | return {
|
443 | type,
|
444 | groupKey,
|
445 | grid,
|
446 | items: nextItems,
|
447 | renderItems: nextItems,
|
448 | };
|
449 | });
|
450 |
|
451 | this.setOutlines(status.outlines);
|
452 | this._registerGroups(nextGroups);
|
453 | this._updatePlaceholder();
|
454 | this.setCursors(status.cursors[0], status.cursors[1]);
|
455 | }
|
456 | public appendPlaceholders(items: number | InfiniteGridItemStatus[], groupKey?: string | number) {
|
457 | return this.insertPlaceholders("end", items, groupKey);
|
458 | }
|
459 | public prependPlaceholders(items: number | InfiniteGridItemStatus[], groupKey?: string | number) {
|
460 | return this.insertPlaceholders("start", items, groupKey);
|
461 | }
|
462 | public removePlaceholders(type: "start" | "end" | { groupKey: string | number }) {
|
463 | const groups = this.groups;
|
464 | const length = groups.length;
|
465 |
|
466 | if (type === "start") {
|
467 | const index = findIndex(groups, (group) => group.type === GROUP_TYPE.NORMAL);
|
468 |
|
469 | groups.splice(0, index);
|
470 |
|
471 | } else if (type === "end") {
|
472 | const index = findLastIndex(groups, (group) => group.type === GROUP_TYPE.NORMAL);
|
473 |
|
474 | groups.splice(index + 1, length - index - 1);
|
475 | } else {
|
476 | const groupKey = type.groupKey;
|
477 |
|
478 | const index = findIndex(groups, (group) => group.groupKey === groupKey);
|
479 |
|
480 | if (index > -1) {
|
481 | groups.splice(index, 1);
|
482 | }
|
483 | }
|
484 |
|
485 | this.syncItems(flatGroups(this.getGroups()));
|
486 | }
|
487 | public insertPlaceholders(
|
488 | direction: "start" | "end",
|
489 | items: number | InfiniteGridItemStatus[],
|
490 | groupKey: string | number = makeKey(this.groupKeys, "virtual_"),
|
491 | ) {
|
492 |
|
493 | let infos: InfiniteGridItemInfo[] = [];
|
494 |
|
495 | if (isNumber(items)) {
|
496 | infos = range(items).map(() => ({ type: ITEM_TYPE.VIRTUAL, groupKey }));
|
497 | } else if (Array.isArray(items)) {
|
498 | infos = items.map((status) => ({
|
499 | groupKey,
|
500 | ...status,
|
501 | type: ITEM_TYPE.VIRTUAL,
|
502 | }));
|
503 | }
|
504 | const grid = this._makeGrid();
|
505 | const nextItems = this._syncItemInfos(infos, this.itemKeys);
|
506 |
|
507 | this._updatePlaceholder(nextItems);
|
508 | grid.setItems(nextItems);
|
509 |
|
510 | const group = {
|
511 | type: GROUP_TYPE.VIRTUAL,
|
512 | groupKey,
|
513 | grid,
|
514 | items: nextItems,
|
515 | renderItems: nextItems,
|
516 | };
|
517 |
|
518 | this.groupKeys[groupKey] = group;
|
519 |
|
520 | if (direction === "end") {
|
521 | this.groups.push(group);
|
522 | this.groupItems.push(...nextItems);
|
523 | } else {
|
524 | this.groups.splice(0, 0, group);
|
525 | this.groupItems.splice(0, 0, ...nextItems);
|
526 | if (this.startCursor > -1) {
|
527 | ++this.startCursor;
|
528 | ++this.endCursor;
|
529 | }
|
530 | }
|
531 |
|
532 |
|
533 | return {
|
534 | group,
|
535 | items: nextItems,
|
536 | };
|
537 | }
|
538 |
|
539 | public shouldRerenderItems() {
|
540 | let isRerender = false;
|
541 |
|
542 | this.getVisibleGroups().forEach((group) => {
|
543 | const items = group.items;
|
544 |
|
545 | if (
|
546 | items.length === group.renderItems.length
|
547 | || items.every((item) => item.mountState === MOUNT_STATE.UNCHECKED)
|
548 | ) {
|
549 | return;
|
550 | }
|
551 | isRerender = true;
|
552 | group.renderItems = [...items];
|
553 | });
|
554 | if (isRerender) {
|
555 | this.items = this._getRenderingItems();
|
556 | }
|
557 | return isRerender;
|
558 | }
|
559 |
|
560 | protected _updateItems(items: GridItem[]): void {
|
561 | this.itemRenderer.updateEqualSizeItems(items, this.groupItems);
|
562 | }
|
563 |
|
564 | private _getGroupItems() {
|
565 | return flatGroups(this.getGroups(true));
|
566 | }
|
567 |
|
568 | private _getRenderingItems() {
|
569 | const items = flat(this.getVisibleGroups(true).map((item) => item.renderItems));
|
570 |
|
571 |
|
572 | const loadingGrid = this._loadingGrid;
|
573 | const loadingItem = loadingGrid.getLoadingItem();
|
574 |
|
575 | if (loadingItem) {
|
576 | if (loadingGrid.type === "end") {
|
577 | items.push(loadingItem);
|
578 | } else if (loadingGrid.type === "start") {
|
579 | items.unshift(loadingItem);
|
580 | }
|
581 | }
|
582 |
|
583 | return items;
|
584 | }
|
585 |
|
586 | private _checkShouldRender(options: Record<string, any>) {
|
587 | const GridConstructor = this.options.gridConstructor!;
|
588 | const prevOptions = this.gridOptions;
|
589 | const propertyTypes = GridConstructor.propertyTypes;
|
590 |
|
591 | for (const name in prevOptions) {
|
592 | if (!(name in options) && propertyTypes[name] === PROPERTY_TYPE.RENDER_PROPERTY) {
|
593 | return true;
|
594 | }
|
595 | }
|
596 | for (const name in options) {
|
597 | if (prevOptions[name] !== options[name] && propertyTypes[name] === PROPERTY_TYPE.RENDER_PROPERTY) {
|
598 | return true;
|
599 | }
|
600 | }
|
601 | return false;
|
602 | }
|
603 | private _applyVirtualGrid(grid: Grid, direction: "start" | "end", outline: number[]) {
|
604 | const startOutline = outline.length ? [...outline] : [0];
|
605 | const prevOutlines = grid.getOutlines();
|
606 | const prevOutline = prevOutlines[direction === "end" ? "start" : "end"];
|
607 |
|
608 | if (
|
609 | prevOutline.length !== startOutline.length
|
610 | || prevOutline.some((value, i) => value !== startOutline[i])
|
611 | ) {
|
612 | return {
|
613 | start: [...startOutline],
|
614 | end: [...startOutline],
|
615 | };
|
616 | }
|
617 | return prevOutlines;
|
618 | }
|
619 | private _syncItemInfos(
|
620 | nextItemInfos: InfiniteGridItemStatus[],
|
621 | prevItemKeys: Record<string | number, InfiniteGridItem> = {},
|
622 | ) {
|
623 | const horizontal = this.options.horizontal;
|
624 | const nextItemKeys = this.itemKeys;
|
625 |
|
626 | nextItemInfos.filter((info) => info.key != null).forEach((info) => {
|
627 | const key = info.key!;
|
628 | const prevItem = prevItemKeys[key];
|
629 |
|
630 | if (!prevItem) {
|
631 | nextItemKeys[key] = new InfiniteGridItem(horizontal, {
|
632 | ...info,
|
633 | });
|
634 | } else if (prevItem.type === ITEM_TYPE.VIRTUAL && info.type !== ITEM_TYPE.VIRTUAL) {
|
635 | nextItemKeys[key] = new InfiniteGridItem(horizontal, {
|
636 | orgRect: prevItem.orgRect,
|
637 | rect: prevItem.rect,
|
638 | ...info,
|
639 | });
|
640 | } else {
|
641 | if (info.data) {
|
642 | prevItem.data = info.data;
|
643 | }
|
644 | if (info.groupKey != null) {
|
645 | prevItem.groupKey = info.groupKey!;
|
646 | }
|
647 | if (info.element) {
|
648 | prevItem.element = info.element;
|
649 | }
|
650 | nextItemKeys[key] = prevItem;
|
651 | }
|
652 | });
|
653 | const nextItems = nextItemInfos.map((info) => {
|
654 | let key = info.key!;
|
655 |
|
656 | if (info.key == null) {
|
657 | key = makeKey(nextItemKeys, info.type === ITEM_TYPE.VIRTUAL ? "virtual_" : "");
|
658 | }
|
659 | let item = nextItemKeys[key];
|
660 |
|
661 | if (!item) {
|
662 | const prevItem = prevItemKeys[key];
|
663 |
|
664 | if (prevItem) {
|
665 | item = prevItem;
|
666 |
|
667 | if (info.data) {
|
668 | item.data = info.data;
|
669 | }
|
670 | if (info.element) {
|
671 | item.element = info.element;
|
672 | }
|
673 | } else {
|
674 | item = new InfiniteGridItem(horizontal, {
|
675 | ...info,
|
676 | key,
|
677 | });
|
678 | }
|
679 | nextItemKeys[key] = item;
|
680 | }
|
681 | return item;
|
682 | });
|
683 | return nextItems;
|
684 | }
|
685 | private _registerGroups(groups: InfiniteGridGroup[]) {
|
686 | const nextGroupKeys: Record<string | number, InfiniteGridGroup> = {};
|
687 |
|
688 | groups.forEach((group) => {
|
689 | nextGroupKeys[group.groupKey] = group;
|
690 | });
|
691 |
|
692 | this.groups = groups;
|
693 | this.groupKeys = nextGroupKeys;
|
694 | this.groupItems = this._getGroupItems();
|
695 | }
|
696 | private _splitVirtualGroups(direction: "start" | "end", nextGroups: CategorizedGroup[]) {
|
697 | const groups = splitVirtualGroups(this.groups, direction, nextGroups);
|
698 | const itemKeys = this.itemKeys;
|
699 |
|
700 | groups.forEach(({ renderItems }) => {
|
701 | renderItems.forEach((item) => {
|
702 | itemKeys[item.key] = item;
|
703 | });
|
704 | });
|
705 |
|
706 | return groups;
|
707 | }
|
708 | private _mergeVirtualGroups(groups: Array<CategorizedGroup<InfiniteGridItem>>) {
|
709 | const itemKeys = this.itemKeys;
|
710 | const groupKeys = this.groupKeys;
|
711 |
|
712 | groups.forEach((group) => {
|
713 | const prevGroup = groupKeys[group.groupKey];
|
714 |
|
715 | if (!prevGroup) {
|
716 | return;
|
717 | }
|
718 | const items = group.items;
|
719 |
|
720 | if (items.every((item) => item.mountState === MOUNT_STATE.UNCHECKED)) {
|
721 | prevGroup.renderItems.forEach((item) => {
|
722 | if (item.type === ITEM_TYPE.VIRTUAL && !itemKeys[item.key]) {
|
723 | items.push(item);
|
724 | itemKeys[item.key] = item;
|
725 | }
|
726 | });
|
727 | }
|
728 | });
|
729 | return groups;
|
730 | }
|
731 |
|
732 | private _updatePlaceholder(items = this.groupItems) {
|
733 | const placeholder = this._placeholder;
|
734 |
|
735 | if (!placeholder) {
|
736 | return;
|
737 | }
|
738 |
|
739 | items.filter((item) => item.type === ITEM_TYPE.VIRTUAL).forEach((item) => {
|
740 | setPlaceholder(item, placeholder);
|
741 | });
|
742 | }
|
743 | private _makeGrid() {
|
744 | const GridConstructor = this.options.gridConstructor!;
|
745 | const gridOptions = this.gridOptions;
|
746 | const container = this.containerElement;
|
747 |
|
748 | return new GridConstructor(container, {
|
749 | ...gridOptions,
|
750 | useFit: false,
|
751 | autoResize: false,
|
752 | useResizeObserver: false,
|
753 | observeChildren: false,
|
754 | renderOnPropertyChange: false,
|
755 | externalContainerManager: this.containerManager,
|
756 | externalItemRenderer: this.itemRenderer,
|
757 | });
|
758 | }
|
759 | private _getLoadingGroup(): InfiniteGridGroup {
|
760 | const loadingGrid = this._loadingGrid;
|
761 | const items = loadingGrid.getItems() as InfiniteGridItem[];
|
762 |
|
763 | return {
|
764 | groupKey: LOADING_GROUP_KEY,
|
765 | type: GROUP_TYPE.NORMAL,
|
766 | grid: loadingGrid,
|
767 | items,
|
768 | renderItems: items,
|
769 | };
|
770 | }
|
771 | private _getLoadingItem() {
|
772 | return this._loadingGrid.getLoadingItem();
|
773 | }
|
774 | }
|
775 |
|
776 | export interface GroupManager extends Properties<typeof GroupManager> {
|
777 | getItems(): InfiniteGridItem[];
|
778 | }
|