@mantine/schedule 9.6.2 → 9.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/cjs/components/DayView/DayView.cjs +45 -30
  2. package/cjs/components/DayView/DayView.cjs.map +1 -1
  3. package/cjs/components/DayView/DayView.module.cjs.map +1 -1
  4. package/cjs/components/MobileMonthView/get-mobile-month-view-events.cjs +10 -2
  5. package/cjs/components/MobileMonthView/get-mobile-month-view-events.cjs.map +1 -1
  6. package/cjs/components/MonthView/MonthView.cjs +11 -4
  7. package/cjs/components/MonthView/MonthView.cjs.map +1 -1
  8. package/cjs/components/ResourcesDayView/ResourcesDayView.cjs +74 -59
  9. package/cjs/components/ResourcesDayView/ResourcesDayView.cjs.map +1 -1
  10. package/cjs/components/ResourcesDayView/ResourcesDayView.module.cjs.map +1 -1
  11. package/cjs/components/ResourcesMonthView/ResourcesMonthView.cjs +18 -6
  12. package/cjs/components/ResourcesMonthView/ResourcesMonthView.cjs.map +1 -1
  13. package/cjs/components/ResourcesSchedule/ResourcesSchedule.cjs +6 -1
  14. package/cjs/components/ResourcesSchedule/ResourcesSchedule.cjs.map +1 -1
  15. package/cjs/components/ResourcesWeekView/ResourcesWeekView.cjs +84 -57
  16. package/cjs/components/ResourcesWeekView/ResourcesWeekView.cjs.map +1 -1
  17. package/cjs/components/ResourcesWeekView/ResourcesWeekView.module.cjs.map +1 -1
  18. package/cjs/components/Schedule/Schedule.cjs +14 -1
  19. package/cjs/components/Schedule/Schedule.cjs.map +1 -1
  20. package/cjs/components/ScheduleEvent/ScheduleEvent.module.cjs.map +1 -1
  21. package/cjs/components/WeekView/WeekView.cjs +67 -45
  22. package/cjs/components/WeekView/WeekView.cjs.map +1 -1
  23. package/cjs/components/WeekView/WeekView.module.cjs.map +1 -1
  24. package/cjs/hooks/use-day-grid-event-resize.cjs +38 -8
  25. package/cjs/hooks/use-day-grid-event-resize.cjs.map +1 -1
  26. package/cjs/hooks/use-drag-drop-handlers.cjs +126 -10
  27. package/cjs/hooks/use-drag-drop-handlers.cjs.map +1 -1
  28. package/cjs/hooks/use-event-resize.cjs +36 -12
  29. package/cjs/hooks/use-event-resize.cjs.map +1 -1
  30. package/cjs/hooks/use-horizontal-event-resize.cjs +37 -12
  31. package/cjs/hooks/use-horizontal-event-resize.cjs.map +1 -1
  32. package/cjs/hooks/validate-event-resize.cjs +37 -0
  33. package/cjs/hooks/validate-event-resize.cjs.map +1 -0
  34. package/cjs/index.cjs +2 -0
  35. package/cjs/utils/is-event-placement-allowed/is-event-placement-allowed.cjs +52 -0
  36. package/cjs/utils/is-event-placement-allowed/is-event-placement-allowed.cjs.map +1 -0
  37. package/esm/components/DayView/DayView.mjs +45 -30
  38. package/esm/components/DayView/DayView.mjs.map +1 -1
  39. package/esm/components/DayView/DayView.module.mjs.map +1 -1
  40. package/esm/components/MobileMonthView/get-mobile-month-view-events.mjs +10 -2
  41. package/esm/components/MobileMonthView/get-mobile-month-view-events.mjs.map +1 -1
  42. package/esm/components/MonthView/MonthView.mjs +11 -4
  43. package/esm/components/MonthView/MonthView.mjs.map +1 -1
  44. package/esm/components/ResourcesDayView/ResourcesDayView.mjs +74 -59
  45. package/esm/components/ResourcesDayView/ResourcesDayView.mjs.map +1 -1
  46. package/esm/components/ResourcesDayView/ResourcesDayView.module.mjs.map +1 -1
  47. package/esm/components/ResourcesMonthView/ResourcesMonthView.mjs +18 -6
  48. package/esm/components/ResourcesMonthView/ResourcesMonthView.mjs.map +1 -1
  49. package/esm/components/ResourcesSchedule/ResourcesSchedule.mjs +6 -1
  50. package/esm/components/ResourcesSchedule/ResourcesSchedule.mjs.map +1 -1
  51. package/esm/components/ResourcesWeekView/ResourcesWeekView.mjs +84 -57
  52. package/esm/components/ResourcesWeekView/ResourcesWeekView.mjs.map +1 -1
  53. package/esm/components/ResourcesWeekView/ResourcesWeekView.module.mjs.map +1 -1
  54. package/esm/components/Schedule/Schedule.mjs +14 -1
  55. package/esm/components/Schedule/Schedule.mjs.map +1 -1
  56. package/esm/components/ScheduleEvent/ScheduleEvent.module.mjs.map +1 -1
  57. package/esm/components/WeekView/WeekView.mjs +67 -45
  58. package/esm/components/WeekView/WeekView.mjs.map +1 -1
  59. package/esm/components/WeekView/WeekView.module.mjs.map +1 -1
  60. package/esm/hooks/use-day-grid-event-resize.mjs +38 -8
  61. package/esm/hooks/use-day-grid-event-resize.mjs.map +1 -1
  62. package/esm/hooks/use-drag-drop-handlers.mjs +126 -10
  63. package/esm/hooks/use-drag-drop-handlers.mjs.map +1 -1
  64. package/esm/hooks/use-event-resize.mjs +36 -12
  65. package/esm/hooks/use-event-resize.mjs.map +1 -1
  66. package/esm/hooks/use-horizontal-event-resize.mjs +37 -12
  67. package/esm/hooks/use-horizontal-event-resize.mjs.map +1 -1
  68. package/esm/hooks/validate-event-resize.mjs +37 -0
  69. package/esm/hooks/validate-event-resize.mjs.map +1 -0
  70. package/esm/index.mjs +2 -1
  71. package/esm/utils/is-event-placement-allowed/is-event-placement-allowed.mjs +50 -0
  72. package/esm/utils/is-event-placement-allowed/is-event-placement-allowed.mjs.map +1 -0
  73. package/lib/components/DayView/DayView.d.ts +11 -1
  74. package/lib/components/MonthView/MonthView.d.ts +9 -1
  75. package/lib/components/ResourcesDayView/ResourcesDayView.d.ts +11 -1
  76. package/lib/components/ResourcesMonthView/ResourcesMonthView.d.ts +11 -1
  77. package/lib/components/ResourcesSchedule/ResourcesSchedule.d.ts +12 -2
  78. package/lib/components/ResourcesWeekView/ResourcesWeekView.d.ts +11 -1
  79. package/lib/components/Schedule/Schedule.d.ts +12 -2
  80. package/lib/components/WeekView/WeekView.d.ts +11 -1
  81. package/lib/hooks/use-day-grid-event-resize.d.ts +4 -2
  82. package/lib/hooks/use-drag-drop-handlers.d.ts +17 -1
  83. package/lib/hooks/use-event-resize.d.ts +4 -2
  84. package/lib/hooks/use-horizontal-event-resize.d.ts +4 -2
  85. package/lib/hooks/validate-event-resize.d.ts +33 -0
  86. package/lib/types.d.ts +65 -0
  87. package/lib/utils/index.d.ts +2 -0
  88. package/lib/utils/is-event-placement-allowed/is-event-placement-allowed.d.ts +22 -0
  89. package/package.json +6 -6
  90. package/styles.css +29 -0
  91. package/styles.layer.css +29 -0
@@ -2,14 +2,27 @@
2
2
  import { clampIntervalMinutes } from "../utils/clamp-interval-minutes/clamp-interval-minutes.mjs";
3
3
  import { parseTimeString } from "../utils/parse-time-string/parse-time-string.mjs";
4
4
  import { getDayRelativePercent } from "../utils/get-day-relative-percent/get-day-relative-percent.mjs";
5
+ import { validateEventResize } from "./validate-event-resize.mjs";
5
6
  import dayjs from "dayjs";
6
7
  import { useCallback, useEffect, useEffectEvent, useRef, useState } from "react";
7
8
  //#region packages/@mantine/schedule/src/hooks/use-horizontal-event-resize.ts
