Skip to content

Commit 91d0c07

Browse files
authored
feat: add useDetachedRecycle option, fix loading state, offset (#585)
* fix: fix loading state * feat: add isReachStart, isReachEnd props and reacthStart, reachEnd props * feat: add `useDetachedRecycle` option * fix: fix scroll offset when scrollSize is samller than containerSize
1 parent eb6d533 commit 91d0c07

17 files changed

+1664
-211
lines changed

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@
4545
"lerna": "^4.0.0",
4646
"typescript": "^4.5.0 <4.6.0"
4747
},
48+
"resolutions": {
49+
"@types/lodash": "4.14.30",
50+
"@storybook/addon-essentials": "6.1.11"
51+
},
4852
"workspaces": {
4953
"packages": [
5054
"packages/*",

packages/infinitegrid/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@egjs/infinitegrid",
3-
"version": "4.12.0",
3+
"version": "4.13.0-beta.8",
44
"description": "A module used to arrange elements including content infinitely according to grid type. With this module, you can implement various grids composed of different card elements whose sizes vary. It guarantees performance by maintaining the number of DOMs the module is handling under any circumstance",
55
"module": "dist/infinitegrid.esm.js",
66
"main": "dist/infinitegrid.cjs.js",
@@ -122,7 +122,7 @@
122122
"@cfcs/core": "^0.0.5",
123123
"@egjs/children-differ": "^1.0.1",
124124
"@egjs/component": "^3.0.0",
125-
"@egjs/grid": "~1.16.0",
125+
"@egjs/grid": "1.17.0-beta.3",
126126
"@egjs/list-differ": "^1.0.0"
127127
}
128128
}

packages/infinitegrid/src/GroupManager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ export class GroupManager extends Grid<GroupManagerOptions> {
155155
}
156156

