Source: src/grids/MasonryGrid.ts

/**
 * egjs-grid
 * Copyright (c) 2021-present NAVER Corp.
 * MIT license
 */
import Grid from "../Grid";
import { PROPERTY_TYPE, UPDATE_STATE } from "../consts";
import { GridOptions, Properties, GridOutlines, GridAlign, MasonryGridVerticalAlign } from "../types";
import { range, GetterSetter, between, isString, throttle } from "../utils";
import { GridItem } from "../GridItem";


function getColumnPoint(
  outline: number[],
  columnIndex: number,
  columnCount: number,
  pointCaculationName: "max" | "min",
) {
  return Math[pointCaculationName](...outline.slice(columnIndex, columnIndex + columnCount));
}

function getColumnIndex(
  outline: number[],
  columnCount: number,
  nearestCalculationName: "max" | "min",
  startPos: number,
) {
  const length = outline.length - columnCount + 1;
  const pointCaculationName = nearestCalculationName === "max" ? "min" : "max";
  const indexCaculationName = nearestCalculationName === "max" ? "lastIndexOf" : "indexOf";
  const points = range(length).map((index) => {
    const point = getColumnPoint(outline, index, columnCount, pointCaculationName);

    return Math[pointCaculationName](startPos, point);
  });

  return points[indexCaculationName](Math[nearestCalculationName](...points));
}

/**
 * @typedef
 * @memberof Grid.MasonryGrid
 * @extends Grid.GridOptions
 */
export interface MasonryGridOptions extends GridOptions {
  /**
   * The number of columns. If the number of columns is 0, it is automatically calculated according to the size of the container. Can be used instead of outlineLength.
   * <ko>열의 개수. 열의 개수가 0이라면, 컨테이너의 사이즈에 의해 계산이 된다. outlineLength 대신 사용할 수 있다.</ko>
   * @default 0
   */
  column?: number;
  /**
   * The size of the columns. If it is 0, it is calculated as the size of the first item in items. Can be used instead of outlineSize.
   * <ko>열의 사이즈. 만약 열의 사이즈가 0이면, 아이템들의 첫번째 아이템의 사이즈로 계산이 된다. outlineSize 대신 사용할 수 있다.</ko>
   * @default 0
   */
  columnSize?: number;
  /**
   * The size ratio(inlineSize / contentSize) of the columns. 0 is not set. `true` is automatically calculated as orgInlineSize / orgContentSize.
   * <ko>열의 사이즈 비율(inlineSize / contentSize). 0은 미설정이다. `true` 는 orgInlineSize / orgContentSize로 자동 계산이 된다.</ko>
   * @default 0
   */
  columnSizeRatio?: number | boolean;
  /**
   * Align of the position of the items. If you want to use `stretch`, be sure to set `column`, `columnSize` or `maxStretchColumnSize` option. ("start", "center", "end", "justify", "stretch")
   * <ko>아이템들의 위치의 정렬. `stretch`를 사용하고 싶다면 `column`, `columnSize` 또는 `maxStretchColumnSize` 옵션을 설정해라.  ("start", "center", "end", "justify", "stretch")</ko>
   * @default "justify"
   */
  align?: GridAlign;
  /**
   * Content direction alignment of items. “Masonry” is sorted in the form of masonry. Others are applied as content direction alignment, similar to vertical-align of inline-block.
   * If you set multiple columns (`data-grid-column`), the screen may look strange.
   * <ko>아이템들의 Content 방향의 정렬. "masonry"는 masonry 형태로 정렬이 된다. 그 외는 inline-block의 vertical-align과 유사하게 content 방향 정렬로 적용이 된다.칼럼(`data-grid-column` )을 여러개 설정하면 화면이 이상하게 보일 수 있다. </ko>
   * @default "masonry"
   */
  contentAlign?: MasonryGridVerticalAlign;
  /**
   * Difference Threshold for Counting Columns. Since offsetSize is calculated by rounding, the number of columns may not be accurate.
   * <ko>칼럼 개수를 계산하기 위한 차이 임계값. offset 사이즈는 반올림으로 게산하기 때문에 정확하지 않을 수 있다.</ko>
   * @default 1
   */
  columnCalculationThreshold?: number;
  /**
   * If stretch is used, the column can be automatically calculated by setting the maximum size of the column that can be stretched.
   * <ko>stretch를 사용한 경우 최대로 늘릴 수 있는 column의 사이즈를 설정하여 column을 자동 계산할 수 있다.</ko>
   * @default Infinity
   */
  maxStretchColumnSize?: number;

