UNPKG

@virtuoso.dev/masonry

Version:

Virtualized React component for rendering masonry layouts

649 lines (648 loc) 18.1 kB
import { jsxs as Yt, jsx as A } from "react/jsx-runtime"; import { forwardRef as Dt, useMemo as jt, useImperativeHandle as Gt, useEffect as St, useState as Ut, useRef as ft, useCallback as _ } from "react"; import { Cell as v, link as vt, pipe as b, filter as Rt, map as w, DerivedCell as R, combine as It, withLatestFrom as gt, changeWith as $t, Signal as Nt, scan as Ct, sub as qt, pub as Kt, Realm as Jt, RealmContext as Qt, useRealm as Xt, useCellValues as Zt, useCellValue as te } from "@virtuoso.dev/gurx"; const Et = ({ index: t }) => /* @__PURE__ */ Yt("div", { children: [ "Item ", t ] }), Mt = v(Et), D = v(0), at = v(null), tt = v(null, () => { vt( b( tt, Rt((t) => t !== null), w((t) => t.length) ), D ); }), X = v(0), ht = v(0), ee = v(0), Q = v(0), et = v(0), j = v(!1), ne = R(0, () => b( It(ht, j, et), w(([t, e, n]) => e ? t - Math.max(n, 0) : t) )), G = { lvl: 0 }; function Vt(t, e, n, r = G, o = G) { return { k: t, l: r, lvl: n, r: o, v: e }; } function a(t) { return t === G; } function nt() { return G; } function dt(t, e) { if (a(t)) return G; const { k: n, l: r, r: o } = t; if (e === n) { if (a(r)) return o; if (a(o)) return r; const [s, l] = Ht(r); return Z(h(t, { k: s, l: Ot(r), v: l })); } return e < n ? Z(h(t, { l: dt(r, e) })) : Z(h(t, { r: dt(o, e) })); } function U(t, e, n = "k") { if (a(t)) return [Number.NEGATIVE_INFINITY, void 0]; if (t[n] === e) return [t.k, t.v]; if (t[n] < e) { const r = U(t.r, e, n); return r[0] === Number.NEGATIVE_INFINITY ? [t.k, t.v] : r; } return U(t.l, e, n); } function T(t, e, n) { return a(t) ? Vt(e, n, 1) : e === t.k ? h(t, { k: e, v: n }) : e < t.k ? yt(h(t, { l: T(t.l, e, n) })) : yt(h(t, { r: T(t.r, e, n) })); } function mt(t, e, n) { if (a(t)) return []; const { k: r, l: o, r: s, v: l } = t; let i = []; return r > e && (i = i.concat(mt(o, e, n))), r >= e && r <= n && i.push({ k: r, v: l }), r <= n && (i = i.concat(mt(s, e, n))), i; } function Ht(t) { return a(t.r) ? [t.k, t.v] : Ht(t.r); } function Ot(t) { return a(t.r) ? t.l : Z(h(t, { r: Ot(t.r) })); } function h(t, e) { return Vt(e.k ?? t.k, e.v ?? t.v, e.lvl ?? t.lvl, e.l ?? t.l, e.r ?? t.r); } function ut(t) { return a(t) || t.lvl > t.r.lvl; } function yt(t) { return xt(kt(t)); } function Z(t) { const { l: e, lvl: n, r } = t; if (r.lvl >= n - 1 && e.lvl >= n - 1) return t; if (n > r.lvl + 1) { if (ut(e)) return kt(h(t, { lvl: n - 1 })); if (!a(e) && !a(e.r)) return h(e.r, { l: h(e, { r: e.r.l }), lvl: n, r: h(t, { l: e.r.r, lvl: n - 1 }) }); throw new Error("Unexpected empty nodes"); } if (ut(t)) return xt(h(t, { lvl: n - 1 })); if (!a(r) && !a(r.l)) { const o = r.l, s = ut(o) ? r.lvl - 1 : r.lvl; return h(o, { l: h(t, { lvl: n - 1, r: o.l }), lvl: o.lvl + 1, r: xt(h(r, { l: o.r, lvl: s })) }); } throw new Error("Unexpected empty nodes"); } function At(t, e, n) { if (a(t)) return []; const r = U(t, e)[0]; return re(mt(t, r, n)); } function Tt(t, e) { const n = t.length; if (n === 0) return []; let { index: r, value: o } = e(t[0]); const s = []; for (let l = 1; l < n; l++) { const { index: i, value: u } = e(t[l]); s.push({ end: i - 1, start: r, value: o }), r = i, o = u; } return s.push({ end: Number.POSITIVE_INFINITY, start: r, value: o }), s; } function re(t) { return Tt(t, ({ k: e, v: n }) => ({ index: e, value: n })); } function xt(t) { const { lvl: e, r: n } = t; return !a(n) && !a(n.r) && n.lvl === e && n.r.lvl === e ? h(n, { l: h(t, { r: n.l }), lvl: e + 1 }) : t; } function kt(t) { const { l: e } = t; return !a(e) && e.lvl === t.lvl ? h(e, { r: h(t, { l: e.r }) }) : t; } function oe(t) { const { endIndex: e, size: n, startIndex: r } = t; return (o) => o.start === r && (o.end === e || o.end === Number.POSITIVE_INFINITY) && o.value === n; } function Wt(t, e) { let n = a(t) ? 0 : Number.POSITIVE_INFINITY; for (const r of e) { const { endIndex: o, size: s, startIndex: l } = r; if (n = Math.min(n, l), a(t)) { t = T(t, 0, s); continue; } const i = At(t, l - 1, o + 1); if (i.some(oe(r))) continue; let u = !1, f = !1; for (const { end: m, start: $, value: y } of i) u ? (o >= $ || s === y) && (t = dt(t, $)) : (f = y !== s, u = !0), m > o && o >= $ && y !== s && (t = T(t, o + 1, y)); f && (t = T(t, l, s)); } return [t, n]; } function rt(t, e, n, r = 0) { let o = t.length - 1; for (; r <= o; ) { const s = Math.floor((r + o) / 2), l = t[s], i = n(l, e); if (i === 0) return s; if (i === -1) { if (o - r < 2) return s - 1; o = s - 1; } else { if (o === r) return s; r = s + 1; } } throw new Error(`Failed binary finding record in array - ${t.join(",")}, searched for ${e}`); } function se(t, e, n) { return t[rt(t, e, n)]; } function le(t, e, n, r) { const o = rt(t, e, r), s = rt(t, n, r, o); return t.slice(o, s + 1); } function Pt({ index: t }, e) { return e === t ? 0 : e < t ? -1 : 1; } function ie({ offset: t }, e) { return e === t ? 0 : e < t ? -1 : 1; } function ce(t) { return { index: t.index, value: t }; } function ue(t, e, n, r = 0) { return r > 0 && (e = Math.max(e, se(t, r, Pt).offset)), e = Math.max(0, e), Tt(le(t, e, n, ie), ce); } function fe(t, [e, n]) { let r = 0, o = 0, s = 0, l = 0; if (n !== 0) { l = rt(t, n - 1, Pt), s = t[l].offset; const u = U(e, n - 1); if (u[1] === void 0) throw new Error("Invariant violation"); r = u[0], o = u[1], t.length && t[l].height === U(e, n)[1] && (l -= 1), t = t.slice(0, l + 1); } else t = []; for (const { start: i, value: u } of At(e, n, Number.POSITIVE_INFINITY)) { const f = (i - r) * o + s; t.push({ height: u, index: i, offset: f }), r = i, s = f, o = u; } return [t, o, s, r]; } function ae(t, [e, n]) { if (n.length > 0 && a(t) && e.length === 2) { const r = e[0].size, o = e[1].size; return [ n.reduce((s, l) => T(T(s, l, r), l + 1, o), nt()), 0 ]; } return Wt(t, e); } const zt = Nt(), Ft = Nt(), lt = v([], () => { $t(lt, Ft, (t, e) => { const n = t.slice(); for (const [r, o] of Object.entries(e)) n[Number.parseInt(r, 10)] = o; for (let r = 0; r < n.length; r++) if (n[r] === void 0) return t; return n; }); }), k = v(0), pt = v(0), ot = v([], () => { vt( b( k, gt(lt), w(([t, e]) => { const n = Array.from({ length: t }, () => [nt(), 0]); if (e.length > 0) { const r = Array.from({ length: t }, () => 0), o = Array.from({ length: t }, () => 0); for (let s = 0; s < e.length; s++) { const l = e[s], i = r.indexOf(Math.min(...r)), u = o[i]; o[i]++, r[i] += l, n[i] = Wt(n[i][0], [ { endIndex: u, size: l, startIndex: u } ]); } } return n.map(([r]) => [r, 0]); }) ), ot ), $t(ot, zt, (t, e) => { const n = t.map(([o]) => o), r = []; for (let o = 0; o < e.length; o++) { const s = n[o] ?? nt(); if (e[o].length === 0) { r.push([s, 0]); continue; } r.push(ae(s, [e[o], []])); } return r; }); }), _t = R([], () => b( ot, w((t) => t.map(([e]) => e)) )), he = R([], (t) => t.pipe( ot, w((e) => e.map(([, n]) => n)) )), it = R([], () => b( _t, gt(he), Ct((t, [e, n]) => { const r = []; for (let o = 0; o < e.length; o++) { const s = t[o] ?? [[]]; r.push(fe(s[0], [e[o], n[o]])); } return r; }, []) )), de = R( [], () => b( it, w((t) => t.map(([e]) => e)) ) ), me = R( [], () => b( it, w((t) => t.map(([, e]) => e)) ) ), xe = R( [], () => b( it, w((t) => t.map(([, , e]) => e)) ) ), ve = R( [], (t) => t.pipe( it, w((e) => e.map(([, , , n]) => n)) ) ), Y = R([], () => b( k, gt(D, pt, lt), w(([t, e, n, r]) => { if (r.length > 0) { const l = Array.from({ length: t }, () => 0), i = Array.from({ length: t }, () => []); for (let u = 0; u < r.length; u++) { const f = r[u], m = l.indexOf(Math.min(...l)); i[m].push(u), l[m] += f; } return i; } const o = n === 0 ? Math.min(t, e) : n; return Array.from({ length: t }, (l, i) => { const u = []; for (let f = i; f < o; f += t) u.push(f); return u; }); }) )), Ie = R( [], () => b( It(k, D, ve, xe, me, Y), w(([t, e, n, r, o, s]) => { const l = s.reduce((f, m) => f + m.length, 0), i = Math.ceil((e - l) / t); return Array.from({ length: t }, (f, m) => { const y = s[m].length + i - n[m]; return r[m] + y * o[m]; }); }) ) ), st = v({ columns: [], data: [], indexesInColumns: [], totalCount: -1 }, (t) => { vt( b( It( k, X, ne, _t, de, D, Ie, tt, Y, pt, et, j ), Ct( (e, [ n, r, o, s, l, i, u, f, m, $, y, B ]) => { if (i === 0) return e; const bt = B ? y + r : 0, c = Math.min(Math.max(r - bt, 0), Math.max(...u) - o), d = c + o, S = $ === 0 ? Math.min(n, i) : $; return { columns: Array.from( { length: n }, (q, I) => { const g = e.columns[I], E = s[I] ?? nt(), N = l[I] ?? [], p = u[I] ?? 0, M = m[I] ?? []; if (a(E) || M.length === 0) { const H = []; let O = 0; for (let C = I; C < S; C += n) H.push({ columnIndex: I, data: f?.[C], height: 0, index: C, indexInColumn: O++, offset: 0 }); return { index: I, items: H, listBottom: 0, listTop: 0, offsetTree: N, totalHeight: 0, viewportBottom: d }; } if (g?.offsetTree === N && e.totalCount === i && e.data === f && e.indexesInColumns === m && c >= g.listTop && d <= g.listBottom) return g; const K = [], P = M.length - 1, z = 0, ct = ue(N, c, d, z); let x = 0, L = 0, V = !1; for (const H of ct) { const { value: { height: O, offset: C } } = H; let F = H.start; x = C, C < c && (F += Math.floor((c - C) / O), x += (F - H.start) * O), F < z && (x += (z - F) * O, F = z); const Bt = Math.min(H.end, P); for (let J = F; J <= Bt && !(x >= d); J++) { const wt = M[J], Lt = { columnIndex: I, data: f?.[wt], height: O, index: wt, indexInColumn: J, offset: x }; V || (V = !0, L = x), K.push(Lt), x += O; } } return { index: I, items: K, listBottom: x, listTop: L, offsetTree: N, totalHeight: p, viewportBottom: d }; } ), data: f, indexesInColumns: m, totalCount: i }; }, { columns: [], data: [], indexesInColumns: [], totalCount: -1 } ) ), st ), qt( b( st, Rt((e) => e.columns.some((n) => n.listBottom < n.viewportBottom)) ), (e) => { const n = e.columns.slice().sort((l, i) => l.listBottom - i.listBottom)[0], r = t.getValue(Y), o = r.flat(), s = o.length ? Math.max(...o) + 1 : 0; s >= t.getValue(D) || Kt( Y, r.map((l, i) => i === n.index ? [...l, s] : l) ); } ); }), ge = Dt( ({ columnCount: t, context: e, data: n, initialItemCount: r = 0, ItemContent: o = Et, useWindowScroll: s = !1, ...l }, i) => { const u = jt(() => { const f = new Jt(); return f.register(Y), f.register(st), f.register(lt), f.pubIn({ [k]: t, [at]: e, [tt]: n.slice(), [pt]: r, [Mt]: o, [j]: s }), f; }, []); return Gt(i, () => ({}), []), St(() => { u.pubIn({ [k]: t, [at]: e, [tt]: n.slice() }); }), /* @__PURE__ */ A(Qt.Provider, { value: u, children: /* @__PURE__ */ A(be, { ...l }) }); } // oxlint-disable-next-line typescript-eslint(no-unsafe-type-assertion) -- generic forwardRef pattern requires cast ); ge.displayName = "VirtuosoMasonry"; const pe = { observe: () => { }, unobserve: () => { } }, be = ({ style: t, ...e }) => { const n = Xt(), [r] = Ut(() => { if (typeof ResizeObserver > "u") { if (typeof window < "u") throw new Error("ResizeObserver not found. Please ensure that you have a polyfill installed."); return pe; } return new ResizeObserver((c) => { const d = c.length, S = n.getValue(k), W = n.getValue(j), q = Array.from({ length: S }, () => []), I = {}; let g = {}; for (let E = 0; E < d; E++) { const N = c[E], p = N.target; if (p === l.current) { const V = p.ownerDocument.defaultView; g = { ...g, [Q]: W ? V.document.documentElement.scrollHeight : p.scrollHeight, [X]: W ? V.scrollY : p.scrollTop, [ht]: W ? V.innerHeight : N.contentRect.height, [ee]: W ? V.innerWidth : p.clientWidth, ...W ? {} : { [et]: N.contentRect.top, [Q]: p.scrollHeight } }; continue; } if (p.dataset.role === "column") { l.current && (g = { ...g, [Q]: l.current.scrollHeight }); continue; } if (p.dataset.index === void 0) continue; const M = Number.parseInt(p.dataset.index), K = Number.parseFloat(p.dataset.knownSize ?? ""), P = N.contentRect.height, z = Number.parseInt(p.dataset.columnIndex ?? "0"), ct = Number.parseInt(p.dataset.absoluteIndex ?? "0"); if (I[ct] = P, P === K) continue; const x = q[z], L = x[x.length - 1]; x.length === 0 || L.size !== P || L.endIndex !== M - 1 ? x.push({ endIndex: M, size: P, startIndex: M }) : x[x.length - 1].endIndex++; } q.some((E) => E.length > 0) && (g = { ...g, [zt]: q }), Object.keys(I).length > 0 && (g = { ...g, [Ft]: I }), n.pubIn(g); }); }), o = _( (c) => { r.observe(c); }, [r] ), s = _( (c) => { r.unobserve(c); }, [r] ), l = ft(null), i = ft([]), u = _( (c) => (d) => { if (d) i.current[c] = d, r.observe(d, { box: "border-box" }); else { const S = i.current[c]; S != null && (r.unobserve(S), i.current[c] = null); } }, [r] ), f = _(() => { const c = l.current; c !== null && n.pub(X, c.scrollTop); }, [n]), m = _( (c) => { c ? (l.current = c, c.addEventListener("scroll", f), r.observe(c, { box: "border-box" })) : l.current && (l.current.removeEventListener("scroll", f), r.unobserve(l.current), l.current = null); }, [r, f] ), [$, y, B] = Zt(st, Mt, j); return St(() => { if (B) { const c = l.current?.ownerDocument.defaultView, d = () => { n.pubIn({ [et]: l.current?.getBoundingClientRect().top ?? 0, [Q]: c.document.documentElement.scrollHeight, [X]: c.scrollY, [ht]: c.innerHeight }); }; return c.addEventListener("scroll", d), d(), () => { c.removeEventListener("scroll", d); }; } }, [B, n]), /* @__PURE__ */ A( "div", { ...e, "data-testid": "virtuoso-scroller", ref: m, style: { ...B ? { boxSizing: "border-box", display: "flex", flexDirection: "row" } : { boxSizing: "border-box", display: "flex", flexDirection: "row", overflowY: "scroll" }, ...t }, children: $.columns.map((c, d) => /* @__PURE__ */ A( "div", { "data-testid": "virtuoso-list", ref: u(d), style: { boxSizing: "content-box", flexGrow: 1, height: c.totalHeight, overflowAnchor: "none", position: "relative" }, children: c.items.map((S) => /* @__PURE__ */ A(we, { item: S, ItemContent: y, mount: o, unmount: s }, S.index)) }, `column-${d}` )) } ); }, we = ({ item: t, ItemContent: e, mount: n, unmount: r }) => { const o = te(at), s = ft(null), l = _( (i) => { i ? (s.current = i, n(i)) : s.current && (r(s.current), s.current = null); }, [n, r] ); return /* @__PURE__ */ A( "div", { "data-absolute-index": t.index, "data-column-index": t.columnIndex, "data-index": t.indexInColumn, "data-known-size": t.height, ref: l, style: { boxSizing: "border-box", overflowAnchor: "none", position: "absolute", top: t.offset, width: "100%" }, children: /* @__PURE__ */ A(e, { context: o, data: t.data, index: t.index }) } ); }; export { ge as VirtuosoMasonry };