157157
public waitEndLoading() {
158-
if (this._loadingGrid.type) {
158+
if (!this._loadingGrid.isWaitEnd && this._loadingGrid.type) {
159159
this._loadingGrid.isWaitEnd = true;
160160
return true;
161161
}

packages/infinitegrid/src/Infinite.ts

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export interface InfiniteOptions {
3434
useRecycle?: boolean;
3535
threshold?: number;
3636
defaultDirection?: "start" | "end";
37+
isReachStart?: boolean;
38+
isReachEnd?: boolean;
3739
}
3840

3941
export interface InfiniteItemPart {
@@ -62,9 +64,17 @@ export class Infinite extends Component<InfiniteEvents> {
6264
threshold: 0,
6365
useRecycle: true,
6466
defaultDirection: "end",
67+
isReachStart: false,
68+
isReachEnd: false,
6569
...options,
6670
};
6771
}
72+
public set isReachStart(value: boolean) {
73+
this.options.isReachStart = value;
74+
}
75+
public set isReachEnd(value: boolean) {
76+
this.options.isReachEnd = value;
77+
}
6878
public scroll(scrollPos: number) {
6979
const prevStartCursor = this.startCursor;
7080
const prevEndCursor = this.endCursor;
@@ -75,11 +85,34 @@ export class Infinite extends Component<InfiniteEvents> {
7585
defaultDirection,
7686
threshold,
7787
useRecycle,
88+
isReachEnd,
89+
isReachStart,
7890
} = this.options;
7991
const isDirectionEnd = defaultDirection === "end";
8092

8193
if (!length) {
82-
this.trigger(isDirectionEnd ? "requestAppend" : "requestPrepend", {
94+
if (isReachStart && isReachEnd) {
95+
return;
96+
}
97+
let requestType: "requestAppend" | "requestPrepend" | "" = "";
98+
99+
if (!isReachEnd && isDirectionEnd) {
100+
// 1st order
101+
requestType = "requestAppend";
102+
} else if (!isReachStart && !isDirectionEnd) {
103+
// 2nd order
104+
requestType = "requestPrepend";
105+
} else if (!isReachEnd && isReachStart) {
106+
// 3rd order
107+
requestType = "requestAppend";
108+
} else if (isReachEnd && !isReachStart) {
109+
// 4th order
110+
requestType = "requestPrepend";
111+
}
112+
if (!requestType) {
113+
return;
114+
}
115+
this.trigger(requestType, {
83116
key: undefined,
84117
isVirtual: false,
85118
});
@@ -211,12 +244,12 @@ export class Infinite extends Component<InfiniteEvents> {
211244
}
212245
}
213246
} else if (!this._requestVirtualItems()) {
214-
if ((!isDirectionEnd || !isEnd) && isStart) {
247+
if ((!isDirectionEnd || !isEnd || isReachEnd) && isStart && !isReachStart) {
215248
this.trigger("requestPrepend", {
216249
key: items[prevStartCursor].key,
217250
isVirtual: false,
218251
});
219-
} else if ((isDirectionEnd || !isStart) && isEnd) {
252+
} else if ((isDirectionEnd || !isStart || isReachStart) && isEnd && !isReachEnd) {
220253
this.trigger("requestAppend", {
221254
key: items[prevEndCursor].key,
222255
isVirtual: false,
@@ -361,6 +394,38 @@ export class Infinite extends Component<InfiniteEvents> {
361394
|| visibleResult.removed.length > 0
362395
|| visibleResult.changed.length > 0;
363396
}
397+
398+
const defaultDirection = this.options.defaultDirection;
399+
let prevOutline: number[] = [];
400+
const outlinedItems = [...nextItems];
401+
402+
if (defaultDirection === "start") {
403+
outlinedItems.reverse();
404+
}
405+
outlinedItems.forEach((item, i) => {
406+
if (i > 0 && prevOutline.length) {
407+
if (defaultDirection === "start") {
408+
if (!item.endOutline.length) {
409+
item.endOutline = [...prevOutline];
410+
}
411+
if (!item.startOutline.length) {
412+
item.startOutline = [...item.endOutline];
413+
}
414+
} else {
415+
if (!item.startOutline.length) {
416+
item.startOutline = [...prevOutline];
417+
}
418+
if (!item.endOutline.length) {
419+
item.endOutline = [...item.startOutline];
420+
}
421+
}
422+
}
423+
if (defaultDirection === "start") {
424+
prevOutline = item.startOutline;
425+
} else {
426+
prevOutline = item.endOutline;
427+
}
428+
});
364429
this.setItems(nextItems);
365430
this.setCursors(nextStartCursor, nextEndCursor);
366431
return isChange;

packages/infinitegrid/src/InfiniteGrid.ts

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import Grid, {
1010
GridItem,
1111
ResizeWatcherResizeEvent,
1212
getUpdatedItems,
13+
PROPERTY_TYPE,
1314
} from "@egjs/grid";
1415
import {
1516
DIRECTION,
@@ -93,9 +94,16 @@ class InfiniteGrid<Options extends InfiniteGridOptions = InfiniteGridOptions> ex
9394
renderer: null,
9495
threshold: 100,
9596
useRecycle: true,
97+
useDetachedRecycle: false,
9698
scrollContainer: null,
99+
isReachStart: false,
100+
isReachEnd: false,
97101
appliedItemChecker: (() => false) as (item: InfiniteGridItem, grid: Grid) => boolean,
98102
} as Required<InfiniteGridOptions>;
103+
public static infinitegridTypes = {
104+
isReachEnd: PROPERTY_TYPE.PROPERTY,
105+
isReachStart: PROPERTY_TYPE.PROPERTY,
106+
};
99107
public static propertyTypes = INFINITEGRID_PROPERTY_TYPES;
100108
protected wrapperElement: HTMLElement;
101109
protected scrollManager: ScrollManager;
@@ -394,7 +402,6 @@ class InfiniteGrid<Options extends InfiniteGridOptions = InfiniteGridOptions> ex
394402
scrollManager: this.scrollManager.getStatus(),
395403
};
396404
}
397-
398405
/**
399406
* You can set placeholders to restore status or wait for items to be added.
400407
* @ko status 복구 또는 아이템 추가 대기를 위한 placeholder를 설정할 수 있다.
@@ -622,6 +629,16 @@ class InfiniteGrid<Options extends InfiniteGridOptions = InfiniteGridOptions> ex
622629
public isWait() {
623630
return !!this._waitType;
624631
}
632+
/**
633+
* <ko>scrollOffset(startOffset) 또는 scrollSize의 사이즈를 수동으로 업데이트 한다. 변경이 됐다면 스크롤이 발생시킨다.</ko>
634+
*/
635+
public resizeScroll() {
636+
const result = this._resizeScroll();
637+
638+
if (result) {
639+
this._scroll();
640+
}
641+
}
625642
/**
626643
* Releases the instnace and events and returns the CSS of the container and elements.
627644
* @ko 인스턴스와 이벤트를 해제하고 컨테이너와 엘리먼트들의 CSS를 되돌린다.
@@ -647,6 +664,14 @@ class InfiniteGrid<Options extends InfiniteGridOptions = InfiniteGridOptions> ex
647664
};
648665
});
649666
}
667+
private _setIsReachStart(value: boolean) {
668+
this.options.isReachStart = value;
669+
this.infinite.isReachStart = value;
670+
}
671+
private _setIsReachEnd(value: boolean) {
672+
this.options.isReachEnd = value;
673+
this.infinite.isReachEnd = value;
674+
}
650675
private _syncItems(state?: Record<string, any>): void {
651676
this._getRenderer().syncItems(this._getRendererItems(), state);
652677
}
@@ -659,9 +684,10 @@ class InfiniteGrid<Options extends InfiniteGridOptions = InfiniteGridOptions> ex
659684
private _resizeScroll() {
660685
const scrollManager = this.scrollManager;
661686

662-
scrollManager.resize();
663-
687+
const result = scrollManager.resize();
664688
this.infinite.setSize(scrollManager.getContentSize());
689+
690+
return result;
665691
}
666692
private _syncGroups(isUpdate?: boolean) {
667693
const infinite = this.infinite;
@@ -672,6 +698,7 @@ class InfiniteGrid<Options extends InfiniteGridOptions = InfiniteGridOptions> ex
672698
}
673699
this._syncInfinite();
674700
this.groupManager.setCursors(infinite.getStartCursor(), infinite.getEndCursor());
701+
675702
if (isUpdate) {
676703
this._update();
677704
} else {
@@ -746,6 +773,10 @@ class InfiniteGrid<Options extends InfiniteGridOptions = InfiniteGridOptions> ex
746773

747774
if (orgItem.mountState !== MOUNT_STATE.UNCHECKED) {
748775
orgItem.mountState = MOUNT_STATE.UNMOUNTED;
776+
777+
if (this.options.useDetachedRecycle) {
778+
orgItem.element = null;
779+
}
749780
}
750781
});
751782

@@ -846,6 +877,14 @@ class InfiniteGrid<Options extends InfiniteGridOptions = InfiniteGridOptions> ex
846877
nextGroupKey: e.nextKey,
847878
nextGroupKeys: e.nextKeys || [],
848879
isVirtual: e.isVirtual,
880+
reachStart: () => {
881+
this._setIsReachStart(true);
882+
this._scroll();
883+
},
884+
reachEnd: () => {
885+
this._setIsReachEnd(true);
886+
this._scroll();
887+
},
849888
wait: () => {
850889
this.wait(direction);
851890
},
@@ -877,6 +916,7 @@ class InfiniteGrid<Options extends InfiniteGridOptions = InfiniteGridOptions> ex
877916
const infinite = this.infinite;
878917
const scrollManager = this.scrollManager;
879918
const scrollPos = scrollManager.getRelativeScrollPos()!;
919+
const orgScrollPos = scrollManager.getScrollPos()!;
880920
const prevScrollSize = infinite.getScrollSize();
881921
const prevContainerSize = infinite.getSize();
882922
const prevVisibleArea = infinite.getVisibleArea(scrollPos, direction);
@@ -918,16 +958,23 @@ class InfiniteGrid<Options extends InfiniteGridOptions = InfiniteGridOptions> ex
918958
let offset = nextPos - prevPos;
919959

920960
// If reversed, scroll size (case where container size is reduced)
961+
const nextScrollSize = infinite.getScrollSize();
962+
const nextContainerSize = infinite.getSize();
963+
921964
if (offset < 0) {
922-
const nextScrollSize = infinite.getScrollSize();
923-
const nextContainerSize = infinite.getSize();
924965
const endOffset = Math.max(scrollPos - Math.max(0, prevScrollSize - prevContainerSize), 0);
925966
const nextScollPos
926967
= Math.min(scrollPos, Math.max(0, nextScrollSize - nextContainerSize))
927968
+ endOffset;
928969

929970
// The scroll size is restored to the extent that it has been reduced.
930971
offset += scrollPos - nextScollPos;
972+
} else if (offset > 0) {
973+
// If it is smaller than the scroll size when in the forward direction, the offset is 0.
974+
const maxScrollPos = Math.max(0, nextScrollSize - nextContainerSize);
975+
const nextScrollPos = orgScrollPos + offset;
976+
977+
offset = Math.max(0, Math.min(maxScrollPos, nextScrollPos) - orgScrollPos);
931978
}
932979

933980
this.scrollManager.scrollBy(offset);
@@ -1019,6 +1066,12 @@ class InfiniteGrid<Options extends InfiniteGridOptions = InfiniteGridOptions> ex
10191066
}
10201067
}
10211068

1022-
interface InfiniteGrid extends Properties<typeof InfiniteGrid> { }
1069+
interface InfiniteGrid extends Properties<typeof InfiniteGrid> {
1070+
isReachStart: boolean;
1071+
isReachEnd: boolean;
1072+
}
10231073

10241074
export default InfiniteGrid;
1075+
1076+
1077+

packages/infinitegrid/src/ScrollManager.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ export class ScrollManager extends Component<ScrollManagerEvents> {
130130
eventTarget.scrollTop += y;
131131
}
132132
}
133+
/**
134+
* @return Returns true if scrollOffset or contentSize has changed, otherwise returns false. <ko>scrollOffset 또는 contentSize가 변화가 있으면 true 아니면 false를 반환한다.</ko>
135+
*/
133136
public resize() {
134137
const scrollContainer = this.scrollContainer;
135138
const horizontal = this.options.horizontal;
@@ -139,6 +142,9 @@ export class ScrollManager extends Component<ScrollManagerEvents> {
139142
: scrollContainer.getBoundingClientRect();
140143
const containerRect = this.container.getBoundingClientRect();
141144

145+
const prevScrollOffset = this.scrollOffset;
146+
const prevContentSize = this.contentSize;
147+
142148
this.scrollOffset = (this.getOrgScrollPos()! || 0) + (horizontal
143149
? containerRect.left - scrollContainerRect.left
144150
: containerRect.top - scrollContainerRect.top);
@@ -148,6 +154,8 @@ export class ScrollManager extends Component<ScrollManagerEvents> {
148154
} else {
149155
this.contentSize = horizontal ? scrollContainer.offsetWidth : scrollContainer.offsetHeight;
150156
}
157+
158+
return prevScrollOffset !== this.scrollOffset || prevContentSize !== this.contentSize;
151159
}
152160
public destroy() {
153161
const container = this.container;

packages/infinitegrid/src/consts.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ export const IGNORE_PROPERITES_MAP = {
1111
autoResize: true,
1212
} as const;
1313

14-
1514
export const INFINITEGRID_PROPERTY_TYPES = {
1615
...GRID_PROPERTY_TYPES,
1716
};
@@ -43,6 +42,7 @@ export const ITEM_INFO_PROPERTIES: Record<keyof InfiniteGridItemInfo, true> = {
4342

4443

4544
export const INFINITEGRID_METHODS = [
45+
"resizeScroll",
4646
"insertByGroupIndex",
4747
"updateItems",
4848
"getItems",

0 commit comments

Comments
 (0)