From a577cf0cb8c91f34b5a2e90a7b011ff5e395569c Mon Sep 17 00:00:00 2001 From: Daybrush Date: Wed, 21 Aug 2024 17:11:53 +0900 Subject: [PATCH] feat: add isReachStart, isReachEnd props and reacthStart, reachEnd props --- packages/infinitegrid/src/Infinite.ts | 39 ++++++++- packages/infinitegrid/src/InfiniteGrid.ts | 47 ++++++++++- packages/infinitegrid/src/ScrollManager.ts | 8 ++ packages/infinitegrid/src/consts.ts | 1 - packages/infinitegrid/src/types.ts | 94 ++++++++++++++++++---- packages/infinitegrid/src/utils.ts | 28 ++++++- 6 files changed, 194 insertions(+), 23 deletions(-) diff --git a/packages/infinitegrid/src/Infinite.ts b/packages/infinitegrid/src/Infinite.ts index c9d5fd7f7..4058e69f6 100644 --- a/packages/infinitegrid/src/Infinite.ts +++ b/packages/infinitegrid/src/Infinite.ts @@ -34,6 +34,8 @@ export interface InfiniteOptions { useRecycle?: boolean; threshold?: number; defaultDirection?: "start" | "end"; + isReachStart?: boolean; + isReachEnd?: boolean; } export interface InfiniteItemPart { @@ -62,9 +64,17 @@ export class Infinite extends Component { threshold: 0, useRecycle: true, defaultDirection: "end", + isReachStart: false, + isReachEnd: false, ...options, }; } + public set isReachStart(value: boolean) { + this.options.isReachStart = value; + } + public set isReachEnd(value: boolean) { + this.options.isReachEnd = value; + } public scroll(scrollPos: number) { const prevStartCursor = this.startCursor; const prevEndCursor = this.endCursor; @@ -75,11 +85,34 @@ export class Infinite extends Component { defaultDirection, threshold, useRecycle, + isReachEnd, + isReachStart, } = this.options; const isDirectionEnd = defaultDirection === "end"; if (!length) { - this.trigger(isDirectionEnd ? "requestAppend" : "requestPrepend", { + if (isReachStart && isReachEnd) { + return; + } + let requestType: "requestAppend" | "requestPrepend" | "" = ""; + + if (!isReachEnd && isDirectionEnd) { + // 1st order + requestType = "requestAppend"; + } else if (!isReachStart && !isDirectionEnd) { + // 2nd order + requestType = "requestPrepend"; + } else if (!isReachEnd && isReachStart) { + // 3rd order + requestType = "requestAppend"; + } else if (isReachEnd && !isReachStart) { + // 4th order + requestType = "requestPrepend"; + } + if (!requestType) { + return; + } + this.trigger(requestType, { key: undefined, isVirtual: false, }); @@ -211,12 +244,12 @@ export class Infinite extends Component { } } } else if (!this._requestVirtualItems()) { - if ((!isDirectionEnd || !isEnd) && isStart) { + if ((!isDirectionEnd || !isEnd || isReachEnd) && isStart && !isReachStart) { this.trigger("requestPrepend", { key: items[prevStartCursor].key, isVirtual: false, }); - } else if ((isDirectionEnd || !isStart) && isEnd) { + } else if ((isDirectionEnd || !isStart || isReachStart) && isEnd && !isReachEnd) { this.trigger("requestAppend", { key: items[prevEndCursor].key, isVirtual: false, diff --git a/packages/infinitegrid/src/InfiniteGrid.ts b/packages/infinitegrid/src/InfiniteGrid.ts index e61c0a4c8..88b5a71f1 100644 --- a/packages/infinitegrid/src/InfiniteGrid.ts +++ b/packages/infinitegrid/src/InfiniteGrid.ts @@ -10,6 +10,7 @@ import Grid, { GridItem, ResizeWatcherResizeEvent, getUpdatedItems, + PROPERTY_TYPE, } from "@egjs/grid"; import { DIRECTION, @@ -94,8 +95,14 @@ class InfiniteGrid ex threshold: 100, useRecycle: true, scrollContainer: null, + isReachStart: false, + isReachEnd: false, appliedItemChecker: (() => false) as (item: InfiniteGridItem, grid: Grid) => boolean, } as Required; + public static infinitegridTypes = { + isReachEnd: PROPERTY_TYPE.PROPERTY, + isReachStart: PROPERTY_TYPE.PROPERTY, + }; public static propertyTypes = INFINITEGRID_PROPERTY_TYPES; protected wrapperElement: HTMLElement; protected scrollManager: ScrollManager; @@ -394,7 +401,6 @@ class InfiniteGrid ex scrollManager: this.scrollManager.getStatus(), }; } - /** * You can set placeholders to restore status or wait for items to be added. * @ko status 복구 또는 아이템 추가 대기를 위한 placeholder를 설정할 수 있다. @@ -622,6 +628,16 @@ class InfiniteGrid ex public isWait() { return !!this._waitType; } + /** + * scrollOffset(startOffset) 또는 scrollSize의 사이즈를 수동으로 업데이트 한다. 변경이 됐다면 스크롤이 발생시킨다. + */ + public resizeScroll() { + const result = this._resizeScroll(); + + if (result) { + this._scroll(); + } + } /** * Releases the instnace and events and returns the CSS of the container and elements. * @ko 인스턴스와 이벤트를 해제하고 컨테이너와 엘리먼트들의 CSS를 되돌린다. @@ -647,6 +663,14 @@ class InfiniteGrid ex }; }); } + private _setIsReachStart(value: boolean) { + this.options.isReachStart = value; + this.infinite.isReachStart = value; + } + private _setIsReachEnd(value: boolean) { + this.options.isReachEnd = value; + this.infinite.isReachEnd = value; + } private _syncItems(state?: Record): void { this._getRenderer().syncItems(this._getRendererItems(), state); } @@ -659,9 +683,10 @@ class InfiniteGrid ex private _resizeScroll() { const scrollManager = this.scrollManager; - scrollManager.resize(); - + const result = scrollManager.resize(); this.infinite.setSize(scrollManager.getContentSize()); + + return result; } private _syncGroups(isUpdate?: boolean) { const infinite = this.infinite; @@ -846,6 +871,14 @@ class InfiniteGrid ex nextGroupKey: e.nextKey, nextGroupKeys: e.nextKeys || [], isVirtual: e.isVirtual, + reachStart: () => { + this._setIsReachStart(true); + this._scroll(); + }, + reachEnd: () => { + this._setIsReachEnd(true); + this._scroll(); + }, wait: () => { this.wait(direction); }, @@ -1019,6 +1052,12 @@ class InfiniteGrid ex } } -interface InfiniteGrid extends Properties { } +interface InfiniteGrid extends Properties { + isReachStart: boolean; + isReachEnd: boolean; +} export default InfiniteGrid; + + + diff --git a/packages/infinitegrid/src/ScrollManager.ts b/packages/infinitegrid/src/ScrollManager.ts index 006c6b4d8..bab956ca6 100644 --- a/packages/infinitegrid/src/ScrollManager.ts +++ b/packages/infinitegrid/src/ScrollManager.ts @@ -130,6 +130,9 @@ export class ScrollManager extends Component { eventTarget.scrollTop += y; } } + /** + * @return Returns true if scrollOffset or contentSize has changed, otherwise returns false. scrollOffset 또는 contentSize가 변화가 있으면 true 아니면 false를 반환한다. + */ public resize() { const scrollContainer = this.scrollContainer; const horizontal = this.options.horizontal; @@ -139,6 +142,9 @@ export class ScrollManager extends Component { : scrollContainer.getBoundingClientRect(); const containerRect = this.container.getBoundingClientRect(); + const prevScrollOffset = this.scrollOffset; + const prevContentSize = this.contentSize; + this.scrollOffset = (this.getOrgScrollPos()! || 0) + (horizontal ? containerRect.left - scrollContainerRect.left : containerRect.top - scrollContainerRect.top); @@ -148,6 +154,8 @@ export class ScrollManager extends Component { } else { this.contentSize = horizontal ? scrollContainer.offsetWidth : scrollContainer.offsetHeight; } + + return prevScrollOffset !== this.scrollOffset || prevContentSize !== this.contentSize; } public destroy() { const container = this.container; diff --git a/packages/infinitegrid/src/consts.ts b/packages/infinitegrid/src/consts.ts index 1dd5b33c8..439b445db 100644 --- a/packages/infinitegrid/src/consts.ts +++ b/packages/infinitegrid/src/consts.ts @@ -11,7 +11,6 @@ export const IGNORE_PROPERITES_MAP = { autoResize: true, } as const; - export const INFINITEGRID_PROPERTY_TYPES = { ...GRID_PROPERTY_TYPES, }; diff --git a/packages/infinitegrid/src/types.ts b/packages/infinitegrid/src/types.ts index baea3cf5a..dca5994b8 100644 --- a/packages/infinitegrid/src/types.ts +++ b/packages/infinitegrid/src/types.ts @@ -80,6 +80,15 @@ export interface InfiniteGridOptions extends GridOptions { * @default true */ useRecycle?: boolean; + /** + * @default false + */ + isReachStart?: boolean; + /** + * @default false + */ + isReachEnd?: boolean; + /** * You can set the scrollContainer directly. In this case, the container becomes the wrapper itself. * @ko scrollContainer를 직접 정할 수 있다. 이 경우 container는 wrapper 자기 자신이 된다. @@ -119,41 +128,98 @@ export interface InsertedPlaceholdersResult { /** * @typedef - * @property - An InfiniteGrid instance that triggered this event. 이 이벤트를 트리거한 InfiniteGrid의 인스턴스 - * @property - Last group key. 마지막 그룹의 키. - * @property - The key of the next group that should replace Virtual Item(placeholder)s. Virtual Item(placeholder)들을 대체해야 할 다음 그룹의 키. - * @property - Array of the following group keys that need to be replaced with Virtual Item(placeholder)s. Virtual Item(placeholder)들을 대체해야 할 다음 그룹키 배열. - * @property - Whether to request virtual groups corresponding to Virtual Item(placeholder)s. Virtual Item(placeholder)들에 해당하는 가상의 그룹을 요청하는지 여부 - * @property - Set to standby to request data. 데이터를 요청하기 위해 대기 상태로 설정한다. - * @property - When the data request is complete, it is set to ready state. 데이터 요청이 끝났다면 준비 상태로 설정한다. */ export interface OnRequestAppend { + /** + * An InfiniteGrid instance that triggered this event. 이 이벤트를 트리거한 InfiniteGrid의 인스턴스 + */ currentTarget: InfiniteGrid; + /** + * Last group key. 마지막 그룹의 키. + */ groupKey: string | number | undefined; + /** + * The key of the next group that should replace Virtual Item(placeholder)s. Virtual Item(placeholder)들을 대체해야 할 다음 그룹의 키. + */ nextGroupKey?: string | number | undefined; + /** + * Array of the following group keys that need to be replaced with Virtual Item(placeholder)s. + * Virtual Item(placeholder)들을 대체해야 할 다음 그룹키 배열. + */ nextGroupKeys: Array; + /** + * Whether to request virtual groups corresponding to Virtual Item(placeholder)s. + * Virtual Item(placeholder)들에 해당하는 가상의 그룹을 요청하는지 여부 + */ isVirtual: boolean; + /** + * Call this when the end has been reached and there are no more groups (or items) to add. Or set the `isReachEnd` prop to true. + * 끝에 도달해서 더 이상 추가할 그룹(또는 아이템)이 없는 경우 호출해라. 또는 `isReachEnd` 옵션을 true로 설정해라. + */ + reachEnd(): void; + /** + * Call this when the start has been reached and there are no more groups (or items) to add. Or set the `isReachStart` prop to true. + * 시작에 도달해서 더 이상 추가할 그룹(또는 아이템)이 없는 경우 호출해라. 또는 `isReachStart` 옵션을 true로 설정해라. + */ + reachStart(): void; + /** + * Set to standby to request data. 데이터를 요청하기 위해 대기 상태로 설정한다. + */ wait(): void; + /** + * When the data request is complete, it is set to ready state. 데이터 요청이 끝났다면 준비 상태로 설정한다. + */ ready(): void; } /** * @typedef - * @property - An InfiniteGrid instance that triggered this event. 이 이벤트를 트리거한 InfiniteGrid의 인스턴스 - * @property - First group key. 첫번째 그룹의 키. - * @property - The key of the next group that should replace Virtual Item(placeholder)s. Virtual Item(placeholder)들을 대체해야 할 다음 그룹의 키. - * @property - Array of the following group keys that need to be replaced with Virtual Item(placeholder)s. Virtual Item(placeholder)들을 대체해야 할 다음 그룹키 배열. - * @property - Whether to request virtual groups corresponding to Virtual Item(placeholder)s. Virtual Item(placeholder)들에 해당하는 가상의 그룹을 요청하는지 여부 - * @property - Set to standby to request data. 데이터를 요청하기 위해 대기 상태로 설정한다. - * @property - When the data request is complete, it is set to ready state. 데이터 요청이 끝났다면 준비 상태로 설정한다. */ export interface OnRequestPrepend { + /** + * An InfiniteGrid instance that triggered this event. + * 이 이벤트를 트리거한 InfiniteGrid의 인스턴스 + */ currentTarget: InfiniteGrid; + /** + * The key of the next group that should replace Virtual Item(placeholder)s. + * Virtual Item(placeholder)들을 대체해야 할 다음 그룹의 키. + */ groupKey: string | number | undefined; + /** + * The key of the next group that should replace Virtual Item(placeholder)s. + * Virtual Item(placeholder)들을 대체해야 할 다음 그룹의 키. + */ nextGroupKey?: string | number | undefined; + /** + * Array of the following group keys that need to be replaced with Virtual Item(placeholder)s. + * Virtual Item(placeholder)들을 대체해야 할 다음 그룹키 배열. + */ nextGroupKeys: Array; + /** + * Whether to request virtual groups corresponding to Virtual Item(placeholder)s. + * Virtual Item(placeholder)들에 해당하는 가상의 그룹을 요청하는지 여부 + */ isVirtual: boolean; + /** + * Call this when the end has been reached and there are no more groups (or items) to add. Or set the `isReachEnd` prop to true. + * 끝에 도달해서 더 이상 추가할 그룹(또는 아이템)이 없는 경우 호출해라. 또는 `isReachEnd` 옵션을 true로 설정해라. + */ + reachEnd(): void; + /** + * Call this when the start has been reached and there are no more groups (or items) to add. Or set the `isReachStart` prop to true. + * 시작에 도달해서 더 이상 추가할 그룹(또는 아이템)이 없는 경우 호출해라. 또는 `isReachStart` 옵션을 true로 설정해라. + */ + reachStart(): void; + /** + * Set to standby to request data. + * 데이터를 요청하기 위해 대기 상태로 설정한다. + */ wait(): void; + /** + * When the data request is complete, it is set to ready state. + * 데이터 요청이 끝났다면 준비 상태로 설정한다. + */ ready(): void; } diff --git a/packages/infinitegrid/src/utils.ts b/packages/infinitegrid/src/utils.ts index def822f61..85036aa20 100644 --- a/packages/infinitegrid/src/utils.ts +++ b/packages/infinitegrid/src/utils.ts @@ -1,4 +1,4 @@ -import { withClassMethods } from "@cfcs/core"; +import { camelize, withClassMethods } from "@cfcs/core"; import Grid, { GRID_PROPERTY_TYPES } from "@egjs/grid"; import { diff } from "@egjs/list-differ"; import { GROUP_TYPE, IGNORE_PROPERITES_MAP, INFINITEGRID_METHODS, ITEM_INFO_PROPERTIES, ITEM_TYPE } from "./consts"; @@ -287,11 +287,37 @@ export function getRenderingItems(items: InfiniteGridItemInfo[], options: Render export function InfiniteGridGetterSetter(component: { prototype: InfiniteGrid, propertyTypes: typeof GRID_PROPERTY_TYPES, + infinitegridTypes: any, }) { const { prototype, propertyTypes, + infinitegridTypes, } = component; + + for (const name in infinitegridTypes) { + const attributes: Record = { + enumerable: true, + configurable: true, + get: function get(this: InfiniteGrid) { + const options = this.options; + + return options[name]; + }, + set: function set(this: InfiniteGrid, value: any) { + const setterName = `_${camelize(`set ${name}`)}`; + + if (this[setterName]) { + this[setterName](value); + } else { + this.options[name] = value; + } + }, + }; + + + Object.defineProperty(prototype, name, attributes); + } for (const name in propertyTypes) { const attributes: Record = { enumerable: true,