Unify toolbox drag and drop reorder behavior
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s

This commit is contained in:
Shinuwa 2026-08-01 20:00:47 +02:00
parent a6d35c6e6b
commit 1879b245fb
17 changed files with 1258 additions and 929 deletions

View file

@ -1,7 +1,7 @@
// Rôle : fournit l'outil images avec import, collage, libellés et annotation rapide.
import { useState } from "react";
import { Icon } from "../../../components/Icon.jsx";
import { usePointerReorder } from "../../../hooks/usePointerReorder.js";
import { moveItem, useGroupedReorder } from "../../../hooks/useGroupedReorder.js";
export function ImagesModule({ toolboxId, moduleId, context, editing }) {
const data = context.getModuleData(toolboxId, moduleId, { images: [] });
@ -9,24 +9,23 @@ export function ImagesModule({ toolboxId, moduleId, context, editing }) {
const [dragOver, setDragOver] = useState(false);
const pastePlaceholder = textContent.pastePlaceholder || "Coller une image ici";
const {
draggingId: draggingImageId,
dropTarget,
startDrag
} = usePointerReorder({
targetSelector: "[data-image-id]",
getTargetId: (target) => target.dataset.imageId,
canDropOn: (target) => target.dataset.imageModuleId === moduleId,
getPlacement: (event, target) => {
const rect = target.getBoundingClientRect();
return event.clientX > rect.left + rect.width / 2 ? "after" : "before";
itemReorder,
getItemProps,
isItemDragging,
isItemDropTarget,
getDropPlacement
} = useGroupedReorder({
namespace: "images",
items: data.images,
getItemId: (image) => image.id,
getParentId: () => moduleId,
orientation: "horizontal",
onItemMove: (operation) => {
context.setModuleData(toolboxId, moduleId, {
images: moveItem(data.images, operation.sourceId, operation.targetId, operation.placement)
});
},
onMove: (draggingId, targetId, placement) => {
const nextImages = data.images.filter((image) => image.id !== draggingId);
const targetIndex = nextImages.findIndex((image) => image.id === targetId);
if (targetIndex === -1) return;
nextImages.splice(placement === "after" ? targetIndex + 1 : targetIndex, 0, data.images.find((image) => image.id === draggingId));
context.setModuleData(toolboxId, moduleId, { images: nextImages.filter(Boolean) });
}
hierarchy: { enabled: true, stickyParents: true }
});
async function addFiles(files) {
@ -90,20 +89,18 @@ export function ImagesModule({ toolboxId, moduleId, context, editing }) {
)}
<div className="images">
{data.images.map((image) => {
const isDragging = draggingImageId === image.id;
const isDropTarget = dropTarget.id === image.id;
const className = [
isDragging ? "is-dragging" : "",
isDropTarget ? "is-drop-target" : "",
isDropTarget && dropTarget.placement === "after" ? "drop-after" : ""
isItemDragging(image.id) ? "is-dragging" : "",
isItemDropTarget(image.id) ? "is-drop-target" : "",
getDropPlacement("item", image.id) === "after" ? "drop-after" : ""
].filter(Boolean).join(" ");
return (
<figure key={image.id} className={className} data-image-id={image.id} data-image-module-id={moduleId}>
<figure key={image.id} className={className} {...getItemProps({ itemId: image.id, parentId: moduleId })}>
<button
className="image-drag-handle"
type="button"
onPointerDown={(event) => startDrag(event, image.id)}
onPointerDown={(event) => itemReorder.startDrag(event, image.id)}
aria-label={textContent.reorderAriaLabel || "Déplacer l'image"}
title={textContent.reorderTitle || "Déplacer"}
>