@1771technologies/lytenyte-pro
Version:
Blazingly fast headless React data grid with 100s of features.
179 lines (178 loc) • 8.03 kB
JavaScript
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));