UNPKG

@virtuoso.dev/masonry

Version:

Virtualized React component for rendering masonry layouts

654 lines (653 loc) 18.5 kB
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: [ "Item ", t ] }), Mt = g(Et), D = g(0), at = g(null), tt = g(null, () => { vt( w( tt, St((t) => t !== null), y((t) => t.length) ), D ); }), X = g(0), ht = g(0), ne = g(0), Q = g(0), re = X, et = g(0), j = g(!1), oe = $(0, () => w( gt(ht, j, et), y(([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 mt(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(m(t, { k: s, l: Ot(r), v: l })); } return e < n ? Z(m(t, { l: mt(r, e) })) : Z(m(t, { r: mt(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 k(t, e, n) { return a(t) ? Vt(e, n, 1) : e === t.k ? m(t, { k: e, v: n }) : e < t.k ? yt(m(t, { l: k(t.l, e, n) })) : yt(m(t, { r: k(t.r, e, n) })); } function dt(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(dt(o, e, n))), r >= e && r <= n && i.push({ k: r, v: l }), r <= n && (i = i.concat(dt(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(m(t, { r: Ot(t.r) })); } function m(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(m(t, { lvl: n - 1 })); if (!a(e) && !a(e.r)) return m(e.r, { l: m(e, { r: e.r.l }), lvl: n, r: m(t, { l: e.r.r, lvl: n - 1 }) }); throw new Error("Unexpected empty nodes"); } if (ut(t)) return xt(m(t, { lvl: n - 1 })); if (!a(r) && !a(r.l)) { const o = r.l, s = ut(o) ? r.lvl - 1 : r.lvl; return m(o, { l: m(t, { lvl: n - 1, r: o.l }), lvl: o.lvl + 1, r: xt(m(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 se(dt(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 se(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 ? m(n, { l: m(t, { r: n.l }), lvl: e + 1 }) : t; } function kt(t) { const { l: e } = t; return !a(e) && e.lvl === t.lvl ? m(e, { r: m(t, { l: e.r }) }) : t; } function le(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 Pt(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 = k(t, 0, s); continue; } const i = At(t, l - 1, o + 1); if (i.some(le(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)); c && (t = k(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 ie(t, e, n) { return t[rt(t, e, n)]; } function ce(t, e, n, r) { const o = rt(t, e, r), s = rt(t, n, r, o); return t.slice(o, s + 1); } function Wt({ index: t }, e) { return e === t ? 0 : e < t ? -1 : 1; } function ue({ offset: t }, e) { return e === t ? 0 : e < t ? -1 : 1; } function fe(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 he(t, [e, n]) { let r = 0, o = 0, s = 0, l = 0; if (n !== 0) { l = rt(t, n - 1, Wt), 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 c = (i - r) * o + s; t.push({ height: u, index: i, offset: c }), r = i, s = c, o = u; } return [t, o, s, r]; } function me(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) => k(k(s, l, r), l + 1, o), nt()), 0 ]; } return Pt(t, e); } const zt = Rt(), Ft = Rt(), lt = g([], () => { $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; }); }), P = g(0), pt = g(0), ot = g([], () => { vt( w( P, It(lt), y(([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] = Pt(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(me(s, [e[o], []])); } return r; }); }), _t = $([], () => w( ot, y((t) => t.map(([e]) => e)) )), de = $([], (t) => t.pipe( ot, y((e) => e.map(([, n]) => n)) )), it = $([], () => w( _t, It(de), Nt((t, [e, n]) => { const r = []; for (let o = 0; o < e.length; o++) { const s = t[o] ?? [[]]; r.push(he(s[0], [e[o], n[o]])); } return r; }, []) )), xe = $( [], () => w( it, y((t) => t.map(([e]) => e)) ) ), ve = $( [], () => w( it, y((t) => t.map(([, e]) => e)) ) ), ge = $( [], () => w( it, y((t) => t.map(([, , e]) => e)) ) ), Ie = $( [], (t) => t.pipe( it, y((e) => e.map(([, , , n]) => n)) ) ), Y = $([], () => w( P, It(D, pt, lt), y(([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 c = r[u], d = l.indexOf(Math.min(...l)); i[d].push(u), l[d] += c; } return i; } const o = n === 0 ? Math.min(t, e) : n; return Array.from({ length: t }, (l, i) => { const u = []; for (let c = i; c < o; c += t) u.push(c); return u; }); }) )), pe = $( [], () => w( gt(P, D, Ie, ge, ve, 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]; }); }) ) ), st = g({ columns: [], data: [], indexesInColumns: [], totalCount: -1 }, (t) => { vt( w( gt( P, re, oe, _t, xe, D, pe, tt, Y, pt, et, j ), Nt( (e, [ n, r, o, s, l, i, u, c, d, R, S, B ]) => { if (i === 0) 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; return { columns: Array.from( { length: n }, (q, I) => { const x = e.columns[I], V = s[I] ?? nt(), C = 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) A.push({ columnIndex: I, data: c == null ? void 0 : c[E], height: 0, index: E, indexInColumn: T++, offset: 0 }); return { index: I, items: A, listBottom: 0, listTop: 0, offsetTree: C, totalHeight: 0, viewportBottom: h }; } if ( // 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 ) return x; const K = [], W = H.length - 1, z = 0, ct = ae(C, f, h, z); let v = 0, L = 0, O = !1; for (const A of ct) { const { value: { height: T, offset: E } } = 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); const Bt = Math.min(A.end, W); for (let J = F; J <= Bt && !(v >= h); J++) { const wt = H[J], Lt = { columnIndex: I, data: c == null ? void 0 : c[wt], height: T, index: wt, indexInColumn: J, offset: v }; O || (O = !0, L = v), K.push(Lt), v += T; } } return { index: I, items: K, listBottom: v, listTop: L, offsetTree: C, totalHeight: p, viewportBottom: h }; } ), data: c, indexesInColumns: d, totalCount: i }; }, { columns: [], data: [], indexesInColumns: [], totalCount: -1 } ) ), st ), jt( w( st, St((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) || Gt( Y, r.map((l, i) => i === n.index ? [...l, s] : l) ); } ); }), be = Xt( ({ columnCount: t, context: e, data: n, initialItemCount: r = 0, ItemContent: o = Et, useWindowScroll: s = !1, ...l }, i) => { const u = Zt(() => { const c = new Ut(); return c.register(Y), c.register(st), c.register(lt), c.pubIn({ [P]: t, [at]: e, [tt]: n.slice(), [pt]: r, [Mt]: o, [j]: s }), c; }, []); return te(i, () => ({}), []), Ct(() => { u.pubIn({ [P]: t, [at]: e, [tt]: n.slice() }); }), /* @__PURE__ */ M(qt.Provider, { value: u, children: /* @__PURE__ */ M(ye, { ...l }) }); } ); be.displayName = "VirtuosoMasonry"; const we = { // eslint-disable-next-line @typescript-eslint/no-empty-function observe: () => { }, // eslint-disable-next-line @typescript-eslint/no-empty-function unobserve: () => { } }, ye = ({ style: t, ...e }) => { const n = Kt(), [r] = ee(() => { if (typeof ResizeObserver > "u") { if (typeof window < "u") throw new Error("ResizeObserver not found. Please ensure that you have a polyfill installed."); return we; } return new ResizeObserver((f) => { const h = f.length, b = n.getValue(P), N = n.getValue(j), q = Array.from({ length: b }, () => []), I = {}; let x = {}; for (let V = 0; V < h; V++) { const C = f[V], p = C.target; if (p === l.current) { const O = p.ownerDocument.defaultView; x = { ...x, [Q]: N ? O.document.documentElement.scrollHeight : p.scrollHeight, [X]: N ? O.scrollY : p.scrollTop, [ht]: N ? O.innerHeight : C.contentRect.height, [ne]: N ? O.innerWidth : p.clientWidth, ...N ? {} : { [et]: C.contentRect.top, [Q]: p.scrollHeight } }; continue; } if (p.dataset.role === "column") { l.current && (x = { ...x, [Q]: l.current.scrollHeight }); continue; } if (p.dataset.index === void 0) 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"); if (I[ct] = W, W === K) continue; const v = q[z], L = v[v.length - 1]; v.length === 0 || L.size !== W || L.endIndex !== H - 1 ? v.push({ endIndex: H, size: W, startIndex: H }) : v[v.length - 1].endIndex++; } q.some((V) => V.length > 0) && (x = { ...x, [zt]: q }), Object.keys(I).length > 0 && (x = { ...x, [Ft]: I }), n.pubIn(x); }); }), o = _( (f) => { r.observe(f); }, [r] ), s = _( (f) => { r.unobserve(f); }, [r] ), l = ft(null), i = ft([]), u = _( (f) => (h) => { if (h) i.current[f] = h, r.observe(h, { box: "border-box" }); else { const b = i.current[f]; b != null && (r.unobserve(b), i.current[f] = null); } }, [r] ), c = _(() => { const f = l.current; f !== null && n.pub(X, f.scrollTop); }, [n]), d = _( (f) => { f ? (l.current = f, f.addEventListener("scroll", c), r.observe(f, { box: "border-box" })) : l.current && (l.current.removeEventListener("scroll", c), r.unobserve(l.current), l.current = null); }, [r, c] ), [R, S, B] = Jt(st, Mt, j); return Ct(() => { var f; if (B) { const h = (f = l.current) == null ? void 0 : f.ownerDocument.defaultView, b = () => { var N; n.pubIn({ [et]: ((N = l.current) == null ? void 0 : N.getBoundingClientRect().top) ?? 0, [Q]: h.document.documentElement.scrollHeight, [X]: h.scrollY, [ht]: h.innerHeight }); }; return h.addEventListener("scroll", b), b(), () => { h.removeEventListener("scroll", b); }; } }, [B, n]), /* @__PURE__ */ M(Dt, { children: /* @__PURE__ */ M( "div", { ...e, "data-testid": "virtuoso-scroller", ref: d, style: { ...B ? { boxSizing: "border-box", display: "flex", flexDirection: "row" } : { boxSizing: "border-box", display: "flex", flexDirection: "row", overflowY: "scroll" }, ...t }, children: R.columns.map((f, h) => /* @__PURE__ */ M( "div", { "data-testid": "virtuoso-list", ref: u(h), style: { boxSizing: "content-box", flexGrow: 1, height: f.totalHeight, overflowAnchor: "none", position: "relative" }, children: f.items.map((b) => /* @__PURE__ */ M(Se, { item: b, ItemContent: S, mount: o, unmount: s }, b.index)) }, `column-${h}` )) } ) }); }, Se = ({ item: t, ItemContent: e, mount: n, unmount: r }) => { const o = Qt(at), s = ft(null), l = _( (i) => { i ? (s.current = i, n(i)) : s.current && (r(s.current), s.current = null); }, [n, r] ); return /* @__PURE__ */ M( "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__ */ M(e, { context: o, data: t.data, index: t.index }) } ); }; export { be as VirtuosoMasonry };