8
- function useHorizontalEventResize({ enabled = false, mode = "default", startTime, endTime, intervalMinutes, resizeIntervalMinutes, onEventResize, canResizeEvent, withBackgroundEvents = false }) {
9
+ function useHorizontalEventResize({ enabled = false, mode = "default", startTime, endTime, intervalMinutes, resizeIntervalMinutes, onEventResize, canResizeEvent, events, preventEventOverlap, canResizeEventTo, onEventPlacementRejected, withBackgroundEvents = false }) {
9
10
  const [resizeState, setResizeState] = useState(null);
11
+ const [resizeValid, setResizeValid] = useState(true);
10
12
  const resizeRef = useRef(null);
11
13
  const justResizedRef = useRef(false);
12
14
  const stableOnEventResize = useEffectEvent(onEventResize || (() => {}));
15
+ const stableOnEventPlacementRejected = useEffectEvent(onEventPlacementRejected || (() => {}));
16
+ const validateResize = useEffectEvent((event, start, end, edge) => validateEventResize({
17
+ event,
18
+ start,
19
+ end,
20
+ edge,
21
+ events,
22
+ preventEventOverlap,
23
+ canResizeEventTo,
24
+ resourceId: event.resourceId
25
+ }));
13
26
  const parsedStartTime = parseTimeString(startTime);
14
27
  const parsedEndTime = parseTimeString(endTime);
15
28
  const startMinutes = parsedStartTime.hours * 60 + parsedStartTime.minutes;
@@ -70,7 +83,12 @@ function useHorizontalEventResize({ enabled = false, mode = "default", startTime
70
83
  };
71
84
  resizeRef.current = state;
72
85
  setResizeState(state);
86
+ setResizeValid(true);
73
87
  }, [enabled, mode]);
88
+ const getResizeRange = useCallback((state) => ({
89
+ start: state.edge === "start" ? percentToDateTime(state.currentLeft, state.eventDate) : state.originalStart,
90
+ end: state.edge === "start" ? state.originalEnd : percentToDateTime(state.currentLeft + state.currentWidth, state.eventDate)
91
+ }), [percentToDateTime]);
74
92
  const isResizing = resizeState !== null;
75
93
  useEffect(() => {
76
94
  if (!isResizing) return;
@@ -108,30 +126,36 @@ function useHorizontalEventResize({ enabled = false, mode = "default", startTime
108
126
  currentWidth: newWidth
109
127
  };
110
128
  setResizeState(resizeRef.current);
129
+ const range = getResizeRange(resizeRef.current);
130
+ setResizeValid(validateResize(state.event, range.start, range.end, state.edge).valid);
111
131
  };
112
132
  const handlePointerUp = () => {
113
133
  const state = resizeRef.current;
114
134
  if (state) {
115
135
  if (state.currentLeft !== state.originalLeft || state.currentWidth !== state.originalWidth) {
116
- let newStart;
117
- let newEnd;
118
- if (state.edge === "start") {
119
- newStart = percentToDateTime(state.currentLeft, state.eventDate);
120
- newEnd = state.originalEnd;
121
- } else {
122
- newStart = state.originalStart;
123
- newEnd = percentToDateTime(state.currentLeft + state.currentWidth, state.eventDate);
124
- }
125
- stableOnEventResize({
136
+ const { start: newStart, end: newEnd } = getResizeRange(state);
137
+ const { valid, conflicts, reason } = validateResize(state.event, newStart, newEnd, state.edge);
138
+ if (valid) stableOnEventResize({
126
139
  eventId: state.eventId,
127
140
  newStart,
128
141
  newEnd,
129
142
  event: state.event
130
143
  });
144
+ else stableOnEventPlacementRejected({
145
+ action: "resize",
146
+ event: state.event,
147
+ start: newStart,
148
+ end: newEnd,
149
+ edge: state.edge,
150
+ resourceId: state.event.resourceId,
151
+ conflicts,
152
+ reason
153
+ });
131
154
  }
132
155
  }
133
156
  resizeRef.current = null;
134
157
  setResizeState(null);
158
+ setResizeValid(true);
135
159
  justResizedRef.current = true;
136
160
  requestAnimationFrame(() => {
137
161
  justResizedRef.current = false;
@@ -172,7 +196,8 @@ function useHorizontalEventResize({ enabled = false, mode = "default", startTime
172
196
  resizingEdge: resizeState?.edge ?? null,
173
197
  getResizePosition,
174
198
  isResizableEvent,
175
- wasResizing
199
+ wasResizing,
200
+ resizeValid
176
201
  };
177
202
  }
178
203
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"use-horizontal-event-resize.mjs","names":[],"sources":["../../src/hooks/use-horizontal-event-resize.ts"],"sourcesContent":["import dayjs from 'dayjs';\nimport { useCallback, useEffect, useEffectEvent, useRef, useState } from 'react';\nimport { DateTimeStringValue, ScheduleEventData, ScheduleMode } from '../types';\nimport { clampIntervalMinutes } from '../utils/clamp-interval-minutes/clamp-interval-minutes';\nimport { getDayRelativePercent } from '../utils/get-day-relative-percent/get-day-relative-percent';\nimport { parseTimeString } from '../utils/parse-time-string/parse-time-string';\n\ntype ResizeEdge = 'start' | 'end';\n\ninterface ResizeState {\n eventId: string | number;\n event: ScheduleEventData;\n edge: ResizeEdge;\n container: HTMLElement;\n originalLeft: number;\n originalWidth: number;\n currentLeft: number;\n currentWidth: number;\n eventDate: string;\n originalStart: DateTimeStringValue;\n originalEnd: DateTimeStringValue;\n dayIndex: number;\n dayCount: number;\n}\n\nexport interface UseHorizontalEventResizeInput {\n enabled?: boolean;\n mode?: ScheduleMode;\n startTime: string;\n endTime: string;\n intervalMinutes: number;\n resizeIntervalMinutes?: number;\n onEventResize?: (data: {\n eventId: string | number;\n newStart: DateTimeStringValue;\n newEnd: DateTimeStringValue;\n event: ScheduleEventData;\n }) => void;\n canResizeEvent?: (event: ScheduleEventData) => boolean;\n withBackgroundEvents?: boolean;\n}\n\nexport function useHorizontalEventResize({\n enabled = false,\n mode = 'default',\n startTime,\n endTime,\n intervalMinutes,\n resizeIntervalMinutes,\n onEventResize,\n canResizeEvent,\n withBackgroundEvents = false,\n}: UseHorizontalEventResizeInput) {\n const [resizeState, setResizeState] = useState<ResizeState | null>(null);\n const resizeRef = useRef<ResizeState | null>(null);\n const justResizedRef = useRef(false);\n const stableOnEventResize = useEffectEvent(onEventResize || (() => {}));\n\n const parsedStartTime = parseTimeString(startTime);\n const parsedEndTime = parseTimeString(endTime);\n const startMinutes = parsedStartTime.hours * 60 + parsedStartTime.minutes;\n const endMinutes = parsedEndTime.hours * 60 + parsedEndTime.minutes;\n const clampedInterval = clampIntervalMinutes(intervalMinutes);\n const clampedResizeInterval = clampIntervalMinutes(resizeIntervalMinutes ?? intervalMinutes);\n const literalRange = endMinutes - startMinutes;\n const totalMinutes = Math.ceil(literalRange / clampedInterval) * clampedInterval;\n\n // Snapped in absolute minutes from midnight so resized edges land on the same grid as\n // dragged events – see the matching comment in `use-event-resize`.\n const clampAndSnap = useCallback(\n (minutes: number): number => {\n const snapped =\n Math.round((startMinutes + minutes) / clampedResizeInterval) * clampedResizeInterval;\n return Math.max(0, Math.min(literalRange, snapped - startMinutes));\n },\n [literalRange, clampedResizeInterval, startMinutes]\n );\n\n const percentToDateTime = useCallback(\n (percent: number, eventDate: string): DateTimeStringValue => {\n const minutes = (percent / 100) * totalMinutes;\n const snappedMinutes = clampAndSnap(minutes);\n const totalMins = startMinutes + snappedMinutes;\n const hours = Math.floor(totalMins / 60);\n const mins = totalMins % 60;\n return `${eventDate} ${String(hours).padStart(2, '0')}:${String(mins).padStart(2, '0')}:00`;\n },\n [totalMinutes, startMinutes, clampAndSnap]\n );\n\n const snapEdgeMinutes = useCallback(\n (minutes: number, direction: 'up' | 'down'): number => {\n const absolute = startMinutes + minutes;\n const snapped =\n direction === 'up'\n ? Math.ceil(absolute / clampedResizeInterval) * clampedResizeInterval\n : Math.floor(absolute / clampedResizeInterval) * clampedResizeInterval;\n return Math.max(0, Math.min(literalRange, snapped - startMinutes));\n },\n [literalRange, clampedResizeInterval, startMinutes]\n );\n\n const percentToMinutes = useCallback(\n (percent: number): number => (percent / 100) * totalMinutes,\n [totalMinutes]\n );\n\n const minutesToPercent = useCallback(\n (minutes: number): number => (minutes / totalMinutes) * 100,\n [totalMinutes]\n );\n\n const handleResizeStart = useCallback(\n ({\n event,\n edge,\n container,\n originalLeft,\n originalWidth,\n eventDate,\n dayIndex = 0,\n dayCount = 1,\n pointerEvent,\n }: {\n event: ScheduleEventData;\n edge: ResizeEdge;\n container: HTMLElement;\n originalLeft: number;\n originalWidth: number;\n eventDate: string;\n dayIndex?: number;\n dayCount?: number;\n pointerEvent: React.PointerEvent;\n }) => {\n if (!enabled || mode === 'static') {\n return;\n }\n\n pointerEvent.preventDefault();\n pointerEvent.stopPropagation();\n\n const state: ResizeState = {\n eventId: event.id,\n event,\n edge,\n container,\n originalLeft,\n originalWidth,\n currentLeft: originalLeft,\n currentWidth: originalWidth,\n eventDate,\n originalStart: dayjs(event.start).format('YYYY-MM-DD HH:mm:ss'),\n originalEnd: dayjs(event.end).format('YYYY-MM-DD HH:mm:ss'),\n dayIndex,\n dayCount,\n };\n\n resizeRef.current = state;\n setResizeState(state);\n },\n [enabled, mode]\n );\n\n const isResizing = resizeState !== null;\n\n useEffect(() => {\n if (!isResizing) {\n return undefined;\n }\n\n const savedUserSelect = document.body.style.userSelect;\n const savedCursor = document.body.style.cursor;\n document.body.style.userSelect = 'none';\n document.body.style.cursor = 'ew-resize';\n\n const handlePointerMove = (e: PointerEvent) => {\n const state = resizeRef.current;\n if (!state) {\n return;\n }\n\n const containerRect = state.container.getBoundingClientRect();\n const rawPercent = getDayRelativePercent({\n clientX: e.clientX,\n containerLeft: containerRect.left,\n containerWidth: containerRect.width,\n dayIndex: state.dayIndex,\n dayCount: state.dayCount,\n });\n const draggedMinutes = clampAndSnap(percentToMinutes(rawPercent));\n\n let newLeft = state.originalLeft;\n let newWidth = state.originalWidth;\n\n if (state.edge === 'end') {\n const leftMinutes = percentToMinutes(state.originalLeft);\n const minEndMinutes = snapEdgeMinutes(leftMinutes + clampedResizeInterval, 'up');\n const endMinutes = Math.max(draggedMinutes, minEndMinutes);\n newWidth = Math.max(0, minutesToPercent(endMinutes) - state.originalLeft);\n } else {\n const rightMinutes = percentToMinutes(state.originalLeft + state.originalWidth);\n const maxStartMinutes = snapEdgeMinutes(rightMinutes - clampedResizeInterval, 'down');\n const startMinutesValue = Math.min(draggedMinutes, maxStartMinutes);\n newLeft = minutesToPercent(startMinutesValue);\n newWidth = Math.max(0, state.originalLeft + state.originalWidth - newLeft);\n }\n\n resizeRef.current = { ...state, currentLeft: newLeft, currentWidth: newWidth };\n setResizeState(resizeRef.current);\n };\n\n const handlePointerUp = () => {\n const state = resizeRef.current;\n if (state) {\n if (\n state.currentLeft !== state.originalLeft ||\n state.currentWidth !== state.originalWidth\n ) {\n let newStart: DateTimeStringValue;\n let newEnd: DateTimeStringValue;\n\n if (state.edge === 'start') {\n newStart = percentToDateTime(state.currentLeft, state.eventDate);\n newEnd = state.originalEnd;\n } else {\n newStart = state.originalStart;\n newEnd = percentToDateTime(state.currentLeft + state.currentWidth, state.eventDate);\n }\n\n stableOnEventResize({\n eventId: state.eventId,\n newStart,\n newEnd,\n event: state.event,\n });\n }\n }\n resizeRef.current = null;\n setResizeState(null);\n justResizedRef.current = true;\n requestAnimationFrame(() => {\n justResizedRef.current = false;\n });\n };\n\n document.addEventListener('pointermove', handlePointerMove);\n document.addEventListener('pointerup', handlePointerUp);\n\n return () => {\n document.body.style.userSelect = savedUserSelect;\n document.body.style.cursor = savedCursor;\n document.removeEventListener('pointermove', handlePointerMove);\n document.removeEventListener('pointerup', handlePointerUp);\n };\n }, [isResizing]);\n\n const getResizePosition = useCallback(\n (eventId: string | number, eventDate?: string) => {\n if (!resizeState || resizeState.eventId !== eventId) {\n return null;\n }\n if (eventDate !== undefined && resizeState.eventDate !== eventDate) {\n return null;\n }\n return { left: resizeState.currentLeft, width: resizeState.currentWidth };\n },\n [resizeState]\n );\n\n const isResizableEvent = useCallback(\n (event: ScheduleEventData) => {\n if (!enabled || mode === 'static') {\n return false;\n }\n if (event.display === 'background' && !withBackgroundEvents) {\n return false;\n }\n return canResizeEvent ? canResizeEvent(event) : true;\n },\n [enabled, mode, canResizeEvent, withBackgroundEvents]\n );\n\n const wasResizing = useCallback(() => justResizedRef.current, []);\n\n return {\n handleResizeStart,\n isResizing,\n resizingEventId: resizeState?.eventId ?? null,\n resizingEdge: resizeState?.edge ?? null,\n getResizePosition,\n isResizableEvent,\n wasResizing,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"use-horizontal-event-resize.mjs","names":[],"sources":["../../src/hooks/use-horizontal-event-resize.ts"],"sourcesContent":["import dayjs from 'dayjs';\nimport { useCallback, useEffect, useEffectEvent, useRef, useState } from 'react';\nimport { DateTimeStringValue, ScheduleEventData, ScheduleMode } from '../types';\nimport { clampIntervalMinutes } from '../utils/clamp-interval-minutes/clamp-interval-minutes';\nimport { getDayRelativePercent } from '../utils/get-day-relative-percent/get-day-relative-percent';\nimport { parseTimeString } from '../utils/parse-time-string/parse-time-string';\nimport { EventResizeValidationOptions, validateEventResize } from './validate-event-resize';\n\ntype ResizeEdge = 'start' | 'end';\n\ninterface ResizeState {\n eventId: string | number;\n event: ScheduleEventData;\n edge: ResizeEdge;\n container: HTMLElement;\n originalLeft: number;\n originalWidth: number;\n currentLeft: number;\n currentWidth: number;\n eventDate: string;\n originalStart: DateTimeStringValue;\n originalEnd: DateTimeStringValue;\n dayIndex: number;\n dayCount: number;\n}\n\nexport interface UseHorizontalEventResizeInput extends EventResizeValidationOptions {\n enabled?: boolean;\n mode?: ScheduleMode;\n startTime: string;\n endTime: string;\n intervalMinutes: number;\n resizeIntervalMinutes?: number;\n onEventResize?: (data: {\n eventId: string | number;\n newStart: DateTimeStringValue;\n newEnd: DateTimeStringValue;\n event: ScheduleEventData;\n }) => void;\n canResizeEvent?: (event: ScheduleEventData) => boolean;\n withBackgroundEvents?: boolean;\n}\n\nexport function useHorizontalEventResize({\n enabled = false,\n mode = 'default',\n startTime,\n endTime,\n intervalMinutes,\n resizeIntervalMinutes,\n onEventResize,\n canResizeEvent,\n events,\n preventEventOverlap,\n canResizeEventTo,\n onEventPlacementRejected,\n withBackgroundEvents = false,\n}: UseHorizontalEventResizeInput) {\n const [resizeState, setResizeState] = useState<ResizeState | null>(null);\n const [resizeValid, setResizeValid] = useState(true);\n const resizeRef = useRef<ResizeState | null>(null);\n const justResizedRef = useRef(false);\n const stableOnEventResize = useEffectEvent(onEventResize || (() => {}));\n const stableOnEventPlacementRejected = useEffectEvent(onEventPlacementRejected || (() => {}));\n\n const validateResize = useEffectEvent(\n (\n event: ScheduleEventData,\n start: DateTimeStringValue,\n end: DateTimeStringValue,\n edge: 'start' | 'end'\n ) =>\n validateEventResize({\n event,\n start,\n end,\n edge,\n events,\n preventEventOverlap,\n canResizeEventTo,\n resourceId: event.resourceId,\n })\n );\n\n const parsedStartTime = parseTimeString(startTime);\n const parsedEndTime = parseTimeString(endTime);\n const startMinutes = parsedStartTime.hours * 60 + parsedStartTime.minutes;\n const endMinutes = parsedEndTime.hours * 60 + parsedEndTime.minutes;\n const clampedInterval = clampIntervalMinutes(intervalMinutes);\n const clampedResizeInterval = clampIntervalMinutes(resizeIntervalMinutes ?? intervalMinutes);\n const literalRange = endMinutes - startMinutes;\n const totalMinutes = Math.ceil(literalRange / clampedInterval) * clampedInterval;\n\n // Snapped in absolute minutes from midnight so resized edges land on the same grid as\n // dragged events – see the matching comment in `use-event-resize`.\n const clampAndSnap = useCallback(\n (minutes: number): number => {\n const snapped =\n Math.round((startMinutes + minutes) / clampedResizeInterval) * clampedResizeInterval;\n return Math.max(0, Math.min(literalRange, snapped - startMinutes));\n },\n [literalRange, clampedResizeInterval, startMinutes]\n );\n\n const percentToDateTime = useCallback(\n (percent: number, eventDate: string): DateTimeStringValue => {\n const minutes = (percent / 100) * totalMinutes;\n const snappedMinutes = clampAndSnap(minutes);\n const totalMins = startMinutes + snappedMinutes;\n const hours = Math.floor(totalMins / 60);\n const mins = totalMins % 60;\n return `${eventDate} ${String(hours).padStart(2, '0')}:${String(mins).padStart(2, '0')}:00`;\n },\n [totalMinutes, startMinutes, clampAndSnap]\n );\n\n const snapEdgeMinutes = useCallback(\n (minutes: number, direction: 'up' | 'down'): number => {\n const absolute = startMinutes + minutes;\n const snapped =\n direction === 'up'\n ? Math.ceil(absolute / clampedResizeInterval) * clampedResizeInterval\n : Math.floor(absolute / clampedResizeInterval) * clampedResizeInterval;\n return Math.max(0, Math.min(literalRange, snapped - startMinutes));\n },\n [literalRange, clampedResizeInterval, startMinutes]\n );\n\n const percentToMinutes = useCallback(\n (percent: number): number => (percent / 100) * totalMinutes,\n [totalMinutes]\n );\n\n const minutesToPercent = useCallback(\n (minutes: number): number => (minutes / totalMinutes) * 100,\n [totalMinutes]\n );\n\n const handleResizeStart = useCallback(\n ({\n event,\n edge,\n container,\n originalLeft,\n originalWidth,\n eventDate,\n dayIndex = 0,\n dayCount = 1,\n pointerEvent,\n }: {\n event: ScheduleEventData;\n edge: ResizeEdge;\n container: HTMLElement;\n originalLeft: number;\n originalWidth: number;\n eventDate: string;\n dayIndex?: number;\n dayCount?: number;\n pointerEvent: React.PointerEvent;\n }) => {\n if (!enabled || mode === 'static') {\n return;\n }\n\n pointerEvent.preventDefault();\n pointerEvent.stopPropagation();\n\n const state: ResizeState = {\n eventId: event.id,\n event,\n edge,\n container,\n originalLeft,\n originalWidth,\n currentLeft: originalLeft,\n currentWidth: originalWidth,\n eventDate,\n originalStart: dayjs(event.start).format('YYYY-MM-DD HH:mm:ss'),\n originalEnd: dayjs(event.end).format('YYYY-MM-DD HH:mm:ss'),\n dayIndex,\n dayCount,\n };\n\n resizeRef.current = state;\n setResizeState(state);\n setResizeValid(true);\n },\n [enabled, mode]\n );\n\n const getResizeRange = useCallback(\n (state: ResizeState) => ({\n start:\n state.edge === 'start'\n ? percentToDateTime(state.currentLeft, state.eventDate)\n : state.originalStart,\n end:\n state.edge === 'start'\n ? state.originalEnd\n : percentToDateTime(state.currentLeft + state.currentWidth, state.eventDate),\n }),\n [percentToDateTime]\n );\n\n const isResizing = resizeState !== null;\n\n useEffect(() => {\n if (!isResizing) {\n return undefined;\n }\n\n const savedUserSelect = document.body.style.userSelect;\n const savedCursor = document.body.style.cursor;\n document.body.style.userSelect = 'none';\n document.body.style.cursor = 'ew-resize';\n\n const handlePointerMove = (e: PointerEvent) => {\n const state = resizeRef.current;\n if (!state) {\n return;\n }\n\n const containerRect = state.container.getBoundingClientRect();\n const rawPercent = getDayRelativePercent({\n clientX: e.clientX,\n containerLeft: containerRect.left,\n containerWidth: containerRect.width,\n dayIndex: state.dayIndex,\n dayCount: state.dayCount,\n });\n const draggedMinutes = clampAndSnap(percentToMinutes(rawPercent));\n\n let newLeft = state.originalLeft;\n let newWidth = state.originalWidth;\n\n if (state.edge === 'end') {\n const leftMinutes = percentToMinutes(state.originalLeft);\n const minEndMinutes = snapEdgeMinutes(leftMinutes + clampedResizeInterval, 'up');\n const endMinutes = Math.max(draggedMinutes, minEndMinutes);\n newWidth = Math.max(0, minutesToPercent(endMinutes) - state.originalLeft);\n } else {\n const rightMinutes = percentToMinutes(state.originalLeft + state.originalWidth);\n const maxStartMinutes = snapEdgeMinutes(rightMinutes - clampedResizeInterval, 'down');\n const startMinutesValue = Math.min(draggedMinutes, maxStartMinutes);\n newLeft = minutesToPercent(startMinutesValue);\n newWidth = Math.max(0, state.originalLeft + state.originalWidth - newLeft);\n }\n\n resizeRef.current = { ...state, currentLeft: newLeft, currentWidth: newWidth };\n setResizeState(resizeRef.current);\n\n const range = getResizeRange(resizeRef.current);\n setResizeValid(validateResize(state.event, range.start, range.end, state.edge).valid);\n };\n\n const handlePointerUp = () => {\n const state = resizeRef.current;\n if (state) {\n if (\n state.currentLeft !== state.originalLeft ||\n state.currentWidth !== state.originalWidth\n ) {\n const { start: newStart, end: newEnd } = getResizeRange(state);\n const { valid, conflicts, reason } = validateResize(\n state.event,\n newStart,\n newEnd,\n state.edge\n );\n\n if (valid) {\n stableOnEventResize({\n eventId: state.eventId,\n newStart,\n newEnd,\n event: state.event,\n });\n } else {\n stableOnEventPlacementRejected({\n action: 'resize',\n event: state.event,\n start: newStart,\n end: newEnd,\n edge: state.edge,\n resourceId: state.event.resourceId,\n conflicts,\n reason: reason!,\n });\n }\n }\n }\n resizeRef.current = null;\n setResizeState(null);\n setResizeValid(true);\n justResizedRef.current = true;\n requestAnimationFrame(() => {\n justResizedRef.current = false;\n });\n };\n\n document.addEventListener('pointermove', handlePointerMove);\n document.addEventListener('pointerup', handlePointerUp);\n\n return () => {\n document.body.style.userSelect = savedUserSelect;\n document.body.style.cursor = savedCursor;\n document.removeEventListener('pointermove', handlePointerMove);\n document.removeEventListener('pointerup', handlePointerUp);\n };\n }, [isResizing]);\n\n const getResizePosition = useCallback(\n (eventId: string | number, eventDate?: string) => {\n if (!resizeState || resizeState.eventId !== eventId) {\n return null;\n }\n if (eventDate !== undefined && resizeState.eventDate !== eventDate) {\n return null;\n }\n return { left: resizeState.currentLeft, width: resizeState.currentWidth };\n },\n [resizeState]\n );\n\n const isResizableEvent = useCallback(\n (event: ScheduleEventData) => {\n if (!enabled || mode === 'static') {\n return false;\n }\n if (event.display === 'background' && !withBackgroundEvents) {\n return false;\n }\n return canResizeEvent ? canResizeEvent(event) : true;\n },\n [enabled, mode, canResizeEvent, withBackgroundEvents]\n );\n\n const wasResizing = useCallback(() => justResizedRef.current, []);\n\n return {\n handleResizeStart,\n isResizing,\n resizingEventId: resizeState?.eventId ?? null,\n resizingEdge: resizeState?.edge ?? null,\n getResizePosition,\n isResizableEvent,\n wasResizing,\n resizeValid,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ "use client";
2
+ import { isEventPlacementAllowed } from "../utils/is-event-placement-allowed/is-event-placement-allowed.mjs";
3
+ //#region packages/@mantine/schedule/src/hooks/validate-event-resize.ts
4
+ function validateEventResize({ event, start, end, edge, events, preventEventOverlap, canResizeEventTo, resourceId }) {
5
+ const { allowed, conflicts } = isEventPlacementAllowed({
6
+ event,
7
+ start,
8
+ end,
9
+ events: events || [],
10
+ preventEventOverlap,
11
+ resourceId
12
+ });
13
+ if (!allowed) return {
14
+ valid: false,
15
+ conflicts,
16
+ reason: "overlap"
17
+ };
18
+ if (canResizeEventTo && canResizeEventTo({
19
+ event,
20
+ start,
21
+ end,
22
+ edge
23
+ }) === false) return {
24
+ valid: false,
25
+ conflicts: [],
26
+ reason: "rejected"
27
+ };
28
+ return {
29
+ valid: true,
30
+ conflicts: [],
31
+ reason: null
32
+ };
33
+ }
34
+ //#endregion
35
+ export { validateEventResize };
36
+
37
+ //# sourceMappingURL=validate-event-resize.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-event-resize.mjs","names":[],"sources":["../../src/hooks/validate-event-resize.ts"],"sourcesContent":["import {\n DateTimeStringValue,\n PreventEventOverlap,\n ScheduleCanResizeEventToData,\n ScheduleEventData,\n} from '../types';\nimport { isEventPlacementAllowed } from '../utils/is-event-placement-allowed/is-event-placement-allowed';\n\nexport interface EventResizeValidationOptions {\n /** Events the candidate range is checked against when `preventEventOverlap` is set */\n events?: ScheduleEventData[];\n\n /** If set, resizes that would make the event overlap another event are rejected */\n preventEventOverlap?: PreventEventOverlap;\n\n /** Called on every pointer move to compute live feedback and again before the resize is committed, return `false` to reject the new size */\n canResizeEventTo?: (data: ScheduleCanResizeEventToData) => boolean;\n\n /** Called when a resize is rejected */\n onEventPlacementRejected?: (data: {\n action: 'resize';\n event: ScheduleEventData;\n start: DateTimeStringValue;\n end: DateTimeStringValue;\n edge: 'start' | 'end';\n resourceId?: string | number;\n conflicts: ScheduleEventData[];\n reason: 'overlap' | 'rejected';\n }) => void;\n}\n\nexport interface ValidateEventResizeInput extends EventResizeValidationOptions {\n event: ScheduleEventData;\n start: DateTimeStringValue;\n end: DateTimeStringValue;\n edge: 'start' | 'end';\n resourceId?: string | number;\n}\n\nexport interface ValidateEventResizeResult {\n valid: boolean;\n conflicts: ScheduleEventData[];\n reason: 'overlap' | 'rejected' | null;\n}\n\nexport function validateEventResize({\n event,\n start,\n end,\n edge,\n events,\n preventEventOverlap,\n canResizeEventTo,\n resourceId,\n}: ValidateEventResizeInput): ValidateEventResizeResult {\n const { allowed, conflicts } = isEventPlacementAllowed({\n event,\n start,\n end,\n events: events || [],\n preventEventOverlap,\n resourceId,\n });\n\n if (!allowed) {\n return { valid: false, conflicts, reason: 'overlap' };\n }\n\n if (canResizeEventTo && canResizeEventTo({ event, start, end, edge }) === false) {\n return { valid: false, conflicts: [], reason: 'rejected' };\n }\n\n return { valid: true, conflicts: [], reason: null };\n}\n"],"mappings":";;;AA6CA,SAAgB,oBAAoB,EAClC,OACA,OACA,KACA,MACA,QACA,qBACA,kBACA,cACsD;CACtD,MAAM,EAAE,SAAS,cAAc,wBAAwB;EACrD;EACA;EACA;EACA,QAAQ,UAAU,CAAC;EACnB;EACA;CACF,CAAC;CAED,IAAI,CAAC,SACH,OAAO;EAAE,OAAO;EAAO;EAAW,QAAQ;CAAU;CAGtD,IAAI,oBAAoB,iBAAiB;EAAE;EAAO;EAAO;EAAK;CAAK,CAAC,MAAM,OACxE,OAAO;EAAE,OAAO;EAAO,WAAW,CAAC;EAAG,QAAQ;CAAW;CAG3D,OAAO;EAAE,OAAO;EAAM,WAAW,CAAC;EAAG,QAAQ;CAAK;AACpD"}
package/esm/index.mjs CHANGED
@@ -43,6 +43,7 @@ import { getIndexFromDragPoint } from "./utils/get-index-from-drag-point/get-ind
43
43
  import { getDayRelativePercent } from "./utils/get-day-relative-percent/get-day-relative-percent.mjs";
44
44
  import { getOverlapClusters } from "./utils/get-overlap-clusters/get-overlap-clusters.mjs";
45
45
  import { applyCascadeLayout } from "./utils/apply-cascade-layout/apply-cascade-layout.mjs";
46
+ import { isEventPlacementAllowed } from "./utils/is-event-placement-allowed/is-event-placement-allowed.mjs";
46
47
  import { AgendaView } from "./components/AgendaView/AgendaView.mjs";
47
48
  import { CurrentTimeIndicator } from "./components/CurrentTimeIndicator/CurrentTimeIndicator.mjs";
48
49
  import { DragContext } from "./components/DragContext/DragContext.mjs";
@@ -60,4 +61,4 @@ import { ResourcesDayView } from "./components/ResourcesDayView/ResourcesDayView
60
61
  import { ResourcesWeekView } from "./components/ResourcesWeekView/ResourcesWeekView.mjs";
61
62
  import { ResourcesMonthView } from "./components/ResourcesMonthView/ResourcesMonthView.mjs";
62
63
  import { ResourcesSchedule } from "./components/ResourcesSchedule/ResourcesSchedule.mjs";
63
- export { AgendaView, CurrentTimeIndicator, DEFAULT_SCHEDULE_LABELS, DayView, DragContext, MobileMonthView, MonthView, MoreEvents, ResourcesDayView, ResourcesMonthView, ResourcesSchedule, ResourcesWeekView, Schedule, ScheduleBackgroundEvent, ScheduleEvent, ScheduleHeader, WeekView, YearView, applyCascadeLayout, calculateDropTime, calculateMonthDropDate, clampIntervalMinutes, expandRecurringEvents, formatDate, getBusinessHoursMod, getCurrentTimePosition, getDayPosition, getDayRelativePercent, getDayTimeIntervals, getEndOfWeek, getGroupToResourceIdMap, getIndexFromDragPoint, getLabel, getMonthDays, getMonthRange, getMonthsByQuarter, getMonthsList, getOrderedResources, getOverlapClusters, getStartOfWeek, getTimeAxisEventStyle, getVisibleEvents, getWeekDays, getWeekNumber, getWeekdaysNames, getYearsList, handleResourcesGridKeyDown, isAfterMinDate, isAllDayEvent, isBeforeMaxDate, isEventInTimeRange, isEventsOverlap, isInTimeRange, isMultidayEvent, isSameMonth, isWithinWeek, nextWeek, parseTimeString, previousWeek, sortEvents, toDateString, toTimeString, validateEvent };
64
+ export { AgendaView, CurrentTimeIndicator, DEFAULT_SCHEDULE_LABELS, DayView, DragContext, MobileMonthView, MonthView, MoreEvents, ResourcesDayView, ResourcesMonthView, ResourcesSchedule, ResourcesWeekView, Schedule, ScheduleBackgroundEvent, ScheduleEvent, ScheduleHeader, WeekView, YearView, applyCascadeLayout, calculateDropTime, calculateMonthDropDate, clampIntervalMinutes, expandRecurringEvents, formatDate, getBusinessHoursMod, getCurrentTimePosition, getDayPosition, getDayRelativePercent, getDayTimeIntervals, getEndOfWeek, getGroupToResourceIdMap, getIndexFromDragPoint, getLabel, getMonthDays, getMonthRange, getMonthsByQuarter, getMonthsList, getOrderedResources, getOverlapClusters, getStartOfWeek, getTimeAxisEventStyle, getVisibleEvents, getWeekDays, getWeekNumber, getWeekdaysNames, getYearsList, handleResourcesGridKeyDown, isAfterMinDate, isAllDayEvent, isBeforeMaxDate, isEventInTimeRange, isEventPlacementAllowed, isEventsOverlap, isInTimeRange, isMultidayEvent, isSameMonth, isWithinWeek, nextWeek, parseTimeString, previousWeek, sortEvents, toDateString, toTimeString, validateEvent };
@@ -0,0 +1,50 @@
1
+ "use client";
2
+ import { isEventsOverlap } from "../is-events-overlap/is-events-overlap.mjs";
3
+ import dayjs from "dayjs";
4
+ //#region packages/@mantine/schedule/src/utils/is-event-placement-allowed/is-event-placement-allowed.ts
5
+ function isSameEvent(event, other) {
6
+ if (event.id === other.id) return true;
7
+ const eventSeriesId = event.recurringEventId ?? event.recurringInstance?.recurringEventId;
8
+ const otherSeriesId = other.recurringEventId ?? other.recurringInstance?.recurringEventId;
9
+ if (eventSeriesId === void 0 || otherSeriesId === void 0) return false;
10
+ const eventOccurrenceId = event.recurrenceId ?? event.recurringInstance?.recurrenceId;
11
+ const otherOccurrenceId = other.recurrenceId ?? other.recurringInstance?.recurrenceId;
12
+ return eventSeriesId === otherSeriesId && eventOccurrenceId !== void 0 && eventOccurrenceId === otherOccurrenceId;
13
+ }
14
+ function isMidnight(value) {
15
+ return value.hour() === 0 && value.minute() === 0 && value.second() === 0;
16
+ }
17
+ function spansWholeDays(event) {
18
+ const start = dayjs(event.start);
19
+ const end = dayjs(event.end);
20
+ if (!isMidnight(start)) return false;
21
+ return isMidnight(end) || end.hour() === 23 && end.minute() === 59 && end.second() === 59;
22
+ }
23
+ function isEventPlacementAllowed({ event, start, end, events, preventEventOverlap, resourceId }) {
24
+ if (!preventEventOverlap) return {
25
+ allowed: true,
26
+ conflicts: []
27
+ };
28
+ const candidate = {
29
+ ...event,
30
+ start,
31
+ end,
32
+ ...resourceId !== void 0 ? { resourceId } : {}
33
+ };
34
+ const candidateIsAllDay = spansWholeDays(candidate);
35
+ const conflicts = events.filter((other) => {
36
+ if (other.display === "background" || isSameEvent(event, other)) return false;
37
+ if (resourceId !== void 0 && other.resourceId !== resourceId) return false;
38
+ if (spansWholeDays(other) !== candidateIsAllDay) return false;
39
+ if (!isEventsOverlap(other, candidate)) return false;
40
+ return typeof preventEventOverlap === "function" ? preventEventOverlap(other, candidate) : true;
41
+ });
42
+ return {
43
+ allowed: conflicts.length === 0,
44
+ conflicts
45
+ };
46
+ }
47
+ //#endregion
48
+ export { isEventPlacementAllowed };
49
+
50
+ //# sourceMappingURL=is-event-placement-allowed.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"is-event-placement-allowed.mjs","names":[],"sources":["../../../src/utils/is-event-placement-allowed/is-event-placement-allowed.ts"],"sourcesContent":["import dayjs from 'dayjs';\nimport { DateTimeStringValue, PreventEventOverlap, ScheduleEventData } from '../../types';\nimport { isEventsOverlap } from '../is-events-overlap/is-events-overlap';\n\nexport interface IsEventPlacementAllowedInput {\n /** Event that is being moved or resized */\n event: ScheduleEventData;\n\n /** Candidate start of the event */\n start: DateTimeStringValue;\n\n /** Candidate end of the event */\n end: DateTimeStringValue;\n\n /** Events the candidate range is checked against */\n events: ScheduleEventData[];\n\n /** If set, placements that overlap another event are rejected */\n preventEventOverlap?: PreventEventOverlap;\n\n /** Target resource, limits the check to events of that resource */\n resourceId?: string | number;\n}\n\nexport interface IsEventPlacementAllowedResult {\n /** True if the candidate range does not conflict with any event */\n allowed: boolean;\n\n /** Events the candidate range collided with */\n conflicts: ScheduleEventData[];\n}\n\nfunction isSameEvent(event: ScheduleEventData, other: ScheduleEventData) {\n if (event.id === other.id) {\n return true;\n }\n\n const eventSeriesId = event.recurringEventId ?? event.recurringInstance?.recurringEventId;\n const otherSeriesId = other.recurringEventId ?? other.recurringInstance?.recurringEventId;\n\n if (eventSeriesId === undefined || otherSeriesId === undefined) {\n return false;\n }\n\n const eventOccurrenceId = event.recurrenceId ?? event.recurringInstance?.recurrenceId;\n const otherOccurrenceId = other.recurrenceId ?? other.recurringInstance?.recurrenceId;\n\n return (\n eventSeriesId === otherSeriesId &&\n eventOccurrenceId !== undefined &&\n eventOccurrenceId === otherOccurrenceId\n );\n}\n\nfunction isMidnight(value: dayjs.Dayjs) {\n return value.hour() === 0 && value.minute() === 0 && value.second() === 0;\n}\n\nfunction spansWholeDays(event: ScheduleEventData) {\n const start = dayjs(event.start);\n const end = dayjs(event.end);\n\n if (!isMidnight(start)) {\n return false;\n }\n\n return isMidnight(end) || (end.hour() === 23 && end.minute() === 59 && end.second() === 59);\n}\n\nexport function isEventPlacementAllowed({\n event,\n start,\n end,\n events,\n preventEventOverlap,\n resourceId,\n}: IsEventPlacementAllowedInput): IsEventPlacementAllowedResult {\n if (!preventEventOverlap) {\n return { allowed: true, conflicts: [] };\n }\n\n const candidate = {\n ...event,\n start,\n end,\n ...(resourceId !== undefined ? { resourceId } : {}),\n } as ScheduleEventData;\n const candidateIsAllDay = spansWholeDays(candidate);\n const conflicts = events.filter((other) => {\n if (other.display === 'background' || isSameEvent(event, other)) {\n return false;\n }\n\n if (resourceId !== undefined && other.resourceId !== resourceId) {\n return false;\n }\n\n if (spansWholeDays(other) !== candidateIsAllDay) {\n return false;\n }\n\n if (!isEventsOverlap(other, candidate)) {\n return false;\n }\n\n return typeof preventEventOverlap === 'function' ? preventEventOverlap(other, candidate) : true;\n });\n\n return { allowed: conflicts.length === 0, conflicts };\n}\n"],"mappings":";;;;AAgCA,SAAS,YAAY,OAA0B,OAA0B;CACvE,IAAI,MAAM,OAAO,MAAM,IACrB,OAAO;CAGT,MAAM,gBAAgB,MAAM,oBAAoB,MAAM,mBAAmB;CACzE,MAAM,gBAAgB,MAAM,oBAAoB,MAAM,mBAAmB;CAEzE,IAAI,kBAAkB,KAAA,KAAa,kBAAkB,KAAA,GACnD,OAAO;CAGT,MAAM,oBAAoB,MAAM,gBAAgB,MAAM,mBAAmB;CACzE,MAAM,oBAAoB,MAAM,gBAAgB,MAAM,mBAAmB;CAEzE,OACE,kBAAkB,iBAClB,sBAAsB,KAAA,KACtB,sBAAsB;AAE1B;AAEA,SAAS,WAAW,OAAoB;CACtC,OAAO,MAAM,KAAK,MAAM,KAAK,MAAM,OAAO,MAAM,KAAK,MAAM,OAAO,MAAM;AAC1E;AAEA,SAAS,eAAe,OAA0B;CAChD,MAAM,QAAQ,MAAM,MAAM,KAAK;CAC/B,MAAM,MAAM,MAAM,MAAM,GAAG;CAE3B,IAAI,CAAC,WAAW,KAAK,GACnB,OAAO;CAGT,OAAO,WAAW,GAAG,KAAM,IAAI,KAAK,MAAM,MAAM,IAAI,OAAO,MAAM,MAAM,IAAI,OAAO,MAAM;AAC1F;AAEA,SAAgB,wBAAwB,EACtC,OACA,OACA,KACA,QACA,qBACA,cAC8D;CAC9D,IAAI,CAAC,qBACH,OAAO;EAAE,SAAS;EAAM,WAAW,CAAC;CAAE;CAGxC,MAAM,YAAY;EAChB,GAAG;EACH;EACA;EACA,GAAI,eAAe,KAAA,IAAY,EAAE,WAAW,IAAI,CAAC;CACnD;CACA,MAAM,oBAAoB,eAAe,SAAS;CAClD,MAAM,YAAY,OAAO,QAAQ,UAAU;EACzC,IAAI,MAAM,YAAY,gBAAgB,YAAY,OAAO,KAAK,GAC5D,OAAO;EAGT,IAAI,eAAe,KAAA,KAAa,MAAM,eAAe,YACnD,OAAO;EAGT,IAAI,eAAe,KAAK,MAAM,mBAC5B,OAAO;EAGT,IAAI,CAAC,gBAAgB,OAAO,SAAS,GACnC,OAAO;EAGT,OAAO,OAAO,wBAAwB,aAAa,oBAAoB,OAAO,SAAS,IAAI;CAC7F,CAAC;CAED,OAAO;EAAE,SAAS,UAAU,WAAW;EAAG;CAAU;AACtD"}
@@ -1,6 +1,6 @@
1
1
  import { BoxProps, DataAttributes, ElementProps, Factory, MantineRadius, ScrollAreaAutosizeProps, StylesApiProps } from '@mantine/core';
2
2
  import { ScheduleLabelsOverride } from '../../labels';
3
- import { AnyDateValue, DateLabelFormat, DateStringValue, DateTimeStringValue, ScheduleEventData, ScheduleEventOverlapMode, ScheduleMode, ScheduleViewLevel } from '../../types';
3
+ import { AnyDateValue, DateLabelFormat, DateStringValue, DateTimeStringValue, PreventEventOverlap, ScheduleCanDropEventData, ScheduleCanDropExternalEventData, ScheduleCanResizeEventToData, ScheduleEventData, ScheduleEventOverlapMode, ScheduleEventPlacementRejectedData, ScheduleMode, ScheduleViewLevel } from '../../types';
4
4
  import { AgendaViewStylesNames } from '../AgendaView/AgendaView';
5
5
  import { CurrentTimeIndicatorStylesNames } from '../CurrentTimeIndicator/CurrentTimeIndicator';
6
6
  import { MoreEventsProps, MoreEventsStylesNames } from '../MoreEvents/MoreEvents';
@@ -128,6 +128,16 @@ export interface DayViewProps extends BoxProps, StylesApiProps<DayViewFactory>,
128
128
  }) => void;
129
129
  /** Function to determine if event can be resized */
130
130
  canResizeEvent?: (event: ScheduleEventData) => boolean;
131
+ /** Called while an event is dragged over a target to compute live feedback and again before the drop is committed, return `false` to reject the drop. Must be pure and cheap. */
132
+ canDropEvent?: (data: ScheduleCanDropEventData) => boolean;
133
+ /** Called while an external item is dragged over a target to compute live feedback and again before the drop is committed, return `false` to reject the drop. Must be pure and cheap. Only `dataTransfer.types` can be read while the drag is in progress. */
134
+ canDropExternalEvent?: (data: ScheduleCanDropExternalEventData) => boolean;
135
+ /** Called on every pointer move while an event is resized to compute live feedback and again before the resize is committed, return `false` to reject the new size. Must be pure and cheap. */
136
+ canResizeEventTo?: (data: ScheduleCanResizeEventToData) => boolean;
137
+ /** If set, drops and resizes that would make the event overlap another event are rejected. Pass a function to decide per pair of events: return `true` to forbid the overlap. The function runs while dragging and resizing to compute live feedback, it must be pure and cheap. @default false */
138
+ preventEventOverlap?: PreventEventOverlap;
139
+ /** Called when a drop or resize is rejected */
140
+ onEventPlacementRejected?: (data: ScheduleEventPlacementRejectedData) => void;
131
141
  /** Snap step for resizing events, in minutes. Must divide evenly into an hour (e.g. `15`, `30`) or be a whole number of hours. When not set, `intervalMinutes` is used. @default intervalMinutes */
132
142
  eventResizeInterval?: number;
133
143
  /** Max number of generated recurring instances per recurring series @default 2000 */
@@ -1,6 +1,6 @@
1
1
  import { BoxProps, DataAttributes, ElementProps, Factory, MantineRadius, ScrollAreaProps, StylesApiProps } from '@mantine/core';
2
2
  import { ScheduleLabelsOverride } from '../../labels';
3
- import { DateLabelFormat, DateStringValue, DateTimeStringValue, DayOfWeek, ScheduleEventData, ScheduleMode, ScheduleViewLevel } from '../../types';
3
+ import { DateLabelFormat, DateStringValue, DateTimeStringValue, DayOfWeek, PreventEventOverlap, ScheduleCanDropEventData, ScheduleCanDropExternalEventData, ScheduleEventData, ScheduleEventPlacementRejectedData, ScheduleMode, ScheduleViewLevel } from '../../types';
4
4
  import { AgendaViewStylesNames } from '../AgendaView/AgendaView';
5
5
  import { MoreEventsProps } from '../MoreEvents/MoreEvents';
6
6
  import { RenderEvent, RenderEventBody } from '../ScheduleEvent/ScheduleEvent';
@@ -80,6 +80,14 @@ export interface MonthViewProps extends BoxProps, StylesApiProps<MonthViewFactor
80
80
  }) => void;
81
81
  /** Function to determine if event can be dragged */
82
82
  canDragEvent?: (event: ScheduleEventData) => boolean;
83
+ /** Called while an event is dragged over a target to compute live feedback and again before the drop is committed, return `false` to reject the drop. Must be pure and cheap. */
84
+ canDropEvent?: (data: ScheduleCanDropEventData) => boolean;
85
+ /** Called while an external item is dragged over a target to compute live feedback and again before the drop is committed, return `false` to reject the drop. Must be pure and cheap. Only `dataTransfer.types` can be read while the drag is in progress. */
86
+ canDropExternalEvent?: (data: ScheduleCanDropExternalEventData) => boolean;
87
+ /** If set, drops that would make the event overlap another event are rejected. Pass a function to decide per pair of events: return `true` to forbid the overlap. The function runs while dragging to compute live feedback, it must be pure and cheap. @default false */
88
+ preventEventOverlap?: PreventEventOverlap;
89
+ /** Called when a drop is rejected */
90
+ onEventPlacementRejected?: (data: ScheduleEventPlacementRejectedData) => void;
83
91
  /** Called when any event drag starts */
84
92
  onEventDragStart?: (event: ScheduleEventData) => void;
85
93
  /** Called when any event drag ends */
@@ -1,6 +1,6 @@
1
1
  import { BoxProps, DataAttributes, ElementProps, Factory, MantineRadius, ScrollAreaProps, StylesApiProps } from '@mantine/core';
2
2
  import { ScheduleLabelsOverride } from '../../labels';
3
- import { AnyDateValue, DateLabelFormat, DateStringValue, DateTimeStringValue, ScheduleEventData, ScheduleMode, ScheduleResourceData, ScheduleResourceGroup, ScheduleViewLevel } from '../../types';
3
+ import { AnyDateValue, DateLabelFormat, DateStringValue, DateTimeStringValue, PreventEventOverlap, ScheduleCanDropEventData, ScheduleCanDropExternalEventData, ScheduleCanResizeEventToData, ScheduleEventData, ScheduleEventPlacementRejectedData, ScheduleMode, ScheduleResourceData, ScheduleResourceGroup, ScheduleViewLevel } from '../../types';
4
4
  import { MoreEventsProps, MoreEventsStylesNames } from '../MoreEvents/MoreEvents';
5
5
  import { RenderEvent, RenderEventBody } from '../ScheduleEvent/ScheduleEvent';
6
6
  import { CombinedScheduleHeaderStylesNames } from '../ScheduleHeader/ScheduleHeader';
@@ -135,6 +135,16 @@ export interface ResourcesDayViewProps extends BoxProps, StylesApiProps<Resource
135
135
  }) => void;
136
136
  /** Function to determine if event can be resized */
137
137
  canResizeEvent?: (event: ScheduleEventData) => boolean;
138
+ /** Called while an event is dragged over a target to compute live feedback and again before the drop is committed, return `false` to reject the drop. Must be pure and cheap. */
139
+ canDropEvent?: (data: ScheduleCanDropEventData) => boolean;
140
+ /** Called while an external item is dragged over a target to compute live feedback and again before the drop is committed, return `false` to reject the drop. Must be pure and cheap. Only `dataTransfer.types` can be read while the drag is in progress. */
141
+ canDropExternalEvent?: (data: ScheduleCanDropExternalEventData) => boolean;
142
+ /** Called on every pointer move while an event is resized to compute live feedback and again before the resize is committed, return `false` to reject the new size. Must be pure and cheap. */
143
+ canResizeEventTo?: (data: ScheduleCanResizeEventToData) => boolean;
144
+ /** If set, drops and resizes that would make the event overlap another event are rejected. Pass a function to decide per pair of events: return `true` to forbid the overlap. The function runs while dragging and resizing to compute live feedback, it must be pure and cheap. @default false */
145
+ preventEventOverlap?: PreventEventOverlap;
146
+ /** Called when a drop or resize is rejected */
147
+ onEventPlacementRejected?: (data: ScheduleEventPlacementRejectedData) => void;
138
148
  /** Snap step for resizing events, in minutes. Must divide evenly into an hour (e.g. `15`, `30`) or be a whole number of hours. When not set, `intervalMinutes` is used. @default intervalMinutes */
139
149
  eventResizeInterval?: number;
140
150
  /** Max number of generated recurring instances per recurring series @default 2000 */
@@ -1,6 +1,6 @@
1
1
  import { BoxProps, DataAttributes, ElementProps, Factory, MantineRadius, ScrollAreaProps, StylesApiProps } from '@mantine/core';
2
2
  import { ScheduleLabelsOverride } from '../../labels';
3
- import { DateLabelFormat, DateStringValue, DateTimeStringValue, DayOfWeek, ScheduleEventData, ScheduleMode, ScheduleResourceData, ScheduleResourceGroup, ScheduleViewLevel } from '../../types';
3
+ import { DateLabelFormat, DateStringValue, DateTimeStringValue, DayOfWeek, PreventEventOverlap, ScheduleCanDropEventData, ScheduleCanDropExternalEventData, ScheduleCanResizeEventToData, ScheduleEventData, ScheduleEventPlacementRejectedData, ScheduleMode, ScheduleResourceData, ScheduleResourceGroup, ScheduleViewLevel } from '../../types';
4
4
  import { MoreEventsProps, MoreEventsStylesNames } from '../MoreEvents/MoreEvents';
5
5
  import { RenderEvent, RenderEventBody } from '../ScheduleEvent/ScheduleEvent';
6
6
  import { MonthYearSelectProps } from '../ScheduleHeader/MonthYearSelect/MonthYearSelect';
@@ -97,6 +97,16 @@ export interface ResourcesMonthViewProps extends BoxProps, StylesApiProps<Resour
97
97
  }) => void;
98
98
  /** Function to determine if event can be resized */
99
99
  canResizeEvent?: (event: ScheduleEventData) => boolean;
100
+ /** Called while an event is dragged over a target to compute live feedback and again before the drop is committed, return `false` to reject the drop. Must be pure and cheap. */
101
+ canDropEvent?: (data: ScheduleCanDropEventData) => boolean;
102
+ /** Called while an external item is dragged over a target to compute live feedback and again before the drop is committed, return `false` to reject the drop. Must be pure and cheap. Only `dataTransfer.types` can be read while the drag is in progress. */
103
+ canDropExternalEvent?: (data: ScheduleCanDropExternalEventData) => boolean;
104
+ /** Called on every pointer move while an event is resized to compute live feedback and again before the resize is committed, return `false` to reject the new size. Must be pure and cheap. */
105
+ canResizeEventTo?: (data: ScheduleCanResizeEventToData) => boolean;
106
+ /** If set, drops and resizes that would make the event overlap another event are rejected. Pass a function to decide per pair of events: return `true` to forbid the overlap. The function runs while dragging and resizing to compute live feedback, it must be pure and cheap. @default false */
107
+ preventEventOverlap?: PreventEventOverlap;
108
+ /** Called when a drop or resize is rejected */
109
+ onEventPlacementRejected?: (data: ScheduleEventPlacementRejectedData) => void;
100
110
  /** Called when event is clicked */
101
111
  onEventClick?: (event: ScheduleEventData, e: React.MouseEvent<HTMLButtonElement>) => void;
102
112
  /** If set, enables drag-to-select day ranges @default false */
@@ -1,13 +1,13 @@
1
1
  import { BoxProps, ElementProps, Factory, MantineRadius, StylesApiProps } from '@mantine/core';
2
2
  import { ScheduleLabelsOverride } from '../../labels';
3
- import { DateStringValue, DateTimeStringValue, ScheduleEventData, ScheduleMode, ScheduleResourceData } from '../../types';
3
+ import { DateStringValue, DateTimeStringValue, PreventEventOverlap, ScheduleCanDropEventData, ScheduleCanDropExternalEventData, ScheduleCanResizeEventToData, ScheduleEventData, ScheduleEventPlacementRejectedData, ScheduleMode, ScheduleResourceData } from '../../types';
4
4
  import { ResourcesDayViewProps, ResourcesDayViewStylesNames } from '../ResourcesDayView/ResourcesDayView';
5
5
  import { ResourcesMonthViewProps, ResourcesMonthViewStylesNames } from '../ResourcesMonthView/ResourcesMonthView';
6
6
  import { ResourcesWeekViewProps, ResourcesWeekViewStylesNames } from '../ResourcesWeekView/ResourcesWeekView';
7
7
  import { RenderEventBody } from '../ScheduleEvent/ScheduleEvent';
8
8
  export type ResourcesScheduleStylesNames = 'root' | ResourcesDayViewStylesNames | ResourcesWeekViewStylesNames | ResourcesMonthViewStylesNames;
9
9
  export type ResourcesScheduleViewLevel = 'day' | 'week' | 'month';
10
- type ResourcesScheduleCommonProps = 'date' | 'onDateChange' | 'resources' | 'events' | 'locale' | 'radius' | 'labels' | 'renderEventBody' | 'renderResourceLabel' | 'withEventsDragAndDrop' | 'onEventDrop' | 'canDragEvent' | 'onEventDragStart' | 'onEventDragEnd' | 'onTimeSlotClick' | 'onEventClick' | 'onDayClick' | 'withDragSlotSelect' | 'onSlotDragEnd' | 'view' | 'onViewChange' | 'mode' | 'onExternalEventDrop' | 'withEventResize' | 'onEventResize' | 'canResizeEvent' | 'recurrenceExpansionLimit' | 'withInteractiveBackgroundEvents';
10
+ type ResourcesScheduleCommonProps = 'date' | 'onDateChange' | 'resources' | 'events' | 'locale' | 'radius' | 'labels' | 'renderEventBody' | 'renderResourceLabel' | 'withEventsDragAndDrop' | 'onEventDrop' | 'canDragEvent' | 'onEventDragStart' | 'onEventDragEnd' | 'onTimeSlotClick' | 'onEventClick' | 'onDayClick' | 'withDragSlotSelect' | 'onSlotDragEnd' | 'view' | 'onViewChange' | 'mode' | 'onExternalEventDrop' | 'withEventResize' | 'onEventResize' | 'canResizeEvent' | 'canDropEvent' | 'canDropExternalEvent' | 'canResizeEventTo' | 'preventEventOverlap' | 'onEventPlacementRejected' | 'recurrenceExpansionLimit' | 'withInteractiveBackgroundEvents';
11
11
  type ResourcesScheduleViewProps<T> = Partial<Omit<T, ResourcesScheduleCommonProps>>;
12
12
  export interface ResourcesScheduleProps extends BoxProps, StylesApiProps<ResourcesScheduleFactory>, ElementProps<'div'> {
13
13
  __staticSelector?: string;
@@ -95,6 +95,16 @@ export interface ResourcesScheduleProps extends BoxProps, StylesApiProps<Resourc
95
95
  }) => void;
96
96
  /** Function to determine if event can be resized */
97
97
  canResizeEvent?: (event: ScheduleEventData) => boolean;
98
+ /** Called while an event is dragged over a target to compute live feedback and again before the drop is committed, return `false` to reject the drop. Must be pure and cheap. */
99
+ canDropEvent?: (data: ScheduleCanDropEventData) => boolean;
100
+ /** Called while an external item is dragged over a target to compute live feedback and again before the drop is committed, return `false` to reject the drop. Must be pure and cheap. Only `dataTransfer.types` can be read while the drag is in progress. */
101
+ canDropExternalEvent?: (data: ScheduleCanDropExternalEventData) => boolean;
102
+ /** Called on every pointer move while an event is resized to compute live feedback and again before the resize is committed, return `false` to reject the new size. Must be pure and cheap. */
103
+ canResizeEventTo?: (data: ScheduleCanResizeEventToData) => boolean;
104
+ /** If set, drops and resizes that would make the event overlap another event are rejected. Pass a function to decide per pair of events: return `true` to forbid the overlap. The function runs while dragging and resizing to compute live feedback, it must be pure and cheap. @default false */
105
+ preventEventOverlap?: PreventEventOverlap;
106
+ /** Called when a drop or resize is rejected */
107
+ onEventPlacementRejected?: (data: ScheduleEventPlacementRejectedData) => void;
98
108
  /** If set, background events (`display: 'background'`) can be clicked and trigger `onEventClick`. Applies to `day` and `week` views only – `ResourcesMonthView` does not render background events separately. Combined with `withEventResize`, timed background events can also be resized by dragging their edges. @default false */
99
109
  withInteractiveBackgroundEvents?: boolean;
100
110
  /** Max number of generated recurring instances @default 2000 */
@@ -1,6 +1,6 @@
1
1
  import { BoxProps, DataAttributes, ElementProps, Factory, MantineRadius, ScrollAreaProps, StylesApiProps } from '@mantine/core';
2
2
  import { ScheduleLabelsOverride } from '../../labels';
3
- import { AnyDateValue, DateLabelFormat, DateStringValue, DateTimeStringValue, DayOfWeek, ScheduleEventData, ScheduleMode, ScheduleResourceData, ScheduleResourceGroup, ScheduleViewLevel } from '../../types';
3
+ import { AnyDateValue, DateLabelFormat, DateStringValue, DateTimeStringValue, DayOfWeek, PreventEventOverlap, ScheduleCanDropEventData, ScheduleCanDropExternalEventData, ScheduleCanResizeEventToData, ScheduleEventData, ScheduleEventPlacementRejectedData, ScheduleMode, ScheduleResourceData, ScheduleResourceGroup, ScheduleViewLevel } from '../../types';
4
4
  import { MoreEventsProps, MoreEventsStylesNames } from '../MoreEvents/MoreEvents';
5
5
  import { RenderEvent, RenderEventBody } from '../ScheduleEvent/ScheduleEvent';
6
6
  import { CombinedScheduleHeaderStylesNames } from '../ScheduleHeader/ScheduleHeader';
@@ -101,6 +101,16 @@ export interface ResourcesWeekViewProps extends BoxProps, StylesApiProps<Resourc
101
101
  }) => void;
