Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clone on drop / #106

Open
agotfredsen82 opened this issue Oct 10, 2024 · 3 comments
Open

Clone on drop / #106

agotfredsen82 opened this issue Oct 10, 2024 · 3 comments
Assignees

Comments

@agotfredsen82
Copy link

agotfredsen82 commented Oct 10, 2024

Hi We love the new 2.0.2 works perfect ..

we used to use a solution similar to this #8

to do clone / copy instead of transfer .. but it does not work anymore with the latest update . is there any build in flag / solution to clone on drop instead of transfer ? or any plugin you haven't released yet that fix this ?

@sashamilenkovic sashamilenkovic self-assigned this Oct 10, 2024
@sashamilenkovic
Copy link
Contributor

@agotfredsen82 You got it; I will look into updating that example.

@ZipBrandon
Copy link

Thanks from me, too! I'm looking to migrate from dndkit and I'll require cloning functionality.

@ZipBrandon
Copy link

ZipBrandon commented Oct 27, 2024

Just putting this rough hack of code from #8 that gets me some cloning. I'm sure I have adapted the code incorrectly but it's cloning.

The biggest issue that I can see is that for some reason when I drag from the done back to the todos then I lose the DnD. I get a warning about the number of elements doesn't match the number expected but I can't see what I'm doing wrong.

import {
	type BaseDragState,
	type DragState,
	type NodeRecord,
	type ParentRecord,
	type SynthDragState,
	dragValues,
	parentValues,
	parents,
	setParentValues,
} from "@formkit/drag-and-drop";
import { useDragAndDrop } from "@formkit/drag-and-drop/react";
import { useState } from "react";

// Functions and plugins
const sourceTransfer = <T,>({
	currentParent,
	targetParent,
	initialParent,
	draggedNodes,
	initialIndex,
	state,
	targetNode,
}: {
	currentParent: ParentRecord<T>;
	targetParent: ParentRecord<T>;
	initialParent: ParentRecord<T>;
	draggedNodes: Array<NodeRecord<T>>;
	initialIndex: number;
	state: BaseDragState<T> | DragState<T> | SynthDragState<T>;
	targetNode?: NodeRecord<T>;
}) => {
	const draggedValues = dragValues(state);

	const initialParentValues = parentValues(
		initialParent.el,
		initialParent.data,
	);

	const newInitialValues = initialParentValues.filter(
		(x) => !draggedValues.includes(x),
	);

	setParentValues(initialParent.el, initialParent.data, newInitialValues);
};

const findDuplicates = (values) => {
	const uniqueElements = new Set();
	const duplicates = [];

	for (const item of values) {
		if (uniqueElements.has(item)) {
			duplicates.push(item);
		} else {
			uniqueElements.add(item);
		}
	}

	return duplicates;
};

const targetTransfer = <T,>({
	currentParent,
	targetParent,
	initialParent,
	draggedNodes,
	initialIndex,
	state,
	targetNode,
}: {
	currentParent: ParentRecord<T>;
	targetParent: ParentRecord<T>;
	initialParent: ParentRecord<T>;
	draggedNodes: Array<NodeRecord<T>>;
	initialIndex: number;
	state: BaseDragState<T> | DragState<T> | SynthDragState<T>;
	targetNode?: NodeRecord<T>;
}) => {
	const draggedValues = dragValues(state);

	const targetData = targetParent.data;
	const targetParentValues = parentValues(targetParent.el, targetParent.data);
	const reset =
		currentParent.el === initialParent.el &&
		currentParent.data.config.sortable === false;

	let targetIndex;
	if ("node" in targetData) {
		if (reset) {
			targetIndex = initialIndex;
		} else if (targetParent.data.config.sortable === false) {
			targetIndex = targetParent.data.enabledNodes.length;
		} else {
			targetIndex = targetData.node.data.index;
		}
		targetParentValues.splice(targetIndex, 0, ...draggedValues);
	} else {
		targetIndex = reset ? initialIndex : targetParent.data.enabledNodes.length;

		targetParentValues.splice(targetIndex, 0, ...draggedValues);
	}

	const duplicates = findDuplicates(targetParentValues);

	for (const duplicate of duplicates) {
		if (!("key" in duplicate) || typeof duplicate !== "object") continue;
		const index = targetParentValues.indexOf(duplicate);
		const newKey = `${duplicate.key}-${Math.random()
			.toString(36)
			.substring(2, 15)}`;

		targetParentValues[index] = {
			...targetParentValues[index],
			key: newKey,
		};
	}

	setParentValues(targetParent.el, targetParent.data, targetParentValues);
};

export const targetClone = (parent: HTMLElement) => {
	const parentData = parents.get(parent);

	if (!parentData) return;

	return {
		setup() {
			parentData.config.performTransfer = targetTransfer;
		},
	};
};

export const sourceClone = (parent: HTMLElement) => {
	const parentData = parents.get(parent);

	if (!parentData) return;

	return {
		setup() {
			parentData.config.performTransfer = sourceTransfer;
		},
	};
};

export function FormkitDnd() {
	// Functions and plugins

	// Initial todos and done values
	const [initialTodos] = useState([
		{
			label: "Schedule perm",
			key: "schedule-perm",
		},
		{
			label: "Rewind VHS tapes",
			key: "rewind-vhs",
		},
		{
			label: "Make change for the arcade",
			key: "make-change",
		},
		{
			label: "Get disposable camera developed",
			key: "disposable-camera",
		},
		{
			label: "Learn C++",
			key: "learn-cpp",
		},
		{
			label: "Return Nintendo Power Glove",
			key: "return-power-glove",
		},
	]);

	const [todoList, todos] = useDragAndDrop(initialTodos, {
		group: "todoList",
		sortable: false,
		plugins: [sourceClone],
	});

	const [doneValues] = useState([
		{
			label: "Pickup new mix-tape from Beth",
			key: "mix-tape",
		},
	]);

	const [doneList, dones] = useDragAndDrop(doneValues, {
		group: "todoList",
		plugins: [targetClone],
	});

	return (
		<div>
			<h2>Cloning example</h2>
			<div className="group bg-slate-800">
				<div className="kanban-board p-px grid grid-cols-2 gap-px text-slate-400">
					<div className="kanban-column">
						<h2 className="kanban-title">ToDos</h2>

						<ul ref={todoList} className="kanban-list">
							{todos.map((todo) => (
								<li key={todo.key} className="kanban-item flex items-center">
									{todo.label}
								</li>
							))}
						</ul>
					</div>
					<div className="kanban-column ">
						<h2 className="kanban-title">Complete</h2>
						<ul ref={doneList} className="kanban-list border min-h-24">
							{dones.map((done) => (
								<li
									key={done.key}
									className="kanban-item kanban-complete flex items-center"
								>
									<span>{done.label}</span>
								</li>
							))}
						</ul>
					</div>
					<pre style={{ fontSize: "10px", color: "white" }}>
						{JSON.stringify(todos, null, 2)}
					</pre>
					<pre style={{ fontSize: "10px", color: "white" }}>
						{JSON.stringify(dones, null, 2)}
					</pre>
				</div>
			</div>
		</div>
	);
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants