@slithy/react-grid-gallery 0.1.2 → 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 +5 -0
- package/dist/index.js +60 -33
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -22,8 +22,12 @@ type GridLayoutRow<T> = {
|
|
|
22
22
|
height: number;
|
|
23
23
|
};
|
|
24
24
|
type GridRow<T> = {
|
|
25
|
+
rowIndex: number;
|
|
26
|
+
startIndex: number;
|
|
25
27
|
items: Array<{
|
|
26
28
|
item: GalleryItem<T>;
|
|
29
|
+
itemIndex: number;
|
|
30
|
+
colIndex: number;
|
|
27
31
|
width: number;
|
|
28
32
|
height: number;
|
|
29
33
|
loaded: boolean;
|
|
@@ -42,6 +46,7 @@ type VirtualWindow = {
|
|
|
42
46
|
declare function useGridGallery<T>(items: GalleryItem<T>[], options: GridOptions, scrollContainerRef?: ScrollContainerRef): {
|
|
43
47
|
containerRef: RefObject<HTMLDivElement | null>;
|
|
44
48
|
rows: GridRow<T>[];
|
|
49
|
+
totalRows: number;
|
|
45
50
|
cellWidth: number;
|
|
46
51
|
cellHeight: number;
|
|
47
52
|
gap: number;
|
package/dist/index.js
CHANGED
|
@@ -80,7 +80,7 @@ function useGridGallery(items, options, scrollContainerRef) {
|
|
|
80
80
|
const [focusedIndex, setFocusedIndex] = useState2(0);
|
|
81
81
|
const pendingFocusRef = useRef2(null);
|
|
82
82
|
const loadedSet = useRef2(/* @__PURE__ */ new Set());
|
|
83
|
-
const [
|
|
83
|
+
const [loadedTick, setLoadedTick] = useState2(0);
|
|
84
84
|
const virtualRange = useVirtualWindow(containerRef, options.virtualize === true, scrollContainerRef);
|
|
85
85
|
useEffect2(() => {
|
|
86
86
|
const observer = new ResizeObserver((entries) => {
|
|
@@ -94,7 +94,7 @@ function useGridGallery(items, options, scrollContainerRef) {
|
|
|
94
94
|
const onLoad = useCallback((key) => {
|
|
95
95
|
if (!loadedSet.current.has(key)) {
|
|
96
96
|
loadedSet.current.add(key);
|
|
97
|
-
|
|
97
|
+
setLoadedTick((v) => v + 1);
|
|
98
98
|
}
|
|
99
99
|
}, []);
|
|
100
100
|
const onError = useCallback((_key) => {
|
|
@@ -110,36 +110,67 @@ function useGridGallery(items, options, scrollContainerRef) {
|
|
|
110
110
|
const resolvedAspectRatio = finitePositive(rawAspectRatio, 1);
|
|
111
111
|
const cellWidth = containerWidth > 0 ? Math.max(0, Math.floor((containerWidth - resolvedGap * (resolvedColumns - 1)) / resolvedColumns)) : 0;
|
|
112
112
|
const cellHeight = cellWidth > 0 ? Math.round(cellWidth / resolvedAspectRatio) : 0;
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
const layoutRows = computeGridLayout(items, resolvedColumns, cellWidth, cellHeight);
|
|
116
|
-
return layoutRows.map((row) => ({
|
|
117
|
-
height: row.height,
|
|
118
|
-
items: row.items.map((item) => ({
|
|
119
|
-
item,
|
|
120
|
-
width: row.width,
|
|
121
|
-
height: row.height,
|
|
122
|
-
loaded: loadedSet.current.has(item.key)
|
|
123
|
-
}))
|
|
124
|
-
}));
|
|
125
|
-
}, [items, resolvedColumns, cellWidth, cellHeight, loadedVersion]);
|
|
113
|
+
const hasLayout = cellWidth > 0 && cellHeight >= 0 && items.length > 0;
|
|
114
|
+
const totalRows = hasLayout ? Math.ceil(items.length / resolvedColumns) : 0;
|
|
126
115
|
const rowStride = cellHeight + resolvedGap;
|
|
127
116
|
let virtualWindow = null;
|
|
128
|
-
if (options.virtualize && virtualRange !== null &&
|
|
129
|
-
const totalRows = rows.length;
|
|
117
|
+
if (options.virtualize && virtualRange !== null && totalRows > 0 && rowStride > 0) {
|
|
130
118
|
const overscan = options.overscan ?? cellHeight * 4;
|
|
131
119
|
const visibleTop = virtualRange.top - overscan;
|
|
132
120
|
const visibleBottom = virtualRange.bottom + overscan;
|
|
133
|
-
|
|
134
|
-
|
|
121
|
+
const maxRowIndex = totalRows - 1;
|
|
122
|
+
const firstIndex = Math.min(maxRowIndex, Math.max(0, Math.floor(visibleTop / rowStride)));
|
|
123
|
+
let lastIndex = Math.min(maxRowIndex, Math.max(0, Math.ceil(visibleBottom / rowStride) - 1));
|
|
135
124
|
if (firstIndex > lastIndex) {
|
|
136
|
-
|
|
137
|
-
lastIndex = totalRows - 1;
|
|
125
|
+
lastIndex = firstIndex;
|
|
138
126
|
}
|
|
139
127
|
const topSpacerHeight = firstIndex * rowStride;
|
|
140
128
|
const bottomSpacerHeight = (totalRows - 1 - lastIndex) * rowStride;
|
|
141
129
|
virtualWindow = { firstIndex, lastIndex, topSpacerHeight, bottomSpacerHeight };
|
|
142
130
|
}
|
|
131
|
+
const rows = useMemo(() => {
|
|
132
|
+
if (!hasLayout) return [];
|
|
133
|
+
if (!options.virtualize) {
|
|
134
|
+
const layoutRows = computeGridLayout(items, resolvedColumns, cellWidth, cellHeight);
|
|
135
|
+
return layoutRows.map((row, rowIndex) => {
|
|
136
|
+
const startIndex = rowIndex * resolvedColumns;
|
|
137
|
+
return {
|
|
138
|
+
rowIndex,
|
|
139
|
+
startIndex,
|
|
140
|
+
height: row.height,
|
|
141
|
+
items: row.items.map((item, colIndex) => ({
|
|
142
|
+
item,
|
|
143
|
+
itemIndex: startIndex + colIndex,
|
|
144
|
+
colIndex,
|
|
145
|
+
width: row.width,
|
|
146
|
+
height: row.height,
|
|
147
|
+
loaded: loadedSet.current.has(item.key)
|
|
148
|
+
}))
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
if (virtualWindow === null) return [];
|
|
153
|
+
const renderRows = [];
|
|
154
|
+
for (let rowIndex = virtualWindow.firstIndex; rowIndex <= virtualWindow.lastIndex; rowIndex++) {
|
|
155
|
+
const startIndex = rowIndex * resolvedColumns;
|
|
156
|
+
const endIndex = Math.min(startIndex + resolvedColumns, items.length);
|
|
157
|
+
const rowItems = items.slice(startIndex, endIndex);
|
|
158
|
+
renderRows.push({
|
|
159
|
+
rowIndex,
|
|
160
|
+
startIndex,
|
|
161
|
+
height: cellHeight,
|
|
162
|
+
items: rowItems.map((item, colIndex) => ({
|
|
163
|
+
item,
|
|
164
|
+
itemIndex: startIndex + colIndex,
|
|
165
|
+
colIndex,
|
|
166
|
+
width: cellWidth,
|
|
167
|
+
height: cellHeight,
|
|
168
|
+
loaded: loadedSet.current.has(item.key)
|
|
169
|
+
}))
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
return renderRows;
|
|
173
|
+
}, [cellHeight, cellWidth, hasLayout, items, options.virtualize, resolvedColumns, virtualWindow, loadedTick]);
|
|
143
174
|
const isControlled = options.focusedIndex !== void 0;
|
|
144
175
|
const padding = options.padding ?? 0;
|
|
145
176
|
function scrollToRow(rowIndex) {
|
|
@@ -233,6 +264,7 @@ function useGridGallery(items, options, scrollContainerRef) {
|
|
|
233
264
|
return {
|
|
234
265
|
containerRef,
|
|
235
266
|
rows,
|
|
267
|
+
totalRows,
|
|
236
268
|
cellWidth,
|
|
237
269
|
cellHeight,
|
|
238
270
|
gap: resolvedGap,
|
|
@@ -249,14 +281,11 @@ function useGridGallery(items, options, scrollContainerRef) {
|
|
|
249
281
|
// src/GridGallery.tsx
|
|
250
282
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
251
283
|
function GridGallery({ items, renderItem, scrollContainerRef, ...options }) {
|
|
252
|
-
const { containerRef, rows, cellHeight, gap, columns, onLoad, onError, virtualWindow, focusedIndex, handleItemFocus, handleItemKeyDown } = useGridGallery(
|
|
284
|
+
const { containerRef, rows, totalRows, cellHeight, gap, columns, onLoad, onError, virtualWindow, focusedIndex, handleItemFocus, handleItemKeyDown } = useGridGallery(
|
|
253
285
|
items,
|
|
254
286
|
options,
|
|
255
287
|
scrollContainerRef
|
|
256
288
|
);
|
|
257
|
-
const firstIndex = virtualWindow?.firstIndex ?? 0;
|
|
258
|
-
const lastIndex = virtualWindow?.lastIndex ?? rows.length - 1;
|
|
259
|
-
const visibleRows = virtualWindow ? rows.slice(firstIndex, lastIndex + 1) : rows;
|
|
260
289
|
const padding = options.padding ?? 0;
|
|
261
290
|
const navigable = options.navigable === true;
|
|
262
291
|
return /* @__PURE__ */ jsxs(
|
|
@@ -264,18 +293,16 @@ function GridGallery({ items, renderItem, scrollContainerRef, ...options }) {
|
|
|
264
293
|
{
|
|
265
294
|
ref: containerRef,
|
|
266
295
|
style: { display: "flex", flexDirection: "column", gap: `${gap}px`, padding: padding > 0 ? `${padding}px` : void 0 },
|
|
267
|
-
...navigable ? { role: "grid", "aria-rowcount":
|
|
296
|
+
...navigable ? { role: "grid", "aria-rowcount": totalRows, "aria-colcount": columns } : {},
|
|
268
297
|
children: [
|
|
269
298
|
virtualWindow && virtualWindow.topSpacerHeight > 0 && /* @__PURE__ */ jsx("div", { style: { height: virtualWindow.topSpacerHeight, contain: "layout" } }),
|
|
270
|
-
|
|
271
|
-
const rowIndex = firstIndex + i;
|
|
299
|
+
rows.map((row) => {
|
|
272
300
|
return /* @__PURE__ */ jsx(
|
|
273
301
|
"div",
|
|
274
302
|
{
|
|
275
303
|
style: { display: "grid", gridTemplateColumns: `repeat(${columns}, 1fr)`, gap: `${gap}px`, contain: "layout" },
|
|
276
|
-
...navigable ? { role: "row", "aria-rowindex": rowIndex + 1 } : {},
|
|
277
|
-
children: row.items.map(({ item, loaded }
|
|
278
|
-
const itemIndex = rowIndex * columns + colIdx;
|
|
304
|
+
...navigable ? { role: "row", "aria-rowindex": row.rowIndex + 1 } : {},
|
|
305
|
+
children: row.items.map(({ item, itemIndex, colIndex, loaded }) => {
|
|
279
306
|
const focused = navigable && focusedIndex === itemIndex;
|
|
280
307
|
return /* @__PURE__ */ jsx(
|
|
281
308
|
"div",
|
|
@@ -283,7 +310,7 @@ function GridGallery({ items, renderItem, scrollContainerRef, ...options }) {
|
|
|
283
310
|
style: { height: `${cellHeight}px` },
|
|
284
311
|
...navigable ? {
|
|
285
312
|
role: "gridcell",
|
|
286
|
-
"aria-colindex":
|
|
313
|
+
"aria-colindex": colIndex + 1,
|
|
287
314
|
tabIndex: focused ? 0 : -1,
|
|
288
315
|
"data-grid-index": itemIndex,
|
|
289
316
|
onKeyDown: (e) => handleItemKeyDown(itemIndex, e),
|
|
@@ -302,7 +329,7 @@ function GridGallery({ items, renderItem, scrollContainerRef, ...options }) {
|
|
|
302
329
|
);
|
|
303
330
|
})
|
|
304
331
|
},
|
|
305
|
-
rowIndex
|
|
332
|
+
row.rowIndex
|
|
306
333
|
);
|
|
307
334
|
}),
|
|
308
335
|
virtualWindow && virtualWindow.bottomSpacerHeight > 0 && /* @__PURE__ */ jsx("div", { style: { height: virtualWindow.bottomSpacerHeight, contain: "layout" } })
|