102
102
  /** Function to determine if event can be resized */
103
103
  canResizeEvent?: (event: ScheduleEventData) => boolean;
104
+ /** Called while an event is dragged over a target to compute live feedback and again before the drop is committed, return `false` to reject the drop. Must be pure and cheap. */
105
+ canDropEvent?: (data: ScheduleCanDropEventData) => boolean;
106
+ /** Called while an external item is dragged over a target to compute live feedback and again before the drop is committed, return `false` to reject the drop. Must be pure and cheap. Only `dataTransfer.types` can be read while the drag is in progress. */
107
+ canDropExternalEvent?: (data: ScheduleCanDropExternalEventData) => boolean;
108
+ /** Called on every pointer move while an event is resized to compute live feedback and again before the resize is committed, return `false` to reject the new size. Must be pure and cheap. */
109
+ canResizeEventTo?: (data: ScheduleCanResizeEventToData) => boolean;
110
+ /** If set, drops and resizes that would make the event overlap another event are rejected. Pass a function to decide per pair of events: return `true` to forbid the overlap. The function runs while dragging and resizing to compute live feedback, it must be pure and cheap. @default false */
111
+ preventEventOverlap?: PreventEventOverlap;
112
+ /** Called when a drop or resize is rejected */
113
+ onEventPlacementRejected?: (data: ScheduleEventPlacementRejectedData) => void;
104
114
  /** Snap step for resizing events, in minutes. Must divide evenly into an hour (e.g. `15`, `30`) or be a whole number of hours. When not set, `intervalMinutes` is used. @default intervalMinutes */
