Skip to content

Commit bc7ca4b

Browse files
committed
chore: resize util refactoring
1 parent 5195d39 commit bc7ca4b

File tree

6 files changed

+533
-478
lines changed

6 files changed

+533
-478
lines changed
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
import { asNumber, first } from '../common/util.js';
2+
import type { ResizeState } from '../resize-container/types.js';
3+
import { ResizeUtil } from './resize-util.js';
4+
import type IgcTileComponent from './tile.js';
5+
import type {
6+
ResizeProps,
7+
ResizeSpanProps,
8+
TileGridDimension,
9+
TileGridPosition,
10+
TileResizeDimensions,
11+
} from './types.js';
12+
13+
const CssValues = new RegExp(/(?<start>\d+)?\s*\/?\s*span\s*(?<span>\d+)?/gi);
14+
15+
function parseTileGridRect(tile: IgcTileComponent): TileGridPosition {
16+
const computed = getComputedStyle(tile);
17+
const { gridColumn, gridRow } = computed;
18+
19+
const [column, row] = [
20+
first(Array.from(gridColumn.matchAll(CssValues))).groups!,
21+
first(Array.from(gridRow.matchAll(CssValues))).groups!,
22+
];
23+
24+
return {
25+
column: {
26+
start: asNumber(column.start, -1),
27+
span: asNumber(column.span, -1),
28+
},
29+
row: { start: asNumber(row.start, -1), span: asNumber(row.span, -1) },
30+
};
31+
}
32+
33+
function parseTileParentGrid(gridContainer: HTMLElement) {
34+
const computed = getComputedStyle(gridContainer);
35+
const { gap, gridTemplateColumns, gridTemplateRows } = computed;
36+
37+
const columns = gridTemplateColumns.split(' ').map(asNumber);
38+
const rows = gridTemplateRows.split(' ').map(asNumber);
39+
40+
return {
41+
gap: asNumber(gap),
42+
columns: {
43+
count: columns.length,
44+
entries: columns,
45+
minSize: asNumber(computed.getPropertyValue('--ig-min-col-width')),
46+
},
47+
rows: {
48+
count: rows.length,
49+
entries: rows,
50+
minSize: asNumber(computed.getPropertyValue('--ig-min-row-height')),
51+
},
52+
};
53+
}
54+
55+
class TileResizeState {
56+
private _initialPosition!: TileGridPosition;
57+
private _resizeUtil!: ResizeUtil;
58+
59+
protected _gap = 0;
60+
protected _prevDeltaX = 0;
61+
protected _prevDeltaY = 0;
62+
63+
protected _prevSnappedWidth = 0;
64+
protected _prevSnappedHeight = 0;
65+
66+
protected _position: TileGridPosition = {
67+
column: { start: 0, span: 0 },
68+
row: { start: 0, span: 0 },
69+
};
70+
71+
protected _columns: TileGridDimension = {
72+
count: 0,
73+
entries: [],
74+
minSize: 0,
75+
};
76+
77+
protected _rows: TileGridDimension = {
78+
count: 0,
79+
entries: [],
80+
minSize: 0,
81+
};
82+
83+
public resizedDimensions: TileResizeDimensions = {
84+
width: null,
85+
height: null,
86+
};
87+
88+
public get emptyResizeDimensions(): TileResizeDimensions {
89+
return { width: null, height: null };
90+
}
91+
92+
public get gap(): number {
93+
return this._gap;
94+
}
95+
96+
public get position(): TileGridPosition {
97+
return structuredClone(this._position);
98+
}
99+
100+
public get columns(): TileGridDimension {
101+
return structuredClone(this._columns);
102+
}
103+
104+
public get rows(): TileGridDimension {
105+
return structuredClone(this._rows);
106+
}
107+
108+
public calculateSnappedWidth(state: ResizeState): number {
109+
const resizeProps = this.getResizeProps(state);
110+
const snappedDimension =
111+
this._resizeUtil.calculateSnappedDimension(resizeProps);
112+
113+
this._prevDeltaX = snappedDimension.newDelta;
114+
this._prevSnappedWidth = snappedDimension.snappedSize;
115+
return snappedDimension.snappedSize;
116+
}
117+
118+
public calculateSnappedHeight(state: ResizeState): number {
119+
const resizeProps = this.getResizeProps(state, true);
120+
const snappedDimension =
121+
this._resizeUtil.calculateSnappedDimension(resizeProps);
122+
123+
this._prevDeltaY = snappedDimension.newDelta;
124+
this._prevSnappedHeight = snappedDimension.snappedSize;
125+
return snappedDimension.snappedSize;
126+
}
127+
128+
public updateState(
129+
tileRect: DOMRect,
130+
tile: IgcTileComponent,
131+
grid: HTMLElement
132+
): void {
133+
this.initState(grid, tile);
134+
this.calculateTileStartPosition(grid, tileRect);
135+
}
136+
137+
/**
138+
* Calculates and returns the CSS column and row properties of a tile after resizing,
139+
* based on its new dimensions and starting position.
140+
*/
141+
public calculateResizedGridPosition(rect: DOMRect) {
142+
const { column, row } = this._initialPosition;
143+
144+
const colProps = this.getResizeSpanProps(rect);
145+
const rowProps = this.getResizeSpanProps(rect, true);
146+
147+
// REVIEW pass col minSize and allowOverflow?
148+
this._position.column.span =
149+
this._resizeUtil.calculateResizedSpan(colProps);
150+
this._position.row.span = this._resizeUtil.calculateResizedSpan(rowProps);
151+
152+
const cssColumn = `${column.start < 0 ? 'auto' : column.start} / span ${this._position.column.span}`;
153+
const cssRow = `${row.start < 0 ? 'auto' : row.start} / span ${this._position.row.span}`;
154+
155+
return { column: cssColumn, row: cssRow };
156+
}
157+
158+
public calculateActualSize(grid: HTMLElement) {
159+
const { columns, rows } = parseTileParentGrid(grid);
160+
161+
const width = this._resizeUtil.calculateSizeFromEntries(
162+
columns.entries,
163+
this.position.column
164+
);
165+
const height = this._resizeUtil.calculateSizeFromEntries(
166+
rows.entries,
167+
this.position.row
168+
);
169+
170+
return { width, height };
171+
}
172+
173+
/**
174+
* Checks and adjusts tile spans based on the column count of the tile manager.
175+
*/
176+
// REVIEW once we decide how to handle empty columns.
177+
public adjustTileGridPosition(tiles: IgcTileComponent[]): void {
178+
const columnCount = this.columns.count;
179+
180+
for (const tile of tiles) {
181+
const colStart = tile.colStart || 0;
182+
const colSpan = tile.colSpan || 0;
183+
184+
if (colStart > columnCount) {
185+
//Prioritize span over start?
186+
tile.colSpan = 1;
187+
tile.colStart = columnCount;
188+
continue;
189+
}
190+
191+
if (colStart + colSpan - 1 > columnCount) {
192+
tile.colSpan = columnCount - colStart + 1;
193+
}
194+
}
195+
}
196+
197+
private initState(grid: HTMLElement, tile: IgcTileComponent): void {
198+
const { gap, columns, rows } = parseTileParentGrid(grid);
199+
200+
this._resizeUtil = new ResizeUtil(gap);
201+
this._initialPosition = parseTileGridRect(tile);
202+
this._position = structuredClone(this._initialPosition);
203+
204+
this._gap = gap;
205+
this._columns = columns;
206+
this._rows = rows;
207+
this._prevDeltaX = 0;
208+
this._prevDeltaY = 0;
209+
this._prevSnappedWidth = 0;
210+
this._prevSnappedHeight = 0;
211+
}
212+
213+
private calculateTileStartPosition(
214+
grid: HTMLElement,
215+
tileRect: DOMRect
216+
): void {
217+
if (this._position.column.start < 0) {
218+
const offsetX = this.getGridOffset(grid, 'horizontal');
219+
220+
this._position.column.start = this._resizeUtil.calculatePosition(
221+
tileRect.left + window.scrollX + grid.scrollLeft - offsetX,
222+
this._columns.entries
223+
);
224+
}
225+
226+
if (this._position.row.start < 0) {
227+
const offsetY = this.getGridOffset(grid, 'vertical');
228+
229+
this._position.row.start = this._resizeUtil.calculatePosition(
230+
tileRect.top + window.scrollY - offsetY,
231+
this._rows.entries
232+
);
233+
}
234+
}
235+
236+
private getGridOffset(
237+
grid: HTMLElement,
238+
axis: 'horizontal' | 'vertical'
239+
): number {
240+
const gridRect = grid.getBoundingClientRect();
241+
const computed = getComputedStyle(grid);
242+
243+
return axis === 'horizontal'
244+
? gridRect.left +
245+
window.scrollX +
246+
grid.scrollLeft +
247+
Number.parseFloat(computed.paddingLeft)
248+
: gridRect.top + window.scrollY + Number.parseFloat(computed.paddingTop);
249+
}
250+
251+
private getResizeProps(state: ResizeState, isRow = false): ResizeProps {
252+
return isRow
253+
? {
254+
currentDelta: state.deltaY,
255+
currentSize: state.current.height,
256+
prevDelta: this._prevDeltaY,
257+
gridEntries: this._rows.entries,
258+
startIndex: this._position.row.start,
259+
prevSnapped: this._prevSnappedHeight,
260+
}
261+
: {
262+
currentDelta: state.deltaX,
263+
currentSize: state.current.width,
264+
prevDelta: this._prevDeltaX,
265+
gridEntries: this._columns.entries,
266+
startIndex: this._position.column.start,
267+
prevSnapped: this._prevSnappedWidth,
268+
};
269+
}
270+
271+
private getResizeSpanProps(rect: DOMRect, isRow = false): ResizeSpanProps {
272+
return isRow
273+
? {
274+
targetSize: rect.height,
275+
tilePosition: this.position.row,
276+
tileGridDimension: this.rows,
277+
gap: this._gap,
278+
allowOverflow: true,
279+
}
280+
: {
281+
targetSize: rect.width,
282+
tilePosition: this.position.column,
283+
tileGridDimension: this.columns,
284+
gap: this._gap,
285+
allowOverflow: false,
286+
};
287+
}
288+
}
289+
290+
export function createTileResizeState(): TileResizeState {
291+
return new TileResizeState();
292+
}

0 commit comments

Comments
 (0)