@egjs/grid
Version:
A component that can arrange items according to the type of grids
221 lines (198 loc) • 6.49 kB
text/typescript
/**
* egjs-grid
* Copyright (c) 2021-present NAVER Corp.
* MIT license
*/
import { DEFAULT_GRID_OPTIONS, MOUNT_STATE, RECT_NAMES, UPDATE_STATE } from "./consts";
import { GridItem } from "./GridItem";
import { DOMRect } from "./types";
import { getDataAttributes, getKeys } from "./utils";
export interface ItemRendererOptions {
attributePrefix?: string;
useTransform?: boolean;
horizontal?: boolean;
percentage?: Array<"position" | "size"> | boolean;
isEqualSize?: boolean;
isConstantSize?: boolean;
useRoundedSize?: boolean;
}
export interface ItemRendererStatus {
initialRects: Record<string, Required<DOMRect>>;
}
export class ItemRenderer {
protected options: Required<ItemRendererOptions>;
protected containerRect: DOMRect;
protected initialRects: Record<string, Required<DOMRect>> = {};
protected sizePercetage = false;
protected posPercetage = false;
constructor(options: ItemRendererOptions) {
this.options = {
attributePrefix: DEFAULT_GRID_OPTIONS.attributePrefix,
useTransform: DEFAULT_GRID_OPTIONS.useTransform,
horizontal: DEFAULT_GRID_OPTIONS.horizontal,
percentage: DEFAULT_GRID_OPTIONS.percentage,
isEqualSize: DEFAULT_GRID_OPTIONS.isEqualSize,
isConstantSize: DEFAULT_GRID_OPTIONS.isConstantSize,
useRoundedSize: DEFAULT_GRID_OPTIONS.useRoundedSize,
...options,
};
this._init();
}
public resize() {
this.initialRects = {};
}
public renderItems(items: GridItem[]) {
items.forEach((item) => {
this._renderItem(item);
});
}
public getInlineSize() {
return this.containerRect[this.options.horizontal ? "height" : "width"]!;
}
public setContainerRect(rect: DOMRect) {
this.containerRect = rect;
}
public updateEqualSizeItems(items: GridItem[], totalItems: GridItem[]) {
this.updateItems(items);
const hasSizeGroup = items.some((item) => item.attributes.sizeGroup);
// Check the rest of the items(totalItems) except `items`.
if (this.options.isEqualSize || hasSizeGroup) {
const updatedItem = items.some((item) => item.updateState === UPDATE_STATE.UPDATED);
if (updatedItem) {
totalItems.forEach((item) => {
if (items.indexOf(item) === -1) {
this.updateItem(item, true);
}
});
}
}
}
public updateItems(items: GridItem[]) {
items.forEach((item) => {
this.updateItem(item);
});
}
public getStatus(): ItemRendererStatus {
return {
initialRects: this.initialRects,
};
}
public setStatus(status: ItemRendererStatus) {
this.initialRects = status.initialRects;
}
private _init() {
const { percentage } = this.options;
let sizePercentage = false;
let posPercentage = false;
if (percentage === true) {
sizePercentage = true;
posPercentage = true;
} else if (percentage) {
if (percentage.indexOf("position") > -1) {
posPercentage = true;
}
if (percentage.indexOf("size") > -1) {
sizePercentage = true;
}
}
this.posPercetage = posPercentage;
this.sizePercetage = sizePercentage;
}
public updateItem(item: GridItem, checkSizeGroup?: boolean) {
const { isEqualSize, isConstantSize, useRoundedSize } = this.options;
const initialRects = this.initialRects;
const { orgRect, element } = item;
const isLoading = item.updateState === UPDATE_STATE.WAIT_LOADING;
const hasOrgSize = orgRect && orgRect.width && orgRect.height;
let rect: Required<DOMRect>;
const attributes: Record<string, string> = element
? getDataAttributes(element, this.options.attributePrefix)
: item.attributes;
const sizeGroup = attributes.sizeGroup ?? "";
const isNotEqualSize = attributes.notEqualSize;
if (sizeGroup !== "" && initialRects[sizeGroup]) {
rect = initialRects[sizeGroup];
} else if (isEqualSize && !isNotEqualSize && !sizeGroup && initialRects[""]) {
rect = initialRects[""];
} else if (isConstantSize && hasOrgSize && !isLoading && item.isFirstUpdate) {
rect = orgRect;
} else if (checkSizeGroup || !element) {
return;
} else {
rect = {
left: element.offsetLeft,
top: element.offsetTop,
width: 0,
height: 0,
};
if (useRoundedSize) {
rect.width = element.offsetWidth;
rect.height = element.offsetHeight;
} else {
const clientRect = element.getBoundingClientRect();
rect.width = clientRect.width;
rect.height = clientRect.height;
}
}
item.attributes = attributes;
item.shouldReupdate = false;
if (!item.isFirstUpdate || !hasOrgSize) {
item.orgRect = { ...rect };
}
item.rect = { ...rect };
// If it's equal size items, it doesn't affect the state.
if (!checkSizeGroup) {
if (item.element) {
item.mountState = MOUNT_STATE.MOUNTED;
}
if (item.updateState === UPDATE_STATE.NEED_UPDATE) {
item.updateState = UPDATE_STATE.UPDATED;
item.isFirstUpdate = true;
}
if (!isLoading && !isNotEqualSize && !initialRects[sizeGroup]) {
initialRects[sizeGroup] = { ...rect };
}
}
return rect;
}
private _renderItem(item: GridItem) {
const element = item.element;
const cssRect = item.cssRect;
if (!element || !cssRect) {
return;
}
const {
horizontal,
useTransform,
} = this.options;
const posPercentage = this.posPercetage;
const sizePercentage = this.sizePercetage;
const cssTexts: string[] = ["position: absolute;"];
const {
inlineSize: sizeName,
inlinePos: posName,
} = RECT_NAMES[horizontal ? "horizontal" : "vertical"];
const inlineSize = this.getInlineSize();
let keys = getKeys(cssRect);
const hasRectProperties = keys.length > 0;
if (useTransform) {
keys = keys.filter((key) => key !== "top" && key !== "left");
cssTexts.push(`transform: `
+ `translate(${cssRect.left || 0}px, ${cssRect.top || 0}px);`
);
}
cssTexts.push(...keys.map((name) => {
const value = cssRect[name]!;
if (
(name === sizeName && sizePercentage) ||
(name === posName && posPercentage)
) {
return `${name}: ${(value / inlineSize) * 100}%;`;
}
return `${name}: ${value}px;`;
}));
if (hasRectProperties) {
element.style.cssText += cssTexts.join("");
}
}
}