UNPKG

@1771technologies/lytenyte-pro

Version:

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

179 lines (178 loc) 8.03 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { forwardRef, memo, useRef, useState } from "react"; import { useSlot } from "../../hooks/use-slot/index.js"; import { usePillRow } from "./pill-row.context.js"; import { getDragData, getDragDirection, useDraggable, useEvent, } from "@1771technologies/lytenyte-core/internal"; import { DragDots } from "./icons.js"; import { usePillRoot } from "./root.context.js"; import { equal, moveRelative } from "@1771technologies/lytenyte-shared"; function PillItemBase({ item, elementEnd, ...props }, ref) { const { expandToggle, expanded, row } = usePillRow(); const { prevRowId, prevSwapId, movedRef, setDragState, setCloned, cloned, rows, orientation, onPillItemThrown, onPillItemActiveChange, onPillRowChange, } = usePillRoot(); const clonedRef = useRef(cloned); clonedRef.current = cloned; const { isDragActive, placeholder, props: dragProps, } = useDraggable({ data: { pill: { data: { item, id: row.id }, kind: "site" } }, onUnhandledDrop: () => { onPillItemThrown?.({ index: rows.findIndex((x) => x.id === row.id), item, row: rows.find((x) => x.id === row.id), }); }, onDragStart: () => { setDragState({ activeId: item.id, activeRow: row.id, activeType: row.type ?? "" }); setCloned(structuredClone(rows)); }, onDragEnd: () => { prevRowId.current = null; prevSwapId.current = null; const full = clonedRef.current; if (movedRef.current) { const move = movedRef.current; const movedPill = full .find((x) => x.id === move.id) ?.pills.find((x) => x.id === move.pillId); if (movedPill) movedPill.active = false; } const changed = clonedRef.current.filter((x, i) => { return !equal(x, rows[i]); }); if (changed.length) { onPillRowChange({ changed, full: clonedRef.current }); } setDragState(null); setCloned(null); }, }); const [_, force] = useState({}); const handleDragEnter = useEvent((ev) => { ev.preventDefault(); ev.stopPropagation(); const ds = getDragData(); if (!ds?.pill?.data) return; const { item: dragged, id: dragRowId } = ds.pill.data; const accepts = new Set([...(row.accepts ?? []), row.id]); if (!accepts.has(dragRowId) && !item.tags?.some((x) => accepts.has(x))) return; const hasPill = row.pills.find((x) => x.id === dragged.id); if (dragged.id === item.id) { if (dragRowId !== row.id) hasPill.active = dragged.active; force({}); return; } if (hasPill) { const draggedIndex = row.pills.findIndex((x) => x.id === dragged.id); const overIndex = row.pills.findIndex((x) => x.id === item.id); const newPills = moveRelative(row.pills, draggedIndex, overIndex); if (dragRowId !== row.id) hasPill.active = dragged.active; const [horizontal, vertical] = getDragDirection(); const currentSwapId = row.pills[overIndex].id; const currentRowId = row.id; if (orientation === "horizontal") { if (currentSwapId === prevSwapId.current && currentRowId === prevRowId.current) { if (horizontal === "end" && draggedIndex >= overIndex) return; if (horizontal === "start" && draggedIndex <= overIndex) return; } } else { if (currentSwapId === prevSwapId.current && currentRowId === prevRowId.current) { if (vertical === "bottom" && draggedIndex >= overIndex) return; if (vertical === "top" && draggedIndex <= overIndex) return; } } // We are gonna swap these two prevSwapId.current = currentSwapId; prevRowId.current = currentRowId; setCloned((prev) => { if (!prev) throw new Error("Can't call drag function without cloning nodes."); const thisRow = prev.findIndex((x) => x.id === row.id); const newPill = { ...prev[thisRow] }; newPill.pills = newPills; const newDef = [...prev]; newDef[thisRow] = newPill; return newDef; }); return; } // If here we need to check if this row accepts items the dragged. if (!row.accepts || !dragged.tags || dragged.tags.every((x) => !row.accepts?.includes(x))) return null; const thisRow = rows.findIndex((x) => x.id === row.id); const originalRow = rows[thisRow]; // The original row has this id so we can't dragged a new item to it. if (originalRow.pills.find((x) => x.id === dragged.id)) return; const overIndex = row.pills.findIndex((x) => x.id === item.id); const draggedIndex = row.pills.findIndex((x) => x.id === dragged.id); let newPills; if (draggedIndex !== -1) { const [horizontal] = getDragDirection(); if (horizontal === "end" && draggedIndex >= overIndex) return; if (horizontal === "start" && draggedIndex <= overIndex) return; newPills = moveRelative(row.pills, draggedIndex, overIndex); } else { newPills = [...originalRow.pills]; newPills.splice(overIndex, 0, dragged); } setCloned((prev) => { if (!prev) throw new Error("Can't call drag function without cloning nodes."); const newPill = { ...prev[thisRow] }; newPill.pills = newPills; const newDef = [...prev]; newDef[thisRow] = newPill; return newDef; }); }); const onItemClick = () => { const next = !item.active; const nextPill = { ...item, active: next }; const nextPills = [...row.pills]; nextPills.splice(nextPills.indexOf(item), 1, nextPill); const nextRow = { ...row, pills: nextPills }; const index = rows.indexOf(row); onPillItemActiveChange({ index, item: nextPill, row: nextRow }); }; const s = (_jsxs("div", { tabIndex: -1, onClick: onItemClick, onKeyDown: (ev) => { if (ev.key === " " && document.activeElement === ev.currentTarget) { onItemClick?.(); ev.preventDefault(); } }, children: [item.movable && (_jsx("div", { style: { width: 20, height: 20, display: "flex", alignItems: "center", justifyContent: "center", cursor: "grab", }, children: _jsx(DragDots, {}) })), _jsx("div", { children: item.name ?? item.id }), elementEnd] })); const slot = useSlot({ props: [item.movable ? dragProps : {}, props, { "data-ln-pill-item": true }], ref, slot: s, state: { item, expanded, expandToggle, row, }, }); return (_jsxs("div", { "data-ln-pill-type": row.type, "data-ln-pill-item-container": true, "data-ln-pill-active": item.active, "data-ln-draggable": item.movable, "data-ln-drag-active": isDragActive, onDragLeave: (e) => { e.stopPropagation(); e.preventDefault(); }, onDragEnter: handleDragEnter, children: [slot, placeholder] })); } export const PillItem = memo(forwardRef(PillItemBase));