UNPKG

@egjs/grid

Version:

A component that can arrange items according to the type of grids

221 lines (198 loc) 6.49 kB
/** * 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(""); } } }