  /**
   * Adjust the contentSize of the items to make the outlines equal.
   * "scale-down": Scales down to fit the start of the item's outline.
   * "scale-center": Scales down or up to fit the center of the item's outline.
   * "scale-up": Scales up to fit the end of the item's outline.
   * <ko>아이템들의 contentSize를 조절하여 outline을 동등하게 한다.
   * "scale-down": 아이템들의 아웃라인 시작 부분에 맞춰 축소시킨다.
   * "scale-center": 아이템들의 아웃라인 중간 부분에 맞춰 축소시키거나 확장시킨다.
   * "scale-up": 아이템들의 아웃라인 끝 부분에 맞춰 확장시킨다.</ko>
   * @default ""
   */
  stretchOutline?: "scale-down" | "scale-up" | "scale-center" | "";
  /**
   * When using stretchOutline, the contentSize of the items is adjusted to adjust the outline to a value between min and max. ([minSize, maxSize])
   * <ko>stretchOutline를 사용하는 경우 아이템들의 contentSize를 조절하여 container의 outline을 min과 max 사이 값으로 조절한다. ([minSize, maxSize])</ko>
   * @default [0, Infinity]
   */
  stretchContainerSize?: number[];
  /**
   * adjust the minimum and maximum item size of the items. ([minSize, maxSize])
   * <ko>아이템들의 item size 최소, 최대 크기를 조절한다. ([minSize, maxSize])</ko>
   * @default [0, Infinity]
   */
  stretchItemSize?: Array<string | number>;
}

/**
 * MasonryGrid is a grid that stacks items with the same width as a stack of bricks. Adjust the width of all images to the same size, find the lowest height column, and insert a new item.
 * @ko MasonryGrid는 벽돌을 쌓아 올린 모양처럼 동일한 너비를 가진 아이템를 쌓는 레이아웃이다. 모든 이미지의 너비를 동일한 크기로 조정하고, 가장 높이가 낮은 열을 찾아 새로운 이미지를 삽입한다. 따라서 배치된 아이템 사이에 빈 공간이 생기지는 않지만 배치된 레이아웃의 아래쪽은 울퉁불퉁해진다.
 * @memberof Grid
 * @param {HTMLElement | string} container - A base element for a module <ko>모듈을 적용할 기준 엘리먼트</ko>
 * @param {Grid.MasonryGrid.MasonryGridOptions} options - The option object of the MasonryGrid module <ko>MasonryGrid 모듈의 옵션 객체</ko>
 */
@GetterSetter
export class MasonryGrid extends Grid<MasonryGridOptions> {
  public static propertyTypes = {
    ...Grid.propertyTypes,
    column: PROPERTY_TYPE.RENDER_PROPERTY,
    columnSize: PROPERTY_TYPE.RENDER_PROPERTY,
    columnSizeRatio: PROPERTY_TYPE.RENDER_PROPERTY,
    align: PROPERTY_TYPE.RENDER_PROPERTY,
    columnCalculationThreshold: PROPERTY_TYPE.RENDER_PROPERTY,
    maxStretchColumnSize: PROPERTY_TYPE.RENDER_PROPERTY,
    contentAlign: PROPERTY_TYPE.RENDER_PROPERTY,
    stretchOutline: PROPERTY_TYPE.RENDER_PROPERTY,
    stretchContainerSize: PROPERTY_TYPE.RENDER_PROPERTY,
    stretchItemSize: PROPERTY_TYPE.RENDER_PROPERTY,
  };
  public static defaultOptions: Required<MasonryGridOptions> = {
    ...Grid.defaultOptions,
    align: "justify",
    column: 0,
    columnSize: 0,
    columnSizeRatio: 0,
    columnCalculationThreshold: 0.5,
    maxStretchColumnSize: Infinity,
    contentAlign: "masonry",
    stretchOutline: "",
    stretchContainerSize: [0, Infinity],
    stretchItemSize: [0, Infinity],
  };

