@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 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: idealHeight,
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 = 1
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
- for (let j = i + 1; j <= n; j++) {
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
- for (let r = 0; r < breaks.length; r++) {
70
- const end = breaks[r];
71
- const isLastRow = r === breaks.length - 1;
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 = idealHeight;
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 optionsKey = `${options.rowHeight}|${options.gap ?? 0}|${options.maxShrink ?? 0.75}|${options.maxStretch ?? 1.5}`;
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 { gap = 0, lastRow = "left" } = options;
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.1.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/tsconfig": "0.0.0",
32
- "@slithy/eslint-config": "0.0.0"
31
+ "@slithy/eslint-config": "0.0.0",
32
+ "@slithy/tsconfig": "0.0.0"
33
33
  },
34
34
  "repository": {
35
35
  "type": "git",