@1771technologies/lytenyte-pro
Version:
Blazingly fast headless React data grid with 100s of features.
117 lines (116 loc) • 5.43 kB
JavaScript
import { jsx as _jsx } from "react/jsx-runtime";
import { forwardRef, Fragment, memo, useLayoutEffect, useMemo, useRef, useState, } from "react";
import { useCombinedRefs } from "@1771technologies/lytenyte-core/internal";
import { PillRootProvider } from "./root.context.js";
import { PillRowDefault } from "./row-default.js";
function PillRootImpl({ children = PillRowDefault, rows, orientation, onPillRowChange, onPillItemActiveChange, onPillItemThrown, ...p }, ref) {
const [cloned, setCloned] = useState(null);
const [dragState, setDragState] = useState(null);
const prevSwapId = useRef(null);
const prevRowId = useRef(null);
const movedRef = useRef(null);
const rootElRef = useRef(null);
const snapshotRef = useRef(new Map());
const animationsRef = useRef(new Map());
const value = useMemo(() => {
return {
orientation: orientation ?? "horizontal",
cloned,
setCloned,
rows: rows,
dragState,
setDragState,
movedRef,
prevSwapId,
prevRowId,
onPillItemActiveChange: onPillItemActiveChange ?? (() => { }),
onPillRowChange: onPillRowChange ?? (() => { }),
onPillItemThrown: onPillItemThrown ?? (() => { }),
};
}, [cloned, dragState, onPillItemActiveChange, onPillItemThrown, onPillRowChange, orientation, rows]);
const rendered = useMemo(() => {
const r = cloned ?? rows;
return r.map((x, i) => _jsx(Fragment, { children: children(x, value) }, i));
}, [children, cloned, rows, value]);
// FLIP "First": capture pill positions before this render's DOM commit.
// useMemo runs during render, before React's mutation phase, so rootElRef.current
// still reflects the previous layout — exactly the "before" positions we need.
// Only snapshot when cloned is non-null (drag is active and a reorder just occurred).
useMemo(() => {
const el = rootElRef.current;
if (!el || cloned == null)
return;
const snap = new Map();
el.querySelectorAll("[data-ln-pill-id]").forEach((pillEl) => {
// Key by "rowId:pillId" — pill IDs are only unique within a row, not across rows.
const key = `${pillEl.getAttribute("data-ln-pill-row-id")}:${pillEl.getAttribute("data-ln-pill-id")}`;
snap.set(key, pillEl.getBoundingClientRect());
});
snapshotRef.current = snap;
}, [cloned]);
// FLIP "Last + Invert + Play": after React commits the new DOM order, measure new
// positions, compute the delta from the snapshot, and animate from delta → none.
useLayoutEffect(() => {
const el = rootElRef.current;
if (!el || cloned == null)
return;
el.querySelectorAll("[data-ln-pill-id]").forEach((pillEl) => {
const pillId = pillEl.getAttribute("data-ln-pill-id");
const rowId = pillEl.getAttribute("data-ln-pill-row-id");
const key = `${rowId}:${pillId}`;
const oldRect = snapshotRef.current.get(key);
if (!oldRect)
return;
const newRect = pillEl.getBoundingClientRect();
const existing = animationsRef.current.get(key);
let dx;
let dy;
if (existing) {
try {
existing.commitStyles();
const xm = /translateX\(([-\d.]+)px\)/.exec(pillEl.style.transform);
const ym = /translateY\(([-\d.]+)px\)/.exec(pillEl.style.transform);
dx = (xm ? parseFloat(xm[1]) : 0) + (oldRect.left - newRect.left);
dy = (ym ? parseFloat(ym[1]) : 0) + (oldRect.top - newRect.top);
}
catch {
dx = oldRect.left - newRect.left;
dy = oldRect.top - newRect.top;
}
}
else {
dx = oldRect.left - newRect.left;
dy = oldRect.top - newRect.top;
}
if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5)
return;
pillEl.style.transform = "none";
const from = `translateX(${dx}px) translateY(${dy}px)`;
if (existing) {
existing.effect?.setKeyframes([
{ transform: from },
{ transform: "none" },
]);
existing.currentTime = 0;
existing.play();
return;
}
const anim = pillEl.animate([{ transform: from }, { transform: "none" }], {
duration: 200,
easing: "ease-out",
});
animationsRef.current.set(key, anim);
anim.finished
.catch(() => { })
.finally(() => {
if (animationsRef.current.get(key) === anim) {
animationsRef.current.delete(key);
pillEl.style.transform = "";
}
});
});
}, [cloned]);
const combined = useCombinedRefs(ref, rootElRef);
return (_jsx(PillRootProvider, { value: value, children: _jsx("div", { ...p, ref: combined, "data-ln-pill-root": true, "data-ln-orientation": orientation ?? "horizontal", children: rendered }) }));
}
export const PillManager = memo(forwardRef(PillRootImpl));