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,6 +1,7 @@
// Rôle : fournit l'outil calculateur avec résultats enregistrés en arborescence.
import { useLayoutEffect, useMemo, useRef, useState } from "react";
import { Icon } from "../../../components/Icon.jsx";
import { moveItem, useGroupedReorder } from "../../../hooks/useGroupedReorder.js";
function calculateExpression(expression) {
const normalized = String(expression || "").replaceAll(",", ".").trim();
@ -43,6 +44,17 @@ export function CalculatorModule({ toolboxId, moduleId, context }) {
const calculatorCardRef = useRef(null);
const result = useMemo(() => calculateExpression(expression), [expression]);
const activeParent = data.entries.find((entry) => entry.id === activeParentId);
const scopeParentId = (parentId = "") => `${moduleId}:${parentId}`;
const reorder = useGroupedReorder({
namespace: "calculator",
items: data.entries,
getItemId: (entry) => entry.id,
getParentId: (entry) => scopeParentId(entry.parentId || ""),
onItemMove: (operation) => {
save(moveItem(data.entries, operation.sourceId, operation.targetId, operation.placement));
},
hierarchy: { enabled: true, stickyParents: true }
});
useLayoutEffect(() => {
const element = calculatorCardRef.current;
@ -165,7 +177,7 @@ export function CalculatorModule({ toolboxId, moduleId, context }) {
</div>
<div className={`tool-split-tree calculator-tree ${data.scrollResults ? "is-scrollable legacy-scrollbar" : ""}`}>
{data.entries.length ? (
<CalculatorEntries entries={data.entries} parentId="" activeParentId={activeParentId} textContent={textContent} onUse={useEntry} onRename={renameEntry} onDelete={deleteEntry} />
<CalculatorEntries entries={data.entries} parentId="" getScopedParentId={scopeParentId} activeParentId={activeParentId} textContent={textContent} reorder={reorder} onUse={useEntry} onRename={renameEntry} onDelete={deleteEntry} />
) : (
<p className="muted">{textContent.emptyResults || "Aucun résultat enregistré."}</p>
)}
@ -175,16 +187,34 @@ export function CalculatorModule({ toolboxId, moduleId, context }) {
);
}
function CalculatorEntries({ entries, parentId, activeParentId, textContent, onUse, onRename, onDelete }) {
function CalculatorEntries({ entries, parentId, getScopedParentId, activeParentId, textContent, reorder, onUse, onRename, onDelete }) {
const children = getChildren(entries, parentId);
const [editingId, setEditingId] = useState("");
if (!children.length) return null;
return (
<ul className="tool-split-entry-list calculator-entry-list">
{children.map((entry) => (
<li className={`${entry.id === activeParentId ? "active" : ""} ${entry.id === editingId ? "is-editing" : ""}`} key={entry.id}>
<div className="tool-split-entry calculator-entry">
{children.map((entry) => {
const className = [
entry.id === activeParentId ? "active" : "",
entry.id === editingId ? "is-editing" : "",
reorder.isItemDragging(entry.id) ? "is-dragging" : "",
reorder.isItemDropTarget(entry.id) ? "is-drop-target" : "",
reorder.getDropPlacement("item", entry.id, parentId) === "after" ? "drop-after" : ""
].filter(Boolean).join(" ");
return (
<li className={className} key={entry.id} {...reorder.getItemProps({ itemId: entry.id, parentId: getScopedParentId(parentId) })}>
<div className="tool-split-entry calculator-entry has-drag-handle">
<button
className="calculator-drag-handle"
type="button"
onPointerDown={(event) => reorder.itemReorder.startDrag(event, entry.id)}
aria-label={`${textContent.reorderTitle || "Déplacer"} ${entry.label || formatResult(entry.value)}`}
title={textContent.reorderTitle || "Déplacer"}
>
<Icon name="drag" />
</button>
{entry.id === editingId ? (
<>
<span className="tool-split-entry-value is-readonly" title={textContent.readonlyValueTitle || "Quantité non modifiable"}>
@ -204,9 +234,10 @@ function CalculatorEntries({ entries, parentId, activeParentId, textContent, onU
<Icon name="trash" />
</button>
</div>
<CalculatorEntries entries={entries} parentId={entry.id} activeParentId={activeParentId} textContent={textContent} onUse={onUse} onRename={onRename} onDelete={onDelete} />
<CalculatorEntries entries={entries} parentId={entry.id} getScopedParentId={getScopedParentId} activeParentId={activeParentId} textContent={textContent} reorder={reorder} onUse={onUse} onRename={onRename} onDelete={onDelete} />
</li>
))}
);
})}
</ul>
);
}