<p class="tw-margin-be-s tw-font-weight-medium">🙊 Sort the animals</p>
<tw-sortable id="tree" nested max-depth="4"></tw-sortable>
<div id="result" class="log"></div>
<script type="module">
// data
const animals = [
{ id: 1, name: "Dog", members: [] },
{
id: 2, name: "Cat", members: [
{ id: 10, name: "Lion", members: [
{ id: 20, name: "Tiger", members: [] },
{ id: 21, name: "Leopard", members: [] }
]}
]
},
{ id: 3, name: "Elephant", members: [] },
{ id: 4, name: "Rabbit", members: [] },
{ id: 5, name: "Fox", members: [] }
];
const tree = document.getElementById("tree");
const result = document.getElementById("result");
function makeItem(node) {
const el = document.createElement("tw-sortable-item");
const content = document.createElement("div");
content.slot = "content";
content.textContent = node.name;
el.appendChild(content);
(node.members || []).forEach(child => el.appendChild(makeItem(child)));
return el;
}
function render(container, nodes) {
container.innerHTML = "";
nodes.forEach(n => container.appendChild(makeItem(n)));
}
render(tree, animals);
function getChildrenAtPath(root, path, key) {
if (path.length === 0) return root;
let arr = root;
for (const i of path) {
const node = arr[i];
if (!node) throw new Error("Invalid path");
arr = node[key] || [];
}
return arr;
}
function removeAtPath(root, path, key) {
const parentPath = path.slice(0, -1);
const idx = path[path.length - 1];
const parentArr = getChildrenAtPath(root, parentPath, key);
const [node] = parentArr.splice(idx, 1);
return { node, parentArr, index: idx };
}
function insertAtPath(root, parentPath, index, node, key) {
const parentArr = getChildrenAtPath(root, parentPath, key);
if (parentPath.length > 0) {
const gpArr = getChildrenAtPath(root, parentPath.slice(0, -1), key);
const parentNode = gpArr[parentPath[parentPath.length - 1]];
if (!Array.isArray(parentNode[key])) parentNode[key] = [];
}
parentArr.splice(index, 0, node);
}
function moveTreeByDetail(root, detail, key = "members") {
const { node } = removeAtPath(root, detail.from, key);
insertAtPath(root, detail.dest.parentPath, detail.dest.index, node, key);
}
tree.addEventListener("restructured", (e) => {
/** @type {{from:number[], to:number[], drop:{type:string,targetPath:number[]|null}, dest:{parentPath:number[], index:number}, parents:{fromIsRoot:boolean,toIsRoot:boolean}}} */
const detail = e.detail;
console.log(e.detail)
moveTreeByDetail(animals, detail, "members");
result.innerText =
`🙈 Moved from ${detail.from.join(".")} to ${detail.to.join(".")} ` +
`(drop: ${detail.drop.type}${detail.drop.targetPath ? " @ " + detail.drop.targetPath.join(".") : " @ root"})`;
result.classList.remove("error");
result.classList.add("ok");
console.log("[restructured]", detail, animals);
});
tree.addEventListener("nestable-error", (e) => {
const { code, message, maxDepth } = e.detail;
result.innerText = ` ${code}: ${message}${typeof maxDepth==='number' ? ` (maxDepth=${maxDepth})` : ""}`;
result.classList.remove("ok");
result.classList.add("error");
console.warn("[nestable-error]", e.detail);
});
</script>