105
115
  eventResizeInterval?: number;
106
116
  recurrenceExpansionLimit?: number;
@@ -1,6 +1,6 @@
1
1
  import { BoxProps, ElementProps, Factory, MantineRadius, StylesApiProps } from '@mantine/core';
2
2
  import { ScheduleLabelsOverride } from '../../labels';
3
- import { DateStringValue, DateTimeStringValue, ScheduleEventData, ScheduleMode, ScheduleViewLevel } from '../../types';
3
+ import { DateStringValue, DateTimeStringValue, PreventEventOverlap, ScheduleCanDropEventData, ScheduleCanDropExternalEventData, ScheduleCanResizeEventToData, ScheduleEventData, ScheduleEventPlacementRejectedData, ScheduleMode, ScheduleViewLevel } from '../../types';
4
4
  import { DayViewProps, DayViewStylesNames } from '../DayView/DayView';
5
5
  import { MobileMonthViewProps, MobileMonthViewStylesNames } from '../MobileMonthView/MobileMonthView';
6
6
  import { MonthViewProps, MonthViewStylesNames } from '../MonthView/MonthView';
@@ -9,7 +9,7 @@ import { WeekViewProps, WeekViewStylesNames } from '../WeekView/WeekView';
9
9
  import { YearViewProps, YearViewStylesNames } from '../YearView/YearView';
10
10
  export type ScheduleStylesNames = 'root' | 'desktopView' | 'mobileView' | DayViewStylesNames | WeekViewStylesNames | MonthViewStylesNames | YearViewStylesNames | MobileMonthViewStylesNames;
11
11
  export type ScheduleLayout = 'default' | 'responsive';
12
- type ScheduleCommonProps = 'date' | 'onDateChange' | 'events' | 'locale' | 'radius' | 'labels' | 'renderEventBody' | 'withEventsDragAndDrop' | 'onEventDrop' | 'canDragEvent' | 'onEventDragStart' | 'onEventDragEnd' | 'onTimeSlotClick' | 'onAllDaySlotClick' | 'onEventClick' | 'onDayClick' | 'onMonthClick' | 'withDragSlotSelect' | 'onSlotDragEnd' | 'view' | 'onViewChange' | 'mode' | 'withAgenda' | 'onExternalEventDrop' | 'withEventResize' | 'onEventResize' | 'canResizeEvent' | 'recurrenceExpansionLimit' | 'withInteractiveBackgroundEvents';
12
+ type ScheduleCommonProps = 'date' | 'onDateChange' | 'events' | 'locale' | 'radius' | 'labels' | 'renderEventBody' | 'withEventsDragAndDrop' | 'onEventDrop' | 'canDragEvent' | 'onEventDragStart' | 'onEventDragEnd' | 'onTimeSlotClick' | 'onAllDaySlotClick' | 'onEventClick' | 'onDayClick' | 'onMonthClick' | 'withDragSlotSelect' | 'onSlotDragEnd' | 'view' | 'onViewChange' | 'mode' | 'withAgenda' | 'onExternalEventDrop' | 'withEventResize' | 'onEventResize' | 'canResizeEvent' | 'canDropEvent' | 'canDropExternalEvent' | 'canResizeEventTo' | 'preventEventOverlap' | 'onEventPlacementRejected' | 'recurrenceExpansionLimit' | 'withInteractiveBackgroundEvents';
13
13
  type ScheduleViewProps<T> = Partial<Omit<T, ScheduleCommonProps>>;
14
14
  export interface ScheduleProps extends BoxProps, StylesApiProps<ScheduleFactory>, ElementProps<'div'> {
15
15
  __staticSelector?: string;
@@ -84,6 +84,16 @@ export interface ScheduleProps extends BoxProps, StylesApiProps<ScheduleFactory>
84
84
  }) => void;
85
85
  /** Function to determine if event can be resized */
86
86
  canResizeEvent?: (event: ScheduleEventData) => boolean;
87
+ /** Called while an event is dragged over a target to compute live feedback and again before the drop is committed, return `false` to reject the drop. Must be pure and cheap. */
88
+ canDropEvent?: (data: ScheduleCanDropEventData) => boolean;
89
+ /** Called while an external item is dragged over a target to compute live feedback and again before the drop is committed, return `false` to reject the drop. Must be pure and cheap. Only `dataTransfer.types` can be read while the drag is in progress. */
90
+ canDropExternalEvent?: (data: ScheduleCanDropExternalEventData) => boolean;
91
+ /** Called on every pointer move while an event is resized to compute live feedback and again before the resize is committed, return `false` to reject the new size. Must be pure and cheap. */
92
+ canResizeEventTo?: (data: ScheduleCanResizeEventToData) => boolean;
93
+ /** If set, drops and resizes that would make the event overlap another event are rejected. Pass a function to decide per pair of events: return `true` to forbid the overlap. The function runs while dragging and resizing to compute live feedback, it must be pure and cheap. @default false */
94
+ preventEventOverlap?: PreventEventOverlap;
95
+ /** Called when a drop or resize is rejected */
96
+ onEventPlacementRejected?: (data: ScheduleEventPlacementRejectedData) => void;
87
97
  /** If set, background events (`display: 'background'`) can be clicked and trigger `onEventClick`. Applies to `day`, `week` and `month` views only – `YearView` and the `MobileMonthView` used by `layout="responsive"` on small screens do not render background events at all. Combined with `withEventResize`, timed background events in `day` and `week` views can also be resized by dragging their edges. @default false */
