);
}
// Errors.
const { errors } = usePage().props;
const [localErrors, setLocalErrors] = useState>(errors)
// Drag & drop module for user interactions (mouse, touch, keyboard or other).
const sensors = useSensors(useSensor(PointerSensor));
// Define a local identifier for each element to enable unique identification during drag and drop.
const localValue = value.map((elmt, index) => {
return { ...elmt, id: elmt.id ?? index + 1 };
});
// Order all elements depending on order values.
const sortedListItems = [...localValue].sort((a, b) => a.order - b.order);
// Extract some values from laravel rules validation.
const isRequired = extractRequiredValidation(field.validation);
const configLimitMax = extractMinMaxFromValidation(field.validation).max ?? 1;
// Accordion open index.
const [openIndex, setOpenIndex] = useState("");
/**
* Get all elements without each unique identification values.
* @param fields DynamicInternalField[]
* @returns Omit[]
*/
const removeIds = (fields: DynamicInternalField[]): Omit[] => {
return fields.map(({ id, ...rest }) => {
const newObj: any = {};
for (const key in rest) {
const value = rest[key as keyof typeof rest];
// If it is an array, we treat it recursively.
if (Array.isArray(value)) {
newObj[key] = removeIds(value);
}
// Otherwise, we keep the value as it is.
else {
newObj[key] = value;
}
}
return newObj;
});
};
/**
* Remove a specific element from the list.
* @param parentName string
* @param fieldKey string
* @param index number
* @return void
*/
const handleRemoveField = (parentName: string, fieldKey: string, index: number) => {
// Get the current list of elements.
const list = Array.isArray(content?.[fieldKey as keyof typeof content])
// @ts-ignore
? [...content[fieldKey as keyof typeof content]]
: [];
// Remove the targeted element.
list.splice(index, 1);
// Save the new list updated.
setData(`${parentName}.${fieldKey}`, list);
// Reset all errors because of alter elements list.
setLocalErrors({});
}
/**
* Reorder the elements list.
* @param event DragEndEvent
* @return void
*/
const handleOnDragEnd = (event: DragEndEvent) => {
const { active, over } = event;
// Check the unique identification value.
if (!over || active.id === over.id) return;
// Get the two elements that are switchable by drag and drop.
const oldIndex = sortedListItems.findIndex((i) => i.id === active.id);
const newIndex = sortedListItems.findIndex((i) => i.id === over.id);
// Reorder all elements list to update visually all elements.
const reorderedList = arrayMove(sortedListItems, oldIndex, newIndex)
.map((item, index) => ({
...item,
order: index + 1,
}));
// Save the new ordered list elements.
setData(`${parentName}.${field.key}`, reorderedList);
}
/**
* Add a new field to the list.
* @return void
*/
const handleAddNewField = () => {
// Set new element with unique identification and order.
const newItem = Object.fromEntries(
(field.children ?? []).map(child => [child.key, ""])
);
const maxOrder = Math.max(0, ...sortedListItems.map((field) => field.order ?? 0)) + 1;
const maxIndex = Math.max(0, ...sortedListItems.map((field) => field.id ?? 0)) + 1;
const newIndex = value.length;
const newItemWithOrder = { ...newItem, id: maxIndex, order: maxOrder };
// Save the list with the new element.
setData(`${parentName}.${field.key}.${newIndex}`, newItemWithOrder);
}
return (
{value.length > 0 && (
{/*
The main provider for dnd-kit.
It manages the entire drag-and-drop system: sensors, movement, collision detection, and lifecycle callbacks (onDragStart, onDragEnd, etc.).
*/}
{/*
Defines a reorderable list area.
It registers sortable items and applies the chosen sorting strategy (vertical, horizontal, grid) to handle item repositioning.
*/}
item.id)}
strategy={verticalListSortingStrategy}
>
{/* Use the recursive component for each elements. */}
{sortedListItems.map((item, itemIndex) => (
))}
)}
)
}
type SortableItemProps = {
id: number;
item: any;
itemIndex: number;
name: string;
parentName: string;
field: any;
setData: (name: string, value: string | boolean | File | object | null) => void;
reset: (name?: string) => void;
removeField: (parentName: string, fieldKey: string, index: number) => void;
parentAccordionState: string;
setParentAccordionState: (key: string) => void;
localErrors: Record;
}
// Recursive component.
function SortableItem({
id,
item,
itemIndex,
name,
parentName,
field,
setData,
reset,
removeField,
parentAccordionState,
setParentAccordionState,
localErrors,
}: SortableItemProps) {
// Provides the necessary props and helpers to make this item draggable and reorderable.
// Includes event listeners, accessibility attributes, DOM ref, drag transform/transition values,
// and a flag indicating whether the item is dragging.
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id });
// Style applied to the sortable item:
// - handles drag movement (transform),
// - animations (transition),
// - and visual feedback (opacity, zIndex) while dragging.
const style = {
transform: CSS.Transform.toString(transform),
transition,
opacity: isDragging ? 0.5 : 1,
zIndex: isDragging ? 50 : 0,
};
// Translations.
const transDragAndDrop = useTrans("back.dnd_field");
const transDelete = useTrans("back.delete_field");
return (
key.startsWith(`${name}.${itemIndex}`)),
})}>
{/* Define a drag-and-drop button to swap the order between the current element and another one. */}