  public applyGrid(items: GridItem[], direction: "start" | "end", outline: number[]): GridOutlines {
    items.forEach((item) => {
      item.isRestoreOrgCSSText = false;
    });
    const columnSize = this.getComputedOutlineSize(items);
    const column = this.getComputedOutlineLength(items);

    // contentPos를 변경했는지에 따라 computedSize로 설정해야 함.
    let useComputedSize = false;
    const {
      align,
      observeChildren,
      columnSizeRatio,
      contentAlign,
      stretchOutline,
      stretchContainerSize,
      stretchItemSize,
    } = this.options;
    const inlineGap = this.getInlineGap();
    const contentGap = this.getContentGap();
    const outlineLength = outline.length;
    const itemsLength = items.length;
    const alignPoses = this._getAlignPoses(column, columnSize);
    const isEndDirection = direction === "end";
    const nearestCalculationName = isEndDirection ? "min" : "max";
    const pointCalculationName = isEndDirection ? "max" : "min";

    let startOutline = [0];

    if (outlineLength === column) {
      startOutline = outline.slice();
    } else {
      const point = outlineLength ? Math[pointCalculationName](...outline) : 0;

      startOutline = range(column).map(() => point);
    }
    let endOutline = startOutline.slice();
    const columnDist = column > 1 ? alignPoses[1] - alignPoses[0] : 0;
    const isStretch = align === "stretch";
    const isStartContentAlign = isEndDirection && contentAlign === "start";
    const itemIndexes: number[][] = range(startOutline.length).map(() => []);



    let startPos = isEndDirection ? -Infinity : Infinity;


    if (isStartContentAlign) {
      // support only end direction
      startPos = Math.min(...endOutline);
    }


    for (let i = 0; i < itemsLength; ++i) {
      const itemIndex = isEndDirection ? i : itemsLength - 1 - i;
      const item = items[itemIndex];
      const columnAttribute = parseInt(item.attributes.column || "1", 10);
      const maxColumnAttribute = parseInt(item.attributes.maxColumn || "1", 10);
      let contentSize = item.contentSize;
      let columnCount = Math.min(
        column,
        columnAttribute || Math.max(1, Math.ceil((item.inlineSize + inlineGap) / columnDist)),
      );
      const maxColumnCount = Math.min(column, Math.max(columnCount, maxColumnAttribute));
      let columnIndex = getColumnIndex(endOutline, columnCount, nearestCalculationName, startPos);
      let contentPos = getColumnPoint(endOutline, columnIndex, columnCount, pointCalculationName);

      if (isStartContentAlign && startPos !== contentPos) {
        startPos = Math.max(...endOutline);
        endOutline = endOutline.map(() => startPos);
        contentPos = startPos;
        columnIndex = 0;
      }

      while (columnCount < maxColumnCount) {
        const nextEndColumnIndex = columnIndex + columnCount;
        const nextColumnIndex = columnIndex - 1;

        if (isEndDirection && (nextEndColumnIndex >= column || endOutline[nextEndColumnIndex] > contentPos)) {
          break;
        }
        if (!isEndDirection && (nextColumnIndex < 0 || endOutline[nextColumnIndex] < contentPos)) {
          break;
        }
        if (!isEndDirection) {
          --columnIndex;
        }
        ++columnCount;
      }

      columnIndex = Math.max(0, columnIndex);
      columnCount = Math.min(column - columnIndex, columnCount);

      itemIndexes[columnIndex].push(itemIndex);

      // stretch mode or data-grid-column > "1"
      if ((columnAttribute > 0 && columnCount > 1) || isStretch) {
        const nextInlineSize = (columnCount - 1) * columnDist + columnSize;

        if ((!this._isObserverEnabled() || !observeChildren) && item.cssInlineSize !== nextInlineSize) {
          item.shouldReupdate = true;
        }
        item.cssInlineSize = nextInlineSize;
      }
      if (columnSizeRatio === true) {
        const ratio = item.orgContentSize / item.orgInlineSize;

        contentSize = item.computedInlineSize * ratio;
        item.cssContentSize = contentSize;
        useComputedSize = true;
      } else if (columnSizeRatio && columnSizeRatio > 0) {
        contentSize = item.computedInlineSize / columnSizeRatio;
        item.cssContentSize = contentSize;
        useComputedSize = true;
      }
      // 배치가 조금 더 수월하기 위해서는 min, max scaling 1차 작업
      let minStretchSize = 0;
      let maxStretchSize = Infinity;
      let useStretchItemSize = false;

      if (isString(stretchItemSize[0])) {
        const [inline, content] = stretchItemSize[0].split(":").map((value) => parseFloat(value));

        minStretchSize = item.computedInlineSize * content / inline;
        useStretchItemSize = true;
      } else if (stretchItemSize[0]) {
        minStretchSize = stretchItemSize[0];
        useStretchItemSize = true;
      }
      if (isString(stretchItemSize[1])) {
        const [inline, content] = stretchItemSize[1].split(":").map((value) => parseFloat(value));

        maxStretchSize = item.computedInlineSize * content / inline;
        useStretchItemSize = true;
      } else if (stretchItemSize[1] && isFinite(stretchItemSize[1])) {
        // 유한한 숫자라면 stretch 대상으로 판단
        maxStretchSize = stretchItemSize[1];
        useStretchItemSize = true;
      }
      const nextContentSize = between(contentSize, minStretchSize, maxStretchSize);


      // stretchItemSize를 사용한 케이스라면 반영
      if (useStretchItemSize) {
        contentSize = nextContentSize;
        item.cssContentSize = contentSize;
        useComputedSize = true;
      }
      const inlinePos = alignPoses[columnIndex];
      contentPos = isEndDirection ? contentPos : contentPos - contentGap - contentSize;

      item.cssInlinePos = inlinePos;
      item.cssContentPos = contentPos;
      const nextOutlinePoint = isEndDirection ? contentPos + contentSize + contentGap : contentPos;

      range(columnCount).forEach((indexOffset) => {
        endOutline[columnIndex + indexOffset] = nextOutlinePoint;
      });
    }

    if (stretchOutline && items.length) {
      let scalePoint = 0;

      if (stretchOutline === "scale-up") {
        scalePoint = Math.max(...endOutline);
      } else if (stretchOutline === "scale-center") {
        scalePoint = (Math.max(...endOutline) + Math.min(...endOutline)) / 2;
      } else {
        // scale-down
        scalePoint = Math.min(...endOutline);
      }
      if (isEndDirection) {
        // end 방향이면 endOutline이 gap만큼 더 추가되어 있다.
        scalePoint -= contentGap;

        // 높이 제한
        const startPoint = Math.min(...startOutline);
        const nextHeight = between(
          // 높이
          scalePoint - startPoint,
          stretchContainerSize[0],
          stretchContainerSize[1],
        );

        scalePoint = nextHeight + startPoint;
      } else {
        // 높이 제한
        const startPoint = Math.max(...startOutline);
        const nextHeight = between(
          // 반대 방향의 경우 startPoint(start)가 scalePoint(end)보다 큰 숫자다.
          startPoint - scalePoint,
          stretchContainerSize[0],
          stretchContainerSize[1],
        );

        scalePoint = nextHeight - startPoint;
      }

      endOutline.forEach((point, i) => {
        const totalGap = (itemIndexes[i].length - 1) * contentGap;
        const startPoint = startOutline[i];
        const prevSize = Math.abs(point - startPoint) - (isEndDirection ? contentGap : 0) - totalGap;
        const nextSize = (Math.abs(scalePoint - startPoint) - totalGap);
        const scale = nextSize / prevSize;

        if (!prevSize || scale === 1 || !isFinite(scale)) {
          return;
        }
        if (isEndDirection) {
          endOutline[i] = scalePoint + contentGap;
        } else {
          endOutline[i] = scalePoint;
        }

        const length = itemIndexes[i].length;
        let prevPoint = isEndDirection ? startOutline[i] : startOutline[i] - contentGap;

        const itemInfos = itemIndexes[i].map((itemIndex, j) => {
          const item = items[itemIndex];
          const originalSize = useComputedSize
            ? item.computedContentSize
            : item.contentSize;
          let minStretchSize = 0;
          let maxStretchSize = Infinity;

          if (isString(stretchItemSize[0])) {
            const [inline, content] = stretchItemSize[0].split(":").map((value) => parseFloat(value));

            minStretchSize = item.computedInlineSize * content / inline;
          } else {
            minStretchSize = stretchItemSize[0];
          }
          if (isString(stretchItemSize[1])) {
            const [inline, content] = stretchItemSize[1].split(":").map((value) => parseFloat(value));

            maxStretchSize = item.computedInlineSize * content / inline;
          } else {
            maxStretchSize = stretchItemSize[1];
          }


          return {
            item,
            index: j,
            itemIndex,
            minSize: minStretchSize,
            originalSize,
            nextSize: originalSize,
            maxSize: maxStretchSize,
          };
        });

        if (scale > 1) {
          itemInfos.sort((a, b) => {
            return a.originalSize - b.originalSize;
          });
        } else {
          itemInfos.sort((a, b) => {
            return b.originalSize - a.originalSize;
          });
        }
        // minSize, maxSize 제한하여 scale을 적용한다.
        let sumPrevSize = 0;
        let sumNextSize = 0;
        itemInfos.forEach((itemInfo) => {
          // 이전까지의 합 scale은 점점 목표치(`scale`)에 다가가야 한다.
          // 그렇지 않는 경우는 max, min에 도달한 경우라고 판단.

          const nextScale = sumNextSize > nextSize ? scale : (nextSize - sumNextSize) / (prevSize - sumPrevSize);
          const prevItemSize = itemInfo.originalSize;
          const nextItemSize = throttle(between(prevItemSize * nextScale, itemInfo.minSize, itemInfo.maxSize), 0.01);

          sumPrevSize += prevItemSize;
          sumNextSize += nextItemSize;
          itemInfo.nextSize = nextItemSize;
        });
        // minSize, maxSize 적용 이후 gap이 남는 경우 전부 강제 scale 한다.
        if (throttle(sumNextSize - nextSize, 0.01)) {
          const lastScale = throttle(nextSize / sumNextSize, 0.01);

          itemInfos.forEach((itemInfo) => {
            itemInfo.nextSize *= lastScale;
          });
        }
        // 다시 index 순서로 정렬해야 포지션을 설정 가능함
        itemInfos.sort((a, b) => {
          return a.index - b.index;
        });
        itemInfos.forEach((itemInfo, j) => {
          const item = itemInfo.item;
          const nextItemSize = itemInfo.nextSize;
          item.addCSSGridRect({
            contentPos: isEndDirection ? prevPoint : prevPoint - nextItemSize,
            contentSize: nextItemSize,
          });

          if (isEndDirection) {
            prevPoint = item.cssContentPos! + item.cssContentSize! + contentGap;
          } else {
            prevPoint = item.cssContentPos! - contentGap;
          }

          if (j === length - 1) {
            let posOffset = 0;
            let sizeOffset = 0;

            if (isEndDirection) {
              sizeOffset = (item.cssContentPos! % 1 >= 0.5 ? 1 : 0)
                + (item.cssContentSize! % 1 >= 0.5 ? 1 : 0)
                + (scalePoint % 1 >= 0.5 ? -1 : 0);
            } else {
              posOffset = (item.cssContentPos! % 1 < 0.5 ? -1 : 0)
                + (scalePoint % 1 < 0.5 ? 1 : 0);
            }
            // Offset position and size need to be adjusted because they are rounded.


            item.addCSSGridRect({
              contentPos: item.cssContentPos! - posOffset,
              contentSize: item.cssContentSize! - sizeOffset,
            });
          }
        });
      });
    }
    // Finally, check whether startPos and min of the outline match.
    // If different, endOutline is updated.
    if (isStartContentAlign && startPos !== Math.min(...endOutline)) {
      startPos = Math.max(...endOutline);
      endOutline = endOutline.map(() => startPos);
    }

    // if end items, startOutline is low, endOutline is high
    // if start items, startOutline is high, endOutline is low
    return {
      start: isEndDirection ? startOutline : endOutline,
      end: isEndDirection ? endOutline : startOutline,
    };
  }
  public getComputedOutlineSize(items = this.items) {
    const { align } = this.options;
    const inlineGap = this.getInlineGap();
    const containerInlineSize = this.getContainerInlineSize();
    const columnSizeOption = this.columnSize || this.outlineSize;
    const columnOption = this.column || this.outlineLength;
    let column = columnOption || 1;

    let columnSize = 0;

    if (align === "stretch") {
      if (!columnOption) {
        const maxStretchColumnSize = this.maxStretchColumnSize || Infinity;

        column = Math.max(1, Math.ceil((containerInlineSize + inlineGap) / (maxStretchColumnSize + inlineGap)));
      }
      columnSize = (containerInlineSize + inlineGap) / (column || 1) - inlineGap;
    } else if (columnSizeOption) {
      columnSize = columnSizeOption;
    } else if (items.length) {
      let checkedItem = items[0];

      for (const item of items) {
        const attributes = item.attributes;
        const columnAttribute = parseInt(attributes.column || "1", 10);
        const maxColumnAttribute = parseInt(attributes.maxColumn || "1", 10);

        if (
          item.updateState !== UPDATE_STATE.UPDATED
          || !item.inlineSize
          || columnAttribute !== 1
          || maxColumnAttribute !== 1
        ) {
          continue;
        }
        checkedItem = item;
        break;
      }
      const inlineSize = checkedItem.inlineSize || 0;

      columnSize = inlineSize;
    } else {
      columnSize = containerInlineSize;
    }
    return columnSize || 0;
  }
  public getComputedOutlineLength(items = this.items) {
    const inlineGap = this.getInlineGap();
    const columnOption = this.column || this.outlineLength;
    const columnCalculationThreshold = this.columnCalculationThreshold;
    let column = 1;

    if (columnOption) {
      column = columnOption;
    } else {
      const columnSize = this.getComputedOutlineSize(items);

      column = Math.max(
        1,
        Math.floor(
          (this.getContainerInlineSize() + inlineGap) /
          (columnSize - columnCalculationThreshold + inlineGap)
        )
      );
      if (!this.maxStretchColumnSize) {
        column = Math.min(
          items.length,
          column,
        );
      }
    }
    return column;
  }
  private _getAlignPoses(column: number, columnSize: number) {
    const { align } = this.options;
    const inlineGap = this.getInlineGap();
    const containerSize = this.getContainerInlineSize();
    const indexes = range(column);

    let offset = 0;
    let dist = 0;

    if (align === "justify" || align === "stretch") {
      const countDist = column - 1;

      dist = countDist ? Math.max((containerSize - columnSize) / countDist, columnSize + inlineGap) : 0;
      offset = Math.min(0, containerSize / 2 - (countDist * dist + columnSize) / 2);
    } else {
      dist = columnSize + inlineGap;
      const totalColumnSize = (column - 1) * dist + columnSize;

      if (align === "center") {
        offset = (containerSize - totalColumnSize) / 2;
      } else if (align === "end") {
        offset = containerSize - totalColumnSize;
      }
    }
    return indexes.map((i) => {
      return offset + i * dist;
    });
  }
}

