From f1e725bee34735cad97fe93bcd9938072fea251e Mon Sep 17 00:00:00 2001 From: Daybrush Date: Fri, 24 Nov 2023 10:52:07 +0900 Subject: [PATCH] feat: add contentAlign prop --- src/grids/MasonryGrid.ts | 51 ++++++++++-- src/types.ts | 7 ++ test/manual/contentAlign.html | 148 ++++++++++++++++++++++++++++++++++ test/unit/MasonryGrid.spec.ts | 36 +++++++++ 4 files changed, 237 insertions(+), 5 deletions(-) create mode 100644 test/manual/contentAlign.html diff --git a/src/grids/MasonryGrid.ts b/src/grids/MasonryGrid.ts index 853bbea..865eb76 100644 --- a/src/grids/MasonryGrid.ts +++ b/src/grids/MasonryGrid.ts @@ -5,7 +5,7 @@ */ import Grid from "../Grid"; import { PROPERTY_TYPE, UPDATE_STATE } from "../consts"; -import { GridOptions, Properties, GridOutlines, GridAlign } from "../types"; +import { GridOptions, Properties, GridOutlines, GridAlign, MasonryGridVerticalAlign } from "../types"; import { range, GetterSetter } from "../utils"; import { GridItem } from "../GridItem"; @@ -19,12 +19,19 @@ function getColumnPoint( return Math[pointCaculationName](...outline.slice(columnIndex, columnIndex + columnCount)); } -function getColumnIndex(outline: number[], columnCount: number, nearestCalculationName: "max" | "min") { +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) => { - return getColumnPoint(outline, index, columnCount, pointCaculationName); + const point = getColumnPoint(outline, index, columnCount, pointCaculationName); + + return Math[pointCaculationName](startPos, point); }); return points[indexCaculationName](Math[nearestCalculationName](...points)); @@ -60,6 +67,13 @@ export interface MasonryGridOptions extends GridOptions { * @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. + * 아이템들의 Content 방향의 정렬. "masonry"는 masonry 형태로 정렬이 된다. 그 외는 inline-block의 vertical-align과 유사하게 content 방향 정렬로 적용이 된다.칼럼(`data-grid-column` )을 여러개 설정하면 화면이 이상하게 보일 수 있다. + * @default "masonry" + */ + contentAlign?: MasonryGridVerticalAlign; /** * Difference Threshold for Counting Columns. Since offsetSize is calculated by rounding, the number of columns may not be accurate. * 칼럼 개수를 계산하기 위한 차이 임계값. offset 사이즈는 반올림으로 게산하기 때문에 정확하지 않을 수 있다. @@ -91,6 +105,7 @@ export class MasonryGrid extends Grid { align: PROPERTY_TYPE.RENDER_PROPERTY, columnCalculationThreshold: PROPERTY_TYPE.RENDER_PROPERTY, maxStretchColumnSize: PROPERTY_TYPE.RENDER_PROPERTY, + contentAlign: PROPERTY_TYPE.RENDER_PROPERTY, }; public static defaultOptions: Required = { ...Grid.defaultOptions, @@ -100,6 +115,7 @@ export class MasonryGrid extends Grid { columnSizeRatio: 0, columnCalculationThreshold: 0.5, maxStretchColumnSize: Infinity, + contentAlign: "masonry", }; public applyGrid(items: GridItem[], direction: "start" | "end", outline: number[]): GridOutlines { @@ -114,6 +130,7 @@ export class MasonryGrid extends Grid { align, observeChildren, columnSizeRatio, + contentAlign, } = this.options; const outlineLength = outline.length; const itemsLength = items.length; @@ -130,9 +147,19 @@ export class MasonryGrid extends Grid { startOutline = range(column).map(() => point); } - const endOutline = startOutline.slice(); + let endOutline = startOutline.slice(); const columnDist = column > 1 ? alignPoses[1] - alignPoses[0] : 0; const isStretch = align === "stretch"; + const isStartContentAlign = isEndDirection && contentAlign === "start"; + + + let startPos = isEndDirection ? -Infinity : Infinity; + + + if (isStartContentAlign) { + // support only end direction + startPos = Math.min(...endOutline); + } for (let i = 0; i < itemsLength; ++i) { const item = items[isEndDirection ? i : itemsLength - 1 - i]; @@ -144,9 +171,16 @@ export class MasonryGrid extends Grid { columnAttribute || Math.max(1, Math.ceil((item.inlineSize + gap) / columnDist)), ); const maxColumnCount = Math.min(column, Math.max(columnCount, maxColumnAttribute)); - let columnIndex = getColumnIndex(endOutline, columnCount, nearestCalculationName); + 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; @@ -191,6 +225,13 @@ export class MasonryGrid extends Grid { }); } + // 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 { diff --git a/src/types.ts b/src/types.ts index 982711b..4a16af5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -210,6 +210,13 @@ export interface OnContentError { */ export type GridAlign = "start" | "center" | "end" | "justify" | "stretch"; +/** + * @typedef + * @memberof Grid + */ +export type MasonryGridVerticalAlign = "masonry" | "start"; + + export type GridEvents = { renderComplete: OnRenderComplete; contentError: OnContentError; diff --git a/test/manual/contentAlign.html b/test/manual/contentAlign.html new file mode 100644 index 0000000..f9b702a --- /dev/null +++ b/test/manual/contentAlign.html @@ -0,0 +1,148 @@ + +
+
+ image1 +
Item 1
+
+
+ image2 +
Item 2
+
+
+ image3 +
Item 3
+
+
+ image4 +
Item 4
+
+
+ image5 +
Item 5
+
+
+ image6 +
Item 6
+
+
+ image7 +
Item 7
+
+
+ image8 +
Item 8
+
+
+ image9 +
Item 9
+
+
+ image10 +
Item 10
+
+
+ + diff --git a/test/unit/MasonryGrid.spec.ts b/test/unit/MasonryGrid.spec.ts index 65c006c..77333d4 100644 --- a/test/unit/MasonryGrid.spec.ts +++ b/test/unit/MasonryGrid.spec.ts @@ -798,5 +798,41 @@ describe("test MasonryGrid", () => { expect(startOutline).to.be.deep.equals([0, 0]); expect(startOutline2).to.be.deep.equals([0]); }); + + it(`should check if it is aligned at the top If contentAlign is "start".`, async () => { + // Given + container!.style.cssText = "width: 600px; height: 600px;"; + grid = new MasonryGrid(container!, { + contentAlign: "start", + }); + + grid.setItems([ + new GridItem(false, { + rect: { width: 300, height: 150, top: 0, left: 0 }, + }), + new GridItem(false, { + rect: { width: 300, height: 100, top: 0, left: 0 }, + }), + new GridItem(false, { + rect: { width: 300, height: 150, top: 0, left: 0 }, + }), + new GridItem(false, { + rect: { width: 300, height: 100, top: 0, left: 0 }, + }), + ]); + + // When + grid.renderItems(); + + await waitEvent(grid, "renderComplete"); + + + // Then + // [0, 0] + // [150, 150] + expect(grid.getItems()[2].cssRect.top).to.be.deep.equals(150); + expect(grid.getItems()[3].cssRect.top).to.be.deep.equals(150); + expect(grid.getOutlines().end).to.be.deep.equals([300, 300]); + }); });