@slithy/react-tessera-gallery 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +4 -2
- package/dist/index.js +26 -13
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { RefObject, ReactEventHandler, ReactNode } from 'react';
|
|
2
2
|
|
|
3
3
|
type LayoutOptions = {
|
|
4
|
-
rowHeight: number;
|
|
5
|
-
gap?: number;
|
|
4
|
+
rowHeight: number | ((containerWidth: number) => number);
|
|
5
|
+
gap?: number | ((containerWidth: number) => number);
|
|
6
6
|
lastRow?: 'justify' | 'left' | 'center' | 'right' | 'hide';
|
|
7
|
+
maxNumRows?: number;
|
|
7
8
|
maxShrink?: number;
|
|
8
9
|
maxStretch?: number;
|
|
9
10
|
justifyThreshold?: number;
|
|
@@ -37,6 +38,7 @@ declare function computeTesseraLayout(items: {
|
|
|
37
38
|
declare function useTesseraGallery<T>(items: GalleryItem<T>[], options: LayoutOptions): {
|
|
38
39
|
containerRef: RefObject<HTMLDivElement | null>;
|
|
39
40
|
rows: ResolvedRow<T>[];
|
|
41
|
+
gap: number;
|
|
40
42
|
onLoad: (key: string | number, naturalWidth: number, naturalHeight: number) => void;
|
|
41
43
|
};
|
|
42
44
|
|
package/dist/index.js
CHANGED
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
var BADNESS_POWER = 3;
|
|
3
3
|
function computeTesseraLayout(items, containerWidth, options) {
|
|
4
4
|
const {
|
|
5
|
-
rowHeight:
|
|
6
|
-
gap = 0,
|
|
5
|
+
rowHeight: rowHeightOption,
|
|
6
|
+
gap: gapOption = 0,
|
|
7
7
|
lastRow = "left",
|
|
8
|
+
maxNumRows = Infinity,
|
|
8
9
|
maxShrink = 0.75,
|
|
9
10
|
maxStretch = 1.5,
|
|
10
|
-
justifyThreshold =
|
|
11
|
+
justifyThreshold = 0.9
|
|
11
12
|
} = options;
|
|
13
|
+
const idealHeight = typeof rowHeightOption === "function" ? rowHeightOption(containerWidth) : rowHeightOption;
|
|
14
|
+
const gap = typeof gapOption === "function" ? gapOption(containerWidth) : gapOption;
|
|
12
15
|
const n = items.length;
|
|
13
16
|
if (n === 0 || containerWidth <= 0) return [];
|
|
14
17
|
const minHeight = idealHeight * maxShrink;
|
|
@@ -36,9 +39,14 @@ function computeTesseraLayout(items, containerWidth, options) {
|
|
|
36
39
|
dp[0] = 0;
|
|
37
40
|
for (let i = 0; i < n; i++) {
|
|
38
41
|
if (dp[i] === Infinity) continue;
|
|
39
|
-
|
|
42
|
+
let lo = i + 1, hi = n;
|
|
43
|
+
while (lo < hi) {
|
|
44
|
+
const mid = lo + hi >> 1;
|
|
45
|
+
if (rowHeightFor(i, mid) > maxHeight) lo = mid + 1;
|
|
46
|
+
else hi = mid;
|
|
47
|
+
}
|
|
48
|
+
for (let j = lo; j <= n; j++) {
|
|
40
49
|
const h = rowHeightFor(i, j);
|
|
41
|
-
if (h > maxHeight) continue;
|
|
42
50
|
if (h < minHeight) break;
|
|
43
51
|
const cost = dp[i] + badness(h);
|
|
44
52
|
if (cost < dp[j]) {
|
|
@@ -66,9 +74,11 @@ function computeTesseraLayout(items, containerWidth, options) {
|
|
|
66
74
|
}
|
|
67
75
|
const rows = [];
|
|
68
76
|
let start = 0;
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
let prevRowHeight = idealHeight;
|
|
78
|
+
const effectiveBreaks = breaks.slice(0, maxNumRows);
|
|
79
|
+
for (let r = 0; r < effectiveBreaks.length; r++) {
|
|
80
|
+
const end = effectiveBreaks[r];
|
|
81
|
+
const isLastRow = r === effectiveBreaks.length - 1;
|
|
72
82
|
const numGaps = end - start - 1;
|
|
73
83
|
const totalAR = prefixAR[end] - prefixAR[start];
|
|
74
84
|
let actualHeight;
|
|
@@ -85,7 +95,7 @@ function computeTesseraLayout(items, containerWidth, options) {
|
|
|
85
95
|
actualHeight = rowHeightFor(start, end);
|
|
86
96
|
justify = true;
|
|
87
97
|
} else {
|
|
88
|
-
actualHeight =
|
|
98
|
+
actualHeight = prevRowHeight;
|
|
89
99
|
justify = false;
|
|
90
100
|
}
|
|
91
101
|
} else {
|
|
@@ -93,6 +103,7 @@ function computeTesseraLayout(items, containerWidth, options) {
|
|
|
93
103
|
justify = true;
|
|
94
104
|
}
|
|
95
105
|
rows.push(buildRow(items, start, end, actualHeight, justify, containerWidth, gap));
|
|
106
|
+
prevRowHeight = actualHeight;
|
|
96
107
|
start = end;
|
|
97
108
|
}
|
|
98
109
|
return rows;
|
|
@@ -184,7 +195,9 @@ function useTesseraGallery(items, options) {
|
|
|
184
195
|
[]
|
|
185
196
|
);
|
|
186
197
|
const resolvedItems = items.filter((item) => aspectRatioCache.current.has(item.key));
|
|
187
|
-
const
|
|
198
|
+
const resolvedRowHeight = typeof options.rowHeight === "function" ? options.rowHeight(containerWidth) : options.rowHeight;
|
|
199
|
+
const resolvedGap = typeof options.gap === "function" ? options.gap(containerWidth) : options.gap ?? 0;
|
|
200
|
+
const optionsKey = `${resolvedRowHeight}|${resolvedGap}|${options.maxShrink ?? 0.75}|${options.maxStretch ?? 1.5}`;
|
|
188
201
|
if (containerWidth !== committedContainerWidthRef.current || optionsKey !== committedOptionsKeyRef.current || resolvedItems.length < committedItemCountRef.current) {
|
|
189
202
|
committedRowsRef.current = [];
|
|
190
203
|
committedItemCountRef.current = 0;
|
|
@@ -222,14 +235,14 @@ function useTesseraGallery(items, options) {
|
|
|
222
235
|
if (lastFrontierRow) {
|
|
223
236
|
rows.push(toResolvedRow(lastFrontierRow, loadedSet.current));
|
|
224
237
|
}
|
|
225
|
-
return { containerRef, rows, onLoad };
|
|
238
|
+
return { containerRef, rows, gap: resolvedGap, onLoad };
|
|
226
239
|
}
|
|
227
240
|
|
|
228
241
|
// src/TesseraGallery.tsx
|
|
229
242
|
import { jsx } from "react/jsx-runtime";
|
|
230
243
|
function TesseraGallery({ items, renderItem, ...options }) {
|
|
231
|
-
const { containerRef, rows, onLoad } = useTesseraGallery(items, options);
|
|
232
|
-
const {
|
|
244
|
+
const { containerRef, rows, gap, onLoad } = useTesseraGallery(items, options);
|
|
245
|
+
const { lastRow = "left" } = options;
|
|
233
246
|
return /* @__PURE__ */ jsx("div", { ref: containerRef, style: { display: "flex", flexDirection: "column", gap: `${gap}px` }, children: rows.map((row, rowIndex) => {
|
|
234
247
|
const isLastRow = rowIndex === rows.length - 1;
|
|
235
248
|
const justifyContent = isLastRow && lastRow === "center" ? "center" : isLastRow && lastRow === "right" ? "flex-end" : "flex-start";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@slithy/react-tessera-gallery",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "React justified gallery with optimal line-breaking layout.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"tsup": "^8",
|
|
29
29
|
"typescript": "^5",
|
|
30
30
|
"vitest": "^4.1.2",
|
|
31
|
-
"@slithy/
|
|
32
|
-
"@slithy/
|
|
31
|
+
"@slithy/eslint-config": "0.0.0",
|
|
32
|
+
"@slithy/tsconfig": "0.0.0"
|
|
33
33
|
},
|
|
34
34
|
"repository": {
|
|
35
35
|
"type": "git",
|