export interface MasonryGrid extends Properties<typeof MasonryGrid> {
}


/**
 * Align of the position of the items. If you want to use `stretch`, be sure to set `column` or `columnSize` option. ("start", "center", "end", "justify", "stretch")
 * @ko 아이템들의 위치의 정렬. `stretch`를 사용하고 싶다면 `column` 또는 `columnSize` 옵션을 설정해라.  ("start", "center", "end", "justify", "stretch")
 * @name Grid.MasonryGrid#align
 * @type {$ts:Grid.MasonryGrid.MasonryGridOptions["align"]}
 * @default "justify"
 * @example
 * ```js
 * import { MasonryGrid } from "@egjs/grid";
 *
 * const grid = new MasonryGrid(container, {
 *   align: "start",
 * });
 *
 * grid.align = "justify";
 * ```
 */


/**
 * The number of columns. If the number of columns is 0, it is automatically calculated according to the size of the container.  Can be used instead of outlineLength.
 * @ko 열의 개수. 열의 개수가 0이라면, 컨테이너의 사이즈에 의해 계산이 된다. outlineLength 대신 사용할 수 있다.
 * @name Grid.MasonryGrid#column
 * @type {$ts:Grid.MasonryGrid.MasonryGridOptions["column"]}
 * @default 0
 * @example
 * ```js
 * import { MasonryGrid } from "@egjs/grid";
 *
 * const grid = new MasonryGrid(container, {
 *   column: 0,
 * });
 *
 * grid.column = 4;
 * ```
 */


