UNPKG

@1771technologies/lytenyte-pro

Version:

Blazingly fast headless React data grid with 100s of features.

117 lines (116 loc) 5.43 kB
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));