@virtuoso.dev/masonry
Version:
Virtualized React component for rendering masonry layouts
649 lines (648 loc) • 18.1 kB
JavaScript
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
};