Ir al contenido

Drag & Drop

trud-calendar usa la API de Pointer Events para todas las interacciones de arrastre — funciona con mouse, pantallas tactiles y stylus sin dependencias extra.

Establece enableDnD={true} y proporciona handlers:

<Calendar
events={events}
enableDnD
onEventDrop={(event, newStart, newEnd) => {
await api.updateEvent(event.id, { start: newStart, end: newEnd });
}}
onEventResize={(event, newStart, newEnd) => {
await api.updateEvent(event.id, { start: newStart, end: newEnd });
}}
onSlotSelect={(start, end) => {
// El usuario arrastro sobre slots vacios para seleccionar un rango
openCreateModal({ start, end });
}}
/>
  • Vista de Mes: Arrastra una pill de evento de una celda a otra. La fecha cambia pero la hora original se preserva.
  • Vista de Semana/Dia: Arrastra un evento de una franja horaria a otra. Tanto la fecha como la hora cambian, ajustandose al incremento configurado.
  • La duracion del evento siempre se mantiene.
  • En las vistas de Semana/Dia, arrastra el borde superior de un evento para cambiar su hora de inicio, o el borde inferior para cambiar su hora de fin.
  • Se ajusta al incremento configurado (por defecto 15 minutos).
  • Dispara onEventResize con los tiempos de inicio/fin actualizados.
  • Arrastra sobre slots de tiempo vacios en la vista de Semana/Dia para seleccionar un rango de tiempo.
  • Dispara onSlotSelect(start, end) con el rango seleccionado.
  • Ideal para flujos de “crear evento” — el usuario arrastra para seleccionar cuando, luego llena los detalles.

Al arrastrar, redimensionar o seleccionar cerca del borde superior o inferior de la grilla de tiempo, el contenedor hace scroll automaticamente para revelar mas contenido. No requiere configuracion — se activa durante cualquier interaccion.

Todas las interacciones funcionan en dispositivos tactiles sin configuracion adicional:

  • Toca y mantiene un evento, luego arrastra para mover
  • Toca el handle de redimensionar en la parte inferior de un evento y arrastra para cambiar duracion
  • Toca y arrastra sobre slots vacios para seleccionar un rango de tiempo

El calendario usa touch-action: none en elementos interactivos para evitar que el navegador haga scroll durante las operaciones de arrastre. Un umbral de movimiento de 5px distingue toques de arrastres.

En dispositivos tactiles, puedes requerir una presion larga antes de que el arrastre comience, para evitar interferir con el scroll:

<Calendar
events={events}
enableDnD
longPressDelay={300} // 300ms de mantenimiento antes de activar el arrastre en touch
onEventDrop={handleDrop}
/>

Esto solo afecta interacciones tactiles — el arrastre con mouse y stylus permanece inmediato.

onEventDrop?: (
event: CalendarEvent, // El objeto evento original
newStart: DateTimeString, // "2026-03-15T10:00:00"
newEnd: DateTimeString, // "2026-03-15T11:00:00"
) => void;
onEventResize?: (
event: CalendarEvent, // El objeto evento original
newStart: DateTimeString, // "2026-03-15T10:00:00"
newEnd: DateTimeString, // "2026-03-15T11:30:00" (nueva duracion)
) => void;
onSlotSelect?: (
start: DateTimeString, // "2026-03-15T10:00:00"
end: DateTimeString, // "2026-03-15T11:00:00"
) => void;

Por defecto, todas las interacciones se ajustan a incrementos de 15 minutos. Cambialo con snapDuration:

<Calendar
events={events}
enableDnD
snapDuration={30} // Ajustar a incrementos de 30 minutos
onEventDrop={handleDrop}
onEventResize={handleResize}
/>

Valores comunes: 5, 10, 15, 30, 60.

Controla donde se pueden soltar, redimensionar o seleccionar eventos con callbacks de restriccion. Devuelve false para prevenir la accion:

<Calendar
events={events}
enableDnD
// Solo permitir drops en horario laboral
dragConstraint={(event, newStart, newEnd) => {
const hour = new Date(newStart).getHours();
return hour >= 8 && hour < 18;
}}
// Prevenir redimensionar mas de 2 horas
resizeConstraint={(event, newStart, newEnd) => {
const ms = new Date(newEnd).getTime() - new Date(newStart).getTime();
return ms <= 2 * 60 * 60 * 1000;
}}
// Solo permitir seleccion en horario laboral
selectConstraint={(start, end) => {
const hour = new Date(start).getHours();
return hour >= 8 && hour < 18;
}}
onEventDrop={handleDrop}
onEventResize={handleResize}
onSlotSelect={handleSelect}
/>

Cuando una restriccion devuelve false, la interaccion se revierte silenciosamente — el evento vuelve a su posicion original y no se dispara ningun callback.

Usa display: "background" en eventos para renderizarlos como bloques de tiempo coloreados detras de los eventos normales. Ideal para mostrar horario laboral, disponibilidad o tiempo bloqueado:

const events = [
{
id: "horario-laboral",
title: "Horario Laboral",
start: "2026-03-25T08:00:00",
end: "2026-03-25T18:00:00",
display: "background",
color: "#22c55e",
},
// ... eventos normales
];
<Calendar events={events} />

Los eventos de fondo no son interactivos — no se pueden arrastrar, redimensionar ni hacer click.

function App() {
const [events, setEvents] = useState(initialEvents);
const [creating, setCreating] = useState(null);
const handleDrop = async (event, newStart, newEnd) => {
// Actualizacion optimista
setEvents((prev) =>
prev.map((e) =>
e.id === event.id ? { ...e, start: newStart, end: newEnd } : e
)
);
await fetch(`/api/events/${event.id}`, {
method: "PATCH",
body: JSON.stringify({ start: newStart, end: newEnd }),
});
};
return (
<Calendar
events={events}
enableDnD
onEventDrop={handleDrop}
onEventResize={handleDrop}
onSlotSelect={(start, end) => setCreating({ start, end })}
/>
);
}