88
98
  withInteractiveBackgroundEvents?: boolean;
89
99
  /** Max number of generated recurring instances per recurring series @default 2000 */
@@ -1,6 +1,6 @@
1
1
  import { BoxProps, DataAttributes, ElementProps, Factory, MantineRadius, ScrollAreaAutosizeProps, StylesApiProps } from '@mantine/core';
2
2
  import { ScheduleLabelsOverride } from '../../labels';
3
- import { AnyDateValue, DateLabelFormat, DateStringValue, DateTimeStringValue, DayOfWeek, ScheduleEventData, ScheduleEventOverlapMode, ScheduleMode, ScheduleViewLevel } from '../../types';
3
+ import { AnyDateValue, DateLabelFormat, DateStringValue, DateTimeStringValue, DayOfWeek, PreventEventOverlap, ScheduleCanDropEventData, ScheduleCanDropExternalEventData, ScheduleCanResizeEventToData, ScheduleEventData, ScheduleEventOverlapMode, ScheduleEventPlacementRejectedData, ScheduleMode, ScheduleViewLevel } from '../../types';
4
4
  import { BusinessHoursValue } from '../../utils';
5
5
  import { AgendaViewStylesNames } from '../AgendaView/AgendaView';
6
6
  import { CurrentTimeIndicatorStylesNames } from '../CurrentTimeIndicator/CurrentTimeIndicator';