/**
 * The size of the columns. If it is 0, it is calculated as the size of the first item in items. Can be used instead of outlineSize.
 * @ko 열의 사이즈. 만약 열의 사이즈가 0이면, 아이템들의 첫번째 아이템의 사이즈로 계산이 된다. outlineSize 대신 사용할 수 있다.
 * @name Grid.MasonryGrid#columnSize
 * @type {$ts:Grid.MasonryGrid.MasonryGridOptions["columnSize"]}
 * @default 0
 * @example
 * ```js
 * import { MasonryGrid } from "@egjs/grid";
 *
 * const grid = new MasonryGrid(container, {
 *   columnSize: 0,
 * });
 *
 * grid.columnSize = 200;
 * ```
 */


/**
 * The size ratio(inlineSize / contentSize) of the columns. 0 is not set.
 * @ko 열의 사이즈 비율(inlineSize / contentSize). 0은 미설정이다.
 * @name Grid.MasonryGrid#columnSizeRatio
 * @type {$ts:Grid.MasonryGrid.MasonryGridOptions["columnSizeRatio"]}
 * @default 0
 * @example
 * ```js
 * import { MasonryGrid } from "@egjs/grid";
 *
 * const grid = new MasonryGrid(container, {
 *   columnSizeRatio: 0,
 * });
 *
 * grid.columnSizeRatio = 0.5;
 * ```
 */


/**
 * If stretch is used, the column can be automatically calculated by setting the maximum size of the column that can be stretched.
 * @ko stretch를 사용한 경우 최대로 늘릴 수 있는 column의 사이즈를 설정하여 column을 자동 계산할 수 있다.
 * @name Grid.MasonryGrid#maxStretchColumnSize
 * @type {$ts:Grid.MasonryGrid.MasonryGridOptions["maxStretchColumnSize"]}
 * @default Infinity
 * @example
 * ```js
 * import { MasonryGrid } from "@egjs/grid";
 *
 * const grid = new MasonryGrid(container, {
 *   align: "stretch",
 *   maxStretchColumnSize: 0,
 * });
 *
 * grid.maxStretchColumnSize = 400;
 * ```
 */
comments powered by Disqus