@virtuoso.dev/masonry
Advanced tools
+11
-0
| # Change Log - @virtuoso.dev/masonry | ||
| ## 1.4.0 | ||
| ### Minor Changes | ||
| - [#1318](https://github.com/petyosi/react-virtuoso/pull/1318) [`8d47a4d`](https://github.com/petyosi/react-virtuoso/commit/8d47a4d8a7ecdd4caa36eb336b6921738d73f425) Thanks [@petyosi](https://github.com/petyosi)! - Updated API reference | ||
| ### Patch Changes | ||
| - Updated dependencies [[`8d47a4d`](https://github.com/petyosi/react-virtuoso/commit/8d47a4d8a7ecdd4caa36eb336b6921738d73f425)]: | ||
| - @virtuoso.dev/gurx@1.2.0 | ||
| ## 1.3.5 | ||
@@ -4,0 +15,0 @@ |
+53
-20
@@ -1,21 +0,12 @@ | ||
| /* Excluded from this release type: ComputeItemKey */ | ||
| /** | ||
| * Used for the custom components that accept the masonry context prop. | ||
| */ | ||
| export declare type ContextAwareComponent<Context = any> = React.ComponentType<{ | ||
| /** | ||
| * The value currently passed to the `context` prop of the `VirtuosoMasonry` component. | ||
| */ | ||
| context: Context; | ||
| }>; | ||
| /* Excluded from this release type: Data */ | ||
| /** | ||
| * A React component that's used to render the individual item. | ||
| * A React component that's used to render the individual masonry item. | ||
| * @typeParam Data - The type of items in the data array. | ||
| * @typeParam Context - Optional contextual data passed from the parent component. | ||
| * @group VirtuosoMasonry | ||
| */ | ||
| export declare type ItemContent<Data = any, Context = any> = React.ComponentType<{ | ||
| /** | ||
| * The value of the `context` prop passed to the list. | ||
| * The value of the `context` prop passed to the masonry. | ||
| */ | ||
@@ -28,3 +19,3 @@ context: Context; | ||
| /** | ||
| * The index of the item in the list data array. | ||
| * The index of the item in the data array. | ||
| */ | ||
@@ -38,3 +29,3 @@ index: number; | ||
| * The DOM attributes that you can pass to the `VirtuosoMasonry` component to customize the scroll element. | ||
| * @noInheritDoc | ||
| * @group VirtuosoMasonry | ||
| */ | ||
@@ -45,2 +36,9 @@ export declare type ScrollerProps = Omit<React.HTMLProps<HTMLDivElement>, 'data' | 'onScroll' | 'ref'>; | ||
| /** | ||
| * A React component for efficiently rendering large masonry/Pinterest-style layouts with variable-height items | ||
| * distributed across multiple columns. Supports virtualization, window scrolling, and SSR. | ||
| * @typeParam Data - The type of items in the data array passed to the component. | ||
| * @typeParam Context - Optional contextual data passed to the ItemContent render callback. | ||
| * @group VirtuosoMasonry | ||
| */ | ||
| export declare const VirtuosoMasonry: <Data, Context>(props: VirtuosoMasonryProps<Data, Context> & { | ||
@@ -50,5 +48,17 @@ ref?: NoInfer<React.Ref<Record<string, never>>>; | ||
| /** | ||
| * Props for the VirtuosoMasonry component. | ||
| * @typeParam Data - The type of items in the data array passed to the component. | ||
| * @typeParam Context - Optional contextual data passed to the ItemContent render callback. | ||
| * @group VirtuosoMasonry | ||
| */ | ||
| export declare interface VirtuosoMasonryProps<Data, Context> extends ScrollerProps { | ||
| /** | ||
| * The number of columns to be rendered. This prop is required. | ||
| * The component distributes items across columns using a shortest-column-first algorithm. | ||
| * | ||
| * @example | ||
| * ```tsx | ||
| * <VirtuosoMasonry columnCount={3} data={items} /> | ||
| * ``` | ||
| */ | ||
@@ -61,15 +71,38 @@ columnCount: number; | ||
| /** | ||
| * The data to be rendered. This prop is required. | ||
| * The data array to be rendered. This prop is required. | ||
| * Each item will be passed to the {@link ItemContent} component for rendering. | ||
| * | ||
| * @example | ||
| * ```tsx | ||
| * const items = [ | ||
| * { id: 1, title: 'Item 1', imageUrl: '...' }, | ||
| * { id: 2, title: 'Item 2', imageUrl: '...' }, | ||
| * ] | ||
| * <VirtuosoMasonry data={items} columnCount={3} /> | ||
| * ``` | ||
| */ | ||
| data: Data[]; | ||
| /** | ||
| * Use this prop for SSR rendering. | ||
| * Use this prop for SSR rendering of a pre-defined amount of items. | ||
| */ | ||
| initialItemCount?: number; | ||
| /** | ||
| * A React component that's used to render the individual item. See {@link ItemContent} for further details on the accepted props. | ||
| * A React component that renders each individual item in the masonry grid. | ||
| * Receives `data`, `index`, and `context` as props. | ||
| * | ||
| * @example | ||
| * ```tsx | ||
| * const MyItemContent: ItemContent<MyData> = ({ data, index }) => ( | ||
| * <div style={{ padding: 8 }}> | ||
| * <img src={data.imageUrl} style={{ width: '100%' }} /> | ||
| * <p>{data.title}</p> | ||
| * </div> | ||
| * ) | ||
| * | ||
| * <VirtuosoMasonry data={items} columnCount={3} ItemContent={MyItemContent} /> | ||
| * ``` | ||
| */ | ||
| ItemContent?: NoInfer<ItemContent<Data, Context>>; | ||
| /** | ||
| * Set to true to make the component use window scroll instead of the scroller element. | ||
| * Set to true to make the component use the document scroller instead of creating an element with `overflow-y: auto`. | ||
| */ | ||
@@ -76,0 +109,0 @@ useWindowScroll?: boolean; |
+72
-72
| import { jsxs as Yt, jsx as M, Fragment as Dt } from "react/jsx-runtime"; | ||
| import { Cell as g, link as vt, pipe as w, filter as St, map as y, DerivedCell as $, combine as gt, withLatestFrom as It, changeWith as $t, Signal as Rt, scan as Nt, sub as jt, pub as Gt, Realm as Ut, RealmContext as qt, useRealm as Kt, useCellValues as Jt, useCellValue as Qt } from "@virtuoso.dev/gurx"; | ||
| import { forwardRef as Xt, useMemo as Zt, useImperativeHandle as te, useEffect as Ct, useState as ee, useRef as ft, useCallback as _ } from "react"; | ||
| const Et = ({ index: t }) => /* @__PURE__ */ Yt("div", { children: [ | ||
| import { Cell as g, link as vt, pipe as w, filter as Rt, map as y, DerivedCell as S, combine as gt, withLatestFrom as It, changeWith as St, Signal as $t, scan as Nt, sub as jt, pub as Gt, Realm as Ut, RealmContext as qt, useRealm as Kt, useCellValues as Jt, useCellValue as Qt } from "@virtuoso.dev/gurx"; | ||
| import { forwardRef as Xt, useMemo as Zt, useImperativeHandle as te, useEffect as Et, useState as ee, useRef as ft, useCallback as _ } from "react"; | ||
| const Ct = ({ index: t }) => /* @__PURE__ */ Yt("div", { children: [ | ||
| "Item ", | ||
| t | ||
| ] }), Mt = g(Et), D = g(0), at = g(null), tt = g(null, () => { | ||
| ] }), Mt = g(Ct), D = g(0), at = g(null), tt = g(null, () => { | ||
| vt( | ||
| w( | ||
| tt, | ||
| St((t) => t !== null), | ||
| Rt((t) => t !== null), | ||
| y((t) => t.length) | ||
@@ -16,3 +16,3 @@ ), | ||
| ); | ||
| }), X = g(0), ht = g(0), ne = g(0), Q = g(0), re = X, et = g(0), j = g(!1), oe = $(0, () => w( | ||
| }), X = g(0), ht = g(0), ne = g(0), Q = g(0), et = g(0), j = g(!1), re = S(0, () => w( | ||
| gt(ht, j, et), | ||
@@ -116,3 +116,3 @@ y(([t, e, n]) => e ? t - Math.max(n, 0) : t) | ||
| const r = U(t, e)[0]; | ||
| return se(dt(t, r, n)); | ||
| return oe(dt(t, r, n)); | ||
| } | ||
@@ -131,3 +131,3 @@ function Tt(t, e) { | ||
| } | ||
| function se(t) { | ||
| function oe(t) { | ||
| return Tt(t, ({ k: e, v: n }) => ({ index: e, value: n })); | ||
@@ -143,3 +143,3 @@ } | ||
| } | ||
| function le(t) { | ||
| function se(t) { | ||
| const { endIndex: e, size: n, startIndex: r } = t; | ||
@@ -157,7 +157,7 @@ return (o) => o.start === r && (o.end === e || o.end === Number.POSITIVE_INFINITY) && o.value === n; | ||
| const i = At(t, l - 1, o + 1); | ||
| if (i.some(le(r))) | ||
| if (i.some(se(r))) | ||
| continue; | ||
| let u = !1, c = !1; | ||
| for (const { end: d, start: R, value: S } of i) | ||
| u ? (o >= R || s === S) && (t = mt(t, R)) : (c = S !== s, u = !0), d > o && o >= R && S !== s && (t = k(t, o + 1, S)); | ||
| for (const { end: d, start: $, value: R } of i) | ||
| u ? (o >= $ || s === R) && (t = mt(t, $)) : (c = R !== s, u = !0), d > o && o >= $ && R !== s && (t = k(t, o + 1, R)); | ||
| c && (t = k(t, l, s)); | ||
@@ -185,6 +185,6 @@ } | ||
| } | ||
| function ie(t, e, n) { | ||
| function le(t, e, n) { | ||
| return t[rt(t, e, n)]; | ||
| } | ||
| function ce(t, e, n, r) { | ||
| function ie(t, e, n, r) { | ||
| const o = rt(t, e, r), s = rt(t, n, r, o); | ||
@@ -196,12 +196,12 @@ return t.slice(o, s + 1); | ||
| } | ||
| function ue({ offset: t }, e) { | ||
| function ce({ offset: t }, e) { | ||
| return e === t ? 0 : e < t ? -1 : 1; | ||
| } | ||
| function fe(t) { | ||
| function ue(t) { | ||
| return { index: t.index, value: t }; | ||
| } | ||
| function ae(t, e, n, r = 0) { | ||
| return r > 0 && (e = Math.max(e, ie(t, r, Wt).offset)), e = Math.max(0, e), Tt(ce(t, e, n, ue), fe); | ||
| function fe(t, e, n, r = 0) { | ||
| return r > 0 && (e = Math.max(e, le(t, r, Wt).offset)), e = Math.max(0, e), Tt(ie(t, e, n, ce), ue); | ||
| } | ||
| function he(t, [e, n]) { | ||
| function ae(t, [e, n]) { | ||
| let r = 0, o = 0, s = 0, l = 0; | ||
@@ -222,3 +222,3 @@ if (n !== 0) { | ||
| } | ||
| function me(t, [e, n]) { | ||
| function he(t, [e, n]) { | ||
| if (n.length > 0 && a(t) && e.length === 2) { | ||
@@ -233,4 +233,4 @@ const r = e[0].size, o = e[1].size; | ||
| } | ||
| const zt = Rt(), Ft = Rt(), lt = g([], () => { | ||
| $t(lt, Ft, (t, e) => { | ||
| const zt = $t(), Ft = $t(), lt = g([], () => { | ||
| St(lt, Ft, (t, e) => { | ||
| const n = t.slice(); | ||
@@ -264,3 +264,3 @@ for (const [r, o] of Object.entries(e)) | ||
| ot | ||
| ), $t(ot, zt, (t, e) => { | ||
| ), St(ot, zt, (t, e) => { | ||
| const n = t.map(([o]) => o), r = []; | ||
@@ -273,15 +273,15 @@ for (let o = 0; o < e.length; o++) { | ||
| } | ||
| r.push(me(s, [e[o], []])); | ||
| r.push(he(s, [e[o], []])); | ||
| } | ||
| return r; | ||
| }); | ||
| }), _t = $([], () => w( | ||
| }), _t = S([], () => w( | ||
| ot, | ||
| y((t) => t.map(([e]) => e)) | ||
| )), de = $([], (t) => t.pipe( | ||
| )), me = S([], (t) => t.pipe( | ||
| ot, | ||
| y((e) => e.map(([, n]) => n)) | ||
| )), it = $([], () => w( | ||
| )), it = S([], () => w( | ||
| _t, | ||
| It(de), | ||
| It(me), | ||
| Nt((t, [e, n]) => { | ||
@@ -291,7 +291,7 @@ const r = []; | ||
| const s = t[o] ?? [[]]; | ||
| r.push(he(s[0], [e[o], n[o]])); | ||
| r.push(ae(s[0], [e[o], n[o]])); | ||
| } | ||
| return r; | ||
| }, []) | ||
| )), xe = $( | ||
| )), de = S( | ||
| [], | ||
@@ -302,3 +302,3 @@ () => w( | ||
| ) | ||
| ), ve = $( | ||
| ), xe = S( | ||
| [], | ||
@@ -309,3 +309,3 @@ () => w( | ||
| ) | ||
| ), ge = $( | ||
| ), ve = S( | ||
| [], | ||
@@ -316,3 +316,3 @@ () => w( | ||
| ) | ||
| ), Ie = $( | ||
| ), ge = S( | ||
| [], | ||
@@ -323,3 +323,3 @@ (t) => t.pipe( | ||
| ) | ||
| ), Y = $([], () => w( | ||
| ), Y = S([], () => w( | ||
| P, | ||
@@ -344,11 +344,11 @@ It(D, pt, lt), | ||
| }) | ||
| )), pe = $( | ||
| )), Ie = S( | ||
| [], | ||
| () => w( | ||
| gt(P, D, Ie, ge, ve, Y), | ||
| gt(P, D, ge, ve, xe, Y), | ||
| y(([t, e, n, r, o, s]) => { | ||
| const l = s.reduce((c, d) => c + d.length, 0), i = Math.ceil((e - l) / t); | ||
| return Array.from({ length: t }, (c, d) => { | ||
| const S = s[d].length + i - n[d]; | ||
| return r[d] + S * o[d]; | ||
| const R = s[d].length + i - n[d]; | ||
| return r[d] + R * o[d]; | ||
| }); | ||
@@ -362,8 +362,8 @@ }) | ||
| P, | ||
| X, | ||
| re, | ||
| oe, | ||
| _t, | ||
| xe, | ||
| de, | ||
| D, | ||
| pe, | ||
| Ie, | ||
| tt, | ||
@@ -386,4 +386,4 @@ Y, | ||
| d, | ||
| $, | ||
| R, | ||
| S, | ||
| B | ||
@@ -393,3 +393,3 @@ ]) => { | ||
| return e; | ||
| const bt = B ? S + r : 0, f = Math.min(Math.max(r - bt, 0), Math.max(...u) - o), h = f + o, b = R === 0 ? Math.min(n, i) : R; | ||
| const bt = B ? R + r : 0, f = Math.min(Math.max(r - bt, 0), Math.max(...u) - o), h = f + o, b = $ === 0 ? Math.min(n, i) : $; | ||
| return { | ||
@@ -399,12 +399,12 @@ columns: Array.from( | ||
| (q, I) => { | ||
| const x = e.columns[I], V = s[I] ?? nt(), C = l[I] ?? [], p = u[I] ?? 0, H = d[I] ?? []; | ||
| const x = e.columns[I], V = s[I] ?? nt(), E = l[I] ?? [], p = u[I] ?? 0, H = d[I] ?? []; | ||
| if (a(V) || H.length === 0) { | ||
| const A = []; | ||
| let T = 0; | ||
| for (let E = I; E < b; E += n) | ||
| for (let C = I; C < b; C += n) | ||
| A.push({ | ||
| columnIndex: I, | ||
| data: c == null ? void 0 : c[E], | ||
| data: c == null ? void 0 : c[C], | ||
| height: 0, | ||
| index: E, | ||
| index: C, | ||
| indexInColumn: T++, | ||
@@ -418,3 +418,3 @@ offset: 0 | ||
| listTop: 0, | ||
| offsetTree: C, | ||
| offsetTree: E, | ||
| totalHeight: 0, | ||
@@ -426,13 +426,13 @@ viewportBottom: h | ||
| // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition | ||
| x && x.offsetTree === C && e.totalCount === i && e.data === c && e.indexesInColumns === d && f >= x.listTop && h <= x.listBottom | ||
| (x == null ? void 0 : x.offsetTree) === E && e.totalCount === i && e.data === c && e.indexesInColumns === d && f >= x.listTop && h <= x.listBottom | ||
| ) | ||
| return x; | ||
| const K = [], W = H.length - 1, z = 0, ct = ae(C, f, h, z); | ||
| const K = [], W = H.length - 1, z = 0, ct = fe(E, f, h, z); | ||
| let v = 0, L = 0, O = !1; | ||
| for (const A of ct) { | ||
| const { | ||
| value: { height: T, offset: E } | ||
| value: { height: T, offset: C } | ||
| } = A; | ||
| let F = A.start; | ||
| v = E, E < f && (F += Math.floor((f - E) / T), v += (F - A.start) * T), F < z && (v += (z - F) * T, F = z); | ||
| v = C, C < f && (F += Math.floor((f - C) / T), v += (F - A.start) * T), F < z && (v += (z - F) * T, F = z); | ||
| const Bt = Math.min(A.end, W); | ||
@@ -456,3 +456,3 @@ for (let J = F; J <= Bt && !(v >= h); J++) { | ||
| listTop: L, | ||
| offsetTree: C, | ||
| offsetTree: E, | ||
| totalHeight: p, | ||
@@ -480,3 +480,3 @@ viewportBottom: h | ||
| st, | ||
| St((e) => e.columns.some((n) => n.listBottom < n.viewportBottom)) | ||
| Rt((e) => e.columns.some((n) => n.listBottom < n.viewportBottom)) | ||
| ), | ||
@@ -491,4 +491,4 @@ (e) => { | ||
| ); | ||
| }), be = Xt( | ||
| ({ columnCount: t, context: e, data: n, initialItemCount: r = 0, ItemContent: o = Et, useWindowScroll: s = !1, ...l }, i) => { | ||
| }), pe = Xt( | ||
| ({ columnCount: t, context: e, data: n, initialItemCount: r = 0, ItemContent: o = Ct, useWindowScroll: s = !1, ...l }, i) => { | ||
| const u = Zt(() => { | ||
@@ -505,3 +505,3 @@ const c = new Ut(); | ||
| }, []); | ||
| return te(i, () => ({}), []), Ct(() => { | ||
| return te(i, () => ({}), []), Et(() => { | ||
| u.pubIn({ | ||
@@ -512,7 +512,7 @@ [P]: t, | ||
| }); | ||
| }), /* @__PURE__ */ M(qt.Provider, { value: u, children: /* @__PURE__ */ M(ye, { ...l }) }); | ||
| }), /* @__PURE__ */ M(qt.Provider, { value: u, children: /* @__PURE__ */ M(we, { ...l }) }); | ||
| } | ||
| ); | ||
| be.displayName = "VirtuosoMasonry"; | ||
| const we = { | ||
| pe.displayName = "VirtuosoMasonry"; | ||
| const be = { | ||
| // eslint-disable-next-line @typescript-eslint/no-empty-function | ||
@@ -524,3 +524,3 @@ observe: () => { | ||
| } | ||
| }, ye = ({ style: t, ...e }) => { | ||
| }, we = ({ style: t, ...e }) => { | ||
| const n = Kt(), [r] = ee(() => { | ||
@@ -530,3 +530,3 @@ if (typeof ResizeObserver > "u") { | ||
| throw new Error("ResizeObserver not found. Please ensure that you have a polyfill installed."); | ||
| return we; | ||
| return be; | ||
| } | ||
@@ -537,3 +537,3 @@ return new ResizeObserver((f) => { | ||
| for (let V = 0; V < h; V++) { | ||
| const C = f[V], p = C.target; | ||
| const E = f[V], p = E.target; | ||
| if (p === l.current) { | ||
@@ -545,5 +545,5 @@ const O = p.ownerDocument.defaultView; | ||
| [X]: N ? O.scrollY : p.scrollTop, | ||
| [ht]: N ? O.innerHeight : C.contentRect.height, | ||
| [ht]: N ? O.innerHeight : E.contentRect.height, | ||
| [ne]: N ? O.innerWidth : p.clientWidth, | ||
| ...N ? {} : { [et]: C.contentRect.top, [Q]: p.scrollHeight } | ||
| ...N ? {} : { [et]: E.contentRect.top, [Q]: p.scrollHeight } | ||
| }; | ||
@@ -561,3 +561,3 @@ continue; | ||
| continue; | ||
| const H = Number.parseInt(p.dataset.index), K = Number.parseFloat(p.dataset.knownSize ?? ""), W = C.contentRect.height, z = Number.parseInt(p.dataset.columnIndex ?? "0"), ct = Number.parseInt(p.dataset.absoluteIndex ?? "0"); | ||
| const H = Number.parseInt(p.dataset.index), K = Number.parseFloat(p.dataset.knownSize ?? ""), W = E.contentRect.height, z = Number.parseInt(p.dataset.columnIndex ?? "0"), ct = Number.parseInt(p.dataset.absoluteIndex ?? "0"); | ||
| if (I[ct] = W, W === K) | ||
@@ -604,4 +604,4 @@ continue; | ||
| [r, c] | ||
| ), [R, S, B] = Jt(st, Mt, j); | ||
| return Ct(() => { | ||
| ), [$, R, B] = Jt(st, Mt, j); | ||
| return Et(() => { | ||
| var f; | ||
@@ -641,3 +641,3 @@ if (B) { | ||
| }, | ||
| children: R.columns.map((f, h) => /* @__PURE__ */ M( | ||
| children: $.columns.map((f, h) => /* @__PURE__ */ M( | ||
| "div", | ||
@@ -654,3 +654,3 @@ { | ||
| }, | ||
| children: f.items.map((b) => /* @__PURE__ */ M(Se, { item: b, ItemContent: S, mount: o, unmount: s }, b.index)) | ||
| children: f.items.map((b) => /* @__PURE__ */ M(ye, { item: b, ItemContent: R, mount: o, unmount: s }, b.index)) | ||
| }, | ||
@@ -661,3 +661,3 @@ `column-${h}` | ||
| ) }); | ||
| }, Se = ({ item: t, ItemContent: e, mount: n, unmount: r }) => { | ||
| }, ye = ({ item: t, ItemContent: e, mount: n, unmount: r }) => { | ||
| const o = Qt(at), s = ft(null), l = _( | ||
@@ -689,3 +689,3 @@ (i) => { | ||
| export { | ||
| be as VirtuosoMasonry | ||
| pe as VirtuosoMasonry | ||
| }; |
+33
-33
@@ -18,6 +18,11 @@ { | ||
| "homepage": "https://virtuoso.dev/virtuoso-masonry/", | ||
| "repository": { | ||
| "type": "git", | ||
| "url": "git+https://github.com/petyosi/react-virtuoso.git", | ||
| "directory": "packages/masonry" | ||
| }, | ||
| "bugs": { | ||
| "email": "support@virtuoso.dev" | ||
| }, | ||
| "version": "1.3.5", | ||
| "version": "1.4.0", | ||
| "type": "module", | ||
@@ -29,18 +34,7 @@ "module": "dist/index.js", | ||
| "publishConfig": { | ||
| "access": "public" | ||
| "access": "public", | ||
| "provenance": true | ||
| }, | ||
| "scripts": { | ||
| "build": "tsc && vite build", | ||
| "dev": "vite build", | ||
| "ladle": "ladle serve", | ||
| "test": "vitest", | ||
| "e2e": "echo 'No e2e tests'", | ||
| "typecheck": "tsc --noEmit", | ||
| "docs:build": "npx typedoc --plugin typedoc-plugin-no-inherit --watch --excludeInternal --excludePrivate --out docs --name 'Virtuoso Masonry' src/index.ts", | ||
| "docs:serve": "npx serve docs", | ||
| "lint": "eslint", | ||
| "knip": "knip" | ||
| }, | ||
| "dependencies": { | ||
| "@virtuoso.dev/gurx": "*" | ||
| "@virtuoso.dev/gurx": "1.2.0" | ||
| }, | ||
@@ -52,11 +46,11 @@ "peerDependencies": { | ||
| "devDependencies": { | ||
| "@virtuoso.dev/tooling": "*", | ||
| "@ladle/react": "^5.0.1", | ||
| "@microsoft/api-extractor": "^7.55.1", | ||
| "@types/node": "^22.10.1", | ||
| "@types/react": "^18.2.64", | ||
| "@types/react-dom": "^18.2.21", | ||
| "@vitejs/plugin-react-swc": "^3.7.2", | ||
| "@vitest/browser": "^3.0.4", | ||
| "@types/react": "^19.2.7", | ||
| "@types/react-dom": "^19.2.3", | ||
| "@vitejs/plugin-react": "^4.7.0", | ||
| "@vitest/browser": "^4.0.16", | ||
| "@vitest/browser-playwright": "^4.0.16", | ||
| "eslint": "^9.19.0", | ||
| "globals": "^15.14.0", | ||
| "jsdom": "^26.0.0", | ||
@@ -67,12 +61,9 @@ "knip": "^5.38.3", | ||
| "prettier": "^3.4.2", | ||
| "react": "18.3.1", | ||
| "react-dom": "18.3.1", | ||
| "resize-observer-polyfill": "^1.5.1", | ||
| "semantic-release": "^24.2.0", | ||
| "typedoc": "^0.27.2", | ||
| "typedoc-plugin-no-inherit": "^1.4.0", | ||
| "typescript": "^5.7.3", | ||
| "vite": "^6.0.11", | ||
| "vite-plugin-dts": "^4.5.0", | ||
| "vitest": "^3.0.4" | ||
| "react": "19.2.0", | ||
| "react-dom": "19.2.0", | ||
| "typescript": "~5.9.3", | ||
| "vite": "^6.4.1", | ||
| "vite-plugin-dts": "^4.5.4", | ||
| "vitest": "^4.0.16", | ||
| "@virtuoso.dev/tooling": "0.1.0" | ||
| }, | ||
@@ -82,3 +73,12 @@ "files": [ | ||
| "CHANGELOG.md" | ||
| ] | ||
| } | ||
| ], | ||
| "scripts": { | ||
| "build": "tsc && vite build", | ||
| "dev": "vite build", | ||
| "ladle": "ladle serve", | ||
| "test": "vitest", | ||
| "typecheck": "tsgo --noEmit", | ||
| "lint": "eslint", | ||
| "knip": "knip" | ||
| } | ||
| } |
+109
-1
@@ -1,1 +0,109 @@ | ||
| # React Virtuoso Masonry | ||
| # Virtuoso Masonry | ||
| [](https://www.npmjs.com/package/@virtuoso.dev/masonry) [](https://www.npmjs.com/package/@virtuoso.dev/masonry) [](https://github.com/petyosi/react-virtuoso/blob/master/LICENSE) [](https://www.typescriptlang.org/) | ||
| A virtualized masonry layout component for React that arranges items in a grid with varying heights. | ||
| ## Features | ||
| - **Virtualized rendering** - only renders visible items for optimal performance with large datasets | ||
| - **Variable item heights** - items can have different heights, automatically measured and arranged | ||
| - **Dynamic column count** - change column count based on screen/container width | ||
| - **Window scroll support** - can use the window as the scroll container | ||
| - **Just-in-time distribution** - items are distributed to columns as you scroll | ||
| :::note | ||
| The column distribution algorithm distributes the items just-in-time, so if you scroll very fast, you will be able to see the arrangement happening. | ||
| ::: | ||
| ## Installation | ||
| ```bash | ||
| npm install @virtuoso.dev/masonry | ||
| ``` | ||
| ## Quick Start | ||
| ```tsx live | ||
| import { VirtuosoMasonry } from '@virtuoso.dev/masonry' | ||
| import { useMemo } from 'react' | ||
| const ItemContent: React.FC<{ data: number }> = ({ data }) => { | ||
| const height = data % 10 === 0 ? 200 : data % 5 === 0 ? 180 : data % 7 ? 150 : 120 | ||
| return ( | ||
| <div style={{ padding: '5px' }}> | ||
| <div style={{ height, border: '1px solid black' }}>Item {data}</div> | ||
| </div> | ||
| ) | ||
| } | ||
| export default function App() { | ||
| const data = useMemo(() => { | ||
| return Array.from({ length: 1000 }, (_, index) => index) | ||
| }, []) | ||
| return ( | ||
| <div> | ||
| <VirtuosoMasonry columnCount={3} data={data} style={{ height: 500 }} initialItemCount={50} ItemContent={ItemContent} /> | ||
| </div> | ||
| ) | ||
| } | ||
| ``` | ||
| ## Window Scroll Example | ||
| ```tsx live | ||
| import { VirtuosoMasonry } from '@virtuoso.dev/masonry' | ||
| import { useEffect, useMemo, useState } from 'react' | ||
| function useWindowWidth() { | ||
| const [width, setWidth] = useState(window.innerWidth) | ||
| useEffect(() => { | ||
| const handleResize = () => setWidth(window.innerWidth) | ||
| window.addEventListener('resize', handleResize) | ||
| return () => window.removeEventListener('resize', handleResize) | ||
| }, []) | ||
| return width | ||
| } | ||
| const ItemContent: React.FC<{ data: number }> = ({ data }) => { | ||
| const height = data % 10 === 0 ? 200 : data % 5 === 0 ? 180 : data % 7 ? 150 : 120 | ||
| return ( | ||
| <div style={{ padding: '5px' }}> | ||
| <div style={{ height, border: '1px solid black' }}>Item {data}</div> | ||
| </div> | ||
| ) | ||
| } | ||
| export default function App() { | ||
| const data = useMemo(() => { | ||
| return Array.from({ length: 1000 }, (_, index) => index) | ||
| }, []) | ||
| const width = useWindowWidth() | ||
| const columnCount = useMemo(() => { | ||
| if (width < 500) { | ||
| return 2 | ||
| } | ||
| if (width < 800) { | ||
| return 3 | ||
| } | ||
| return 4 | ||
| }, [width]) | ||
| return ( | ||
| <div> | ||
| <VirtuosoMasonry columnCount={columnCount} data={data} useWindowScroll={true} initialItemCount={50} ItemContent={ItemContent} /> | ||
| </div> | ||
| ) | ||
| } | ||
| ``` | ||
| ## Links | ||
| - [Documentation](https://virtuoso.dev/masonry/) | ||
| - [API Reference](https://virtuoso.dev/masonry/api-reference/) | ||
| - [Contributing](https://github.com/petyosi/react-virtuoso/blob/master/CONTRIBUTING.md) | ||
| - [Changelog](./CHANGELOG.md) | ||
| - [License](./LICENSE) |
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
29622
21.75%21
-12.5%753
4.87%0
-100%110
5400%1
-50%+ Added
- Removed
Updated