@@ -144,6 +144,16 @@ export interface WeekViewProps extends BoxProps, StylesApiProps<WeekViewFactory>
144
144
  }) => void;
145
145
  /** Function to determine if event can be resized */
146
146
  canResizeEvent?: (event: ScheduleEventData) => boolean;
147
+ /** Called while an event is dragged over a target to compute live feedback and again before the drop is committed, return `false` to reject the drop. Must be pure and cheap. */
148
+ canDropEvent?: (data: ScheduleCanDropEventData) => boolean;
149
+ /** Called while an external item is dragged over a target to compute live feedback and again before the drop is committed, return `false` to reject the drop. Must be pure and cheap. Only `dataTransfer.types` can be read while the drag is in progress. */
150
+ canDropExternalEvent?: (data: ScheduleCanDropExternalEventData) => boolean;
151
+ /** Called on every pointer move while an event is resized to compute live feedback and again before the resize is committed, return `false` to reject the new size. Must be pure and cheap. */
152
+ canResizeEventTo?: (data: ScheduleCanResizeEventToData) => boolean;
153
+ /** If set, drops and resizes that would make the event overlap another event are rejected. Pass a function to decide per pair of events: return `true` to forbid the overlap. The function runs while dragging and resizing to compute live feedback, it must be pure and cheap. @default false */
154
+ preventEventOverlap?: PreventEventOverlap;
155
+ /** Called when a drop or resize is rejected */
156
+ onEventPlacementRejected?: (data: ScheduleEventPlacementRejectedData) => void;
147
157
  /** Snap step for resizing events, in minutes. Must divide evenly into an hour (e.g. `15`, `30`) or be a whole number of hours. When not set, `intervalMinutes` is used. @default intervalMinutes */
148
158
  eventResizeInterval?: number;
149
159
  /** Max number of generated recurring instances per recurring series @default 2000 */
@@ -1,6 +1,7 @@
1
1
  import { DateStringValue, DateTimeStringValue, ScheduleEventData, ScheduleMode } from '../types';
2
2
  import { MonthResizeEdge } from '../utils/calculate-month-resize-dates/calculate-month-resize-dates';
3
- export interface UseDayGridEventResizeInput {
3
+ import { EventResizeValidationOptions } from './validate-event-resize';
4
+ export interface UseDayGridEventResizeInput extends EventResizeValidationOptions {
4
5
  enabled?: boolean;
5
6
  mode?: ScheduleMode;
6
7
  onEventResize?: (data: {
@@ -23,7 +24,7 @@ export interface DayGridResizeStartInput {
23
24
  * The target day is resolved by hit-testing `cells` with the pointer position, so
24
25
  * the hook works for any layout that renders one element per day in a single row.
25
26
  */
26
- export declare function useDayGridEventResize({ enabled, mode, onEventResize, canResizeEvent, }: UseDayGridEventResizeInput): {
27
+ export declare function useDayGridEventResize({ enabled, mode, onEventResize, canResizeEvent, events, preventEventOverlap, canResizeEventTo, onEventPlacementRejected, }: UseDayGridEventResizeInput): {
27
28
  handleResizeStart: ({ event, edge, cells, days, pointerEvent }: DayGridResizeStartInput) => void;
28
29
  isResizing: boolean;
29
30
  resizingEventId: string | number | null;
@@ -32,4 +33,5 @@ export declare function useDayGridEventResize({ enabled, mode, onEventResize, ca
32
33
  previewEnd: string | null;
33
34
  isResizableEvent: (event: ScheduleEventData) => boolean;
34
35
  wasResizing: () => boolean;
36
+ resizeValid: boolean;
35
37
  };
@@ -1,5 +1,5 @@
1
1
  import { DragContextValue } from '../components/DragContext/DragContext';
2
- import { DateTimeStringValue, ScheduleEventData, ScheduleMode } from '../types';
2
+ import { DateTimeStringValue, PreventEventOverlap, ScheduleCanDropEventData, ScheduleCanDropExternalEventData, ScheduleEventData, ScheduleEventPlacementRejectedData, ScheduleMode } from '../types';
3
3
  export interface DragPreview<T = any> {
4
4
  start: DateTimeStringValue;
5
5
  end: DateTimeStringValue;
@@ -33,6 +33,20 @@ export interface UseDragDropHandlersOptions<T = any> {
33
33
  };
34
34
  /** Called when an external item is dropped onto the schedule */
35
35
  onExternalDrop?: (e: React.DragEvent, target: T) => void;
36
+ /** Events the candidate range is checked against when `preventEventOverlap` is set */
37
+ events?: ScheduleEventData[];
38
+ /** If set, drops that would make the event overlap another event are rejected */
39
+ preventEventOverlap?: PreventEventOverlap;
40
+ /** Called on every `dragover` to compute live feedback and again before the drop is committed, return `false` to reject the drop */
41
+ canDropEvent?: (data: ScheduleCanDropEventData) => boolean;
42
+ /** Called on every `dragover` of an external item to compute live feedback and again before the drop is committed, return `false` to reject the drop */
43
+ canDropExternalEvent?: (data: ScheduleCanDropExternalEventData) => boolean;
44
+ /** Called when a drop is rejected */
45
+ onEventPlacementRejected?: (data: ScheduleEventPlacementRejectedData) => void;
46
+ /** Resolves the target resource from the drop target, used by `Resources*` views */
47
+ getTargetResourceId?: (target: T) => string | number | undefined;
48
+ /** Resolves the datetime an external item would be dropped at, `null` when the target has no datetime */
49
+ getExternalDropDateTime?: (target: T) => DateTimeStringValue | null;
36
50
  }
37
51
  export interface DragDropHandlers<T = any> {
38
52
  /** Context value for DragContext.Provider */
@@ -57,6 +71,8 @@ export interface DragDropHandlers<T = any> {
57
71
  dragPreview: DragPreview<T> | null;
58
72
  /** Sets the current drag preview */
59
73
  setDragPreview: (preview: DragPreview<T> | null) => void;
74
+ /** False while the pointer is over a target that would be rejected */
75
+ dropValid: boolean;
60
76
  }
61
77
  /**
62
78
  * Hook that provides unified drag-drop handlers for Schedule views.