@mantine/schedule 9.6.0 → 9.6.2
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.
- package/cjs/components/DayView/DayView.cjs +48 -20
- package/cjs/components/DayView/DayView.cjs.map +1 -1
- package/cjs/components/DayView/DayView.module.cjs +3 -2
- package/cjs/components/DayView/DayView.module.cjs.map +1 -1
- package/cjs/components/DayView/get-day-view-events/get-day-positioned-events.cjs +12 -7
- package/cjs/components/DayView/get-day-view-events/get-day-positioned-events.cjs.map +1 -1
- package/cjs/components/DayView/get-day-view-events/get-day-view-events.cjs +3 -2
- package/cjs/components/DayView/get-day-view-events/get-day-view-events.cjs.map +1 -1
- package/cjs/components/MobileMonthView/MobileMonthView.cjs +1 -1
- package/cjs/components/MobileMonthView/MobileMonthView.cjs.map +1 -1
- package/cjs/components/ResourcesDayView/ResourcesDayView.cjs +40 -15
- package/cjs/components/ResourcesDayView/ResourcesDayView.cjs.map +1 -1
- package/cjs/components/ResourcesDayView/ResourcesDayView.module.cjs.map +1 -1
- package/cjs/components/ResourcesSchedule/ResourcesSchedule.cjs.map +1 -1
- package/cjs/components/ResourcesWeekView/ResourcesWeekView.cjs +46 -16
- package/cjs/components/ResourcesWeekView/ResourcesWeekView.cjs.map +1 -1
- package/cjs/components/ResourcesWeekView/ResourcesWeekView.module.cjs.map +1 -1
- package/cjs/components/Schedule/Schedule.cjs.map +1 -1
- package/cjs/components/ScheduleBackgroundEvent/ScheduleBackgroundEvent.cjs +18 -3
- package/cjs/components/ScheduleBackgroundEvent/ScheduleBackgroundEvent.cjs.map +1 -1
- package/cjs/components/ScheduleEvent/ScheduleEvent.module.cjs.map +1 -1
- package/cjs/components/WeekView/WeekView.cjs +47 -17
- package/cjs/components/WeekView/WeekView.cjs.map +1 -1
- package/cjs/components/WeekView/WeekView.module.cjs +3 -2
- package/cjs/components/WeekView/WeekView.module.cjs.map +1 -1
- package/cjs/components/WeekView/get-week-view-events/get-week-positioned-events.cjs +4 -2
- package/cjs/components/WeekView/get-week-view-events/get-week-positioned-events.cjs.map +1 -1
- package/cjs/hooks/use-event-resize.cjs +7 -4
- package/cjs/hooks/use-event-resize.cjs.map +1 -1
- package/cjs/hooks/use-horizontal-event-resize.cjs +7 -4
- package/cjs/hooks/use-horizontal-event-resize.cjs.map +1 -1
- package/cjs/index.cjs +4 -0
- package/cjs/utils/apply-cascade-layout/apply-cascade-layout.cjs +21 -0
- package/cjs/utils/apply-cascade-layout/apply-cascade-layout.cjs.map +1 -0
- package/cjs/{components/ResourcesDayView → utils}/get-overlap-clusters/get-overlap-clusters.cjs +2 -2
- package/cjs/utils/get-overlap-clusters/get-overlap-clusters.cjs.map +1 -0
- package/cjs/utils/is-event-in-time-range/is-event-in-time-range.cjs +2 -1
- package/cjs/utils/is-event-in-time-range/is-event-in-time-range.cjs.map +1 -1
- package/esm/components/DayView/DayView.mjs +48 -20
- package/esm/components/DayView/DayView.mjs.map +1 -1
- package/esm/components/DayView/DayView.module.mjs +3 -2
- package/esm/components/DayView/DayView.module.mjs.map +1 -1
- package/esm/components/DayView/get-day-view-events/get-day-positioned-events.mjs +12 -7
- package/esm/components/DayView/get-day-view-events/get-day-positioned-events.mjs.map +1 -1
- package/esm/components/DayView/get-day-view-events/get-day-view-events.mjs +3 -2
- package/esm/components/DayView/get-day-view-events/get-day-view-events.mjs.map +1 -1
- package/esm/components/MobileMonthView/MobileMonthView.mjs +1 -1
- package/esm/components/MobileMonthView/MobileMonthView.mjs.map +1 -1
- package/esm/components/ResourcesDayView/ResourcesDayView.mjs +40 -15
- package/esm/components/ResourcesDayView/ResourcesDayView.mjs.map +1 -1
- package/esm/components/ResourcesDayView/ResourcesDayView.module.mjs.map +1 -1
- package/esm/components/ResourcesSchedule/ResourcesSchedule.mjs.map +1 -1
- package/esm/components/ResourcesWeekView/ResourcesWeekView.mjs +46 -16
- package/esm/components/ResourcesWeekView/ResourcesWeekView.mjs.map +1 -1
- package/esm/components/ResourcesWeekView/ResourcesWeekView.module.mjs.map +1 -1
- package/esm/components/Schedule/Schedule.mjs.map +1 -1
- package/esm/components/ScheduleBackgroundEvent/ScheduleBackgroundEvent.mjs +19 -4
- package/esm/components/ScheduleBackgroundEvent/ScheduleBackgroundEvent.mjs.map +1 -1
- package/esm/components/ScheduleEvent/ScheduleEvent.module.mjs.map +1 -1
- package/esm/components/WeekView/WeekView.mjs +47 -17
- package/esm/components/WeekView/WeekView.mjs.map +1 -1
- package/esm/components/WeekView/WeekView.module.mjs +3 -2
- package/esm/components/WeekView/WeekView.module.mjs.map +1 -1
- package/esm/components/WeekView/get-week-view-events/get-week-positioned-events.mjs +4 -2
- package/esm/components/WeekView/get-week-view-events/get-week-positioned-events.mjs.map +1 -1
- package/esm/hooks/use-event-resize.mjs +7 -4
- package/esm/hooks/use-event-resize.mjs.map +1 -1
- package/esm/hooks/use-horizontal-event-resize.mjs +7 -4
- package/esm/hooks/use-horizontal-event-resize.mjs.map +1 -1
- package/esm/index.mjs +3 -1
- package/esm/utils/apply-cascade-layout/apply-cascade-layout.mjs +21 -0
- package/esm/utils/apply-cascade-layout/apply-cascade-layout.mjs.map +1 -0
- package/esm/{components/ResourcesDayView → utils}/get-overlap-clusters/get-overlap-clusters.mjs +2 -2
- package/esm/utils/get-overlap-clusters/get-overlap-clusters.mjs.map +1 -0
- package/esm/utils/is-event-in-time-range/is-event-in-time-range.mjs +2 -1
- package/esm/utils/is-event-in-time-range/is-event-in-time-range.mjs.map +1 -1
- package/lib/components/DayView/DayView.d.ts +7 -3
- package/lib/components/DayView/get-day-view-events/get-day-positioned-events.d.ts +4 -2
- package/lib/components/DayView/get-day-view-events/get-day-view-events.d.ts +3 -2
- package/lib/components/ResourcesDayView/ResourcesDayView.d.ts +1 -1
- package/lib/components/ResourcesSchedule/ResourcesSchedule.d.ts +1 -1
- package/lib/components/ResourcesWeekView/ResourcesWeekView.d.ts +1 -1
- package/lib/components/Schedule/Schedule.d.ts +1 -1
- package/lib/components/ScheduleBackgroundEvent/ScheduleBackgroundEvent.d.ts +15 -2
- package/lib/components/WeekView/WeekView.d.ts +7 -3
- package/lib/components/WeekView/get-week-view-events/get-week-positioned-events.d.ts +4 -2
- package/lib/hooks/use-event-resize.d.ts +3 -2
- package/lib/hooks/use-horizontal-event-resize.d.ts +3 -2
- package/lib/types.d.ts +2 -0
- package/lib/utils/apply-cascade-layout/apply-cascade-layout.d.ts +6 -0
- package/lib/utils/get-overlap-clusters/get-overlap-clusters.d.ts +2 -0
- package/lib/utils/index.d.ts +3 -0
- package/package.json +4 -4
- package/styles.css +315 -6
- package/styles.layer.css +315 -6
- package/cjs/components/ResourcesDayView/get-overlap-clusters/get-overlap-clusters.cjs.map +0 -1
- package/esm/components/ResourcesDayView/get-overlap-clusters/get-overlap-clusters.mjs.map +0 -1
- package/lib/components/ResourcesDayView/get-overlap-clusters/get-overlap-clusters.d.ts +0 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-event-resize.mjs","names":[],"sources":["../../src/hooks/use-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 { parseTimeString } from '../utils/parse-time-string/parse-time-string';\n\ntype ResizeEdge = 'top' | 'bottom';\n\ninterface ResizeState {\n eventId: string | number;\n event: ScheduleEventData;\n edge: ResizeEdge;\n container: HTMLElement;\n originalTop: number;\n originalHeight: number;\n currentTop: number;\n currentHeight: number;\n eventDate: string;\n originalStart: DateTimeStringValue;\n originalEnd: DateTimeStringValue;\n}\n\nexport interface UseEventResizeInput {\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}\n\nexport function useEventResize({\n enabled = false,\n mode = 'default',\n startTime,\n endTime,\n intervalMinutes,\n resizeIntervalMinutes,\n onEventResize,\n canResizeEvent,\n}: UseEventResizeInput) {\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 // Snapping happens in absolute minutes from midnight – the same grid `calculateDropTime`\n // uses for drag and drop. Snapping relative to `startTime` would put resized edges on a\n // different grid than dragged events whenever `startTime` is not a multiple of the interval\n // (with `startTime=\"08:10\"` and a 30 minute step, drops land on :00/:30 but resizes on\n // :10/:40, so the two operations could never align an event).\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 originalTop,\n originalHeight,\n eventDate,\n pointerEvent,\n }: {\n event: ScheduleEventData;\n edge: ResizeEdge;\n container: HTMLElement;\n originalTop: number;\n originalHeight: number;\n eventDate: string;\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 originalTop,\n originalHeight,\n currentTop: originalTop,\n currentHeight: originalHeight,\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 };\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 document.body.style.userSelect = 'none';\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 relativeY = e.clientY - containerRect.top;\n const rawPercent = Math.max(0, Math.min(100, (relativeY / containerRect.height) * 100));\n const draggedMinutes = clampAndSnap(percentToMinutes(rawPercent));\n\n let newTop = state.originalTop;\n let newHeight = state.originalHeight;\n\n if (state.edge === 'bottom') {\n const topMinutes = percentToMinutes(state.originalTop);\n const minEndMinutes = snapEdgeMinutes(topMinutes + clampedResizeInterval, 'up');\n const endMinutes = Math.max(draggedMinutes, minEndMinutes);\n newHeight = Math.max(0, minutesToPercent(endMinutes) - state.originalTop);\n } else {\n const bottomMinutes = percentToMinutes(state.originalTop + state.originalHeight);\n const maxStartMinutes = snapEdgeMinutes(bottomMinutes - clampedResizeInterval, 'down');\n const startMinutesValue = Math.min(draggedMinutes, maxStartMinutes);\n newTop = minutesToPercent(startMinutesValue);\n newHeight = Math.max(0, state.originalTop + state.originalHeight - newTop);\n }\n\n resizeRef.current = { ...state, currentTop: newTop, currentHeight: newHeight };\n setResizeState(resizeRef.current);\n };\n\n const handlePointerUp = () => {\n const state = resizeRef.current;\n if (state) {\n if (\n state.currentTop !== state.originalTop ||\n state.currentHeight !== state.originalHeight\n ) {\n let newStart: DateTimeStringValue;\n let newEnd: DateTimeStringValue;\n\n if (state.edge === 'top') {\n newStart = percentToDateTime(state.currentTop, state.eventDate);\n newEnd = state.originalEnd;\n } else {\n newStart = state.originalStart;\n newEnd = percentToDateTime(state.currentTop + state.currentHeight, 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.removeEventListener('pointermove', handlePointerMove);\n document.removeEventListener('pointerup', handlePointerUp);\n };\n }, [isResizing]);\n\n const getResizePosition = useCallback(\n (eventId: string | number) => {\n if (!resizeState || resizeState.eventId !== eventId) {\n return null;\n }\n return { top: resizeState.currentTop, height: resizeState.currentHeight };\n },\n [resizeState]\n );\n\n const isResizableEvent = useCallback(\n (event: ScheduleEventData) => {\n if (!enabled || mode === 'static' || event.display === 'background') {\n return false;\n }\n return canResizeEvent ? canResizeEvent(event) : true;\n },\n [enabled, mode, canResizeEvent]\n );\n\n const wasResizing = useCallback(() => justResizedRef.current, []);\n\n return {\n handleResizeStart,\n isResizing,\n resizingEventId: resizeState?.eventId ?? null,\n getResizePosition,\n isResizableEvent,\n wasResizing,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-event-resize.mjs","names":[],"sources":["../../src/hooks/use-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 { parseTimeString } from '../utils/parse-time-string/parse-time-string';\n\ntype ResizeEdge = 'top' | 'bottom';\n\ninterface ResizeState {\n eventId: string | number;\n event: ScheduleEventData;\n edge: ResizeEdge;\n container: HTMLElement;\n originalTop: number;\n originalHeight: number;\n currentTop: number;\n currentHeight: number;\n eventDate: string;\n originalStart: DateTimeStringValue;\n originalEnd: DateTimeStringValue;\n}\n\nexport interface UseEventResizeInput {\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 useEventResize({\n enabled = false,\n mode = 'default',\n startTime,\n endTime,\n intervalMinutes,\n resizeIntervalMinutes,\n onEventResize,\n canResizeEvent,\n withBackgroundEvents = false,\n}: UseEventResizeInput) {\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 // Snapping happens in absolute minutes from midnight – the same grid `calculateDropTime`\n // uses for drag and drop. Snapping relative to `startTime` would put resized edges on a\n // different grid than dragged events whenever `startTime` is not a multiple of the interval\n // (with `startTime=\"08:10\"` and a 30 minute step, drops land on :00/:30 but resizes on\n // :10/:40, so the two operations could never align an event).\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 originalTop,\n originalHeight,\n eventDate,\n pointerEvent,\n }: {\n event: ScheduleEventData;\n edge: ResizeEdge;\n container: HTMLElement;\n originalTop: number;\n originalHeight: number;\n eventDate: string;\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 originalTop,\n originalHeight,\n currentTop: originalTop,\n currentHeight: originalHeight,\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 };\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 document.body.style.userSelect = 'none';\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 relativeY = e.clientY - containerRect.top;\n const rawPercent = Math.max(0, Math.min(100, (relativeY / containerRect.height) * 100));\n const draggedMinutes = clampAndSnap(percentToMinutes(rawPercent));\n\n let newTop = state.originalTop;\n let newHeight = state.originalHeight;\n\n if (state.edge === 'bottom') {\n const topMinutes = percentToMinutes(state.originalTop);\n const minEndMinutes = snapEdgeMinutes(topMinutes + clampedResizeInterval, 'up');\n const endMinutes = Math.max(draggedMinutes, minEndMinutes);\n newHeight = Math.max(0, minutesToPercent(endMinutes) - state.originalTop);\n } else {\n const bottomMinutes = percentToMinutes(state.originalTop + state.originalHeight);\n const maxStartMinutes = snapEdgeMinutes(bottomMinutes - clampedResizeInterval, 'down');\n const startMinutesValue = Math.min(draggedMinutes, maxStartMinutes);\n newTop = minutesToPercent(startMinutesValue);\n newHeight = Math.max(0, state.originalTop + state.originalHeight - newTop);\n }\n\n resizeRef.current = { ...state, currentTop: newTop, currentHeight: newHeight };\n setResizeState(resizeRef.current);\n };\n\n const handlePointerUp = () => {\n const state = resizeRef.current;\n if (state) {\n if (\n state.currentTop !== state.originalTop ||\n state.currentHeight !== state.originalHeight\n ) {\n let newStart: DateTimeStringValue;\n let newEnd: DateTimeStringValue;\n\n if (state.edge === 'top') {\n newStart = percentToDateTime(state.currentTop, state.eventDate);\n newEnd = state.originalEnd;\n } else {\n newStart = state.originalStart;\n newEnd = percentToDateTime(state.currentTop + state.currentHeight, 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.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 { top: resizeState.currentTop, height: resizeState.currentHeight };\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 getResizePosition,\n isResizableEvent,\n wasResizing,\n };\n}\n"],"mappings":";;;;;;AAuCA,SAAgB,eAAe,EAC7B,UAAU,OACV,OAAO,WACP,WACA,SACA,iBACA,uBACA,eACA,gBACA,uBAAuB,SACD;CACtB,MAAM,CAAC,aAAa,kBAAkB,SAA6B,IAAI;CACvE,MAAM,YAAY,OAA2B,IAAI;CACjD,MAAM,iBAAiB,OAAO,KAAK;CACnC,MAAM,sBAAsB,eAAe,wBAAwB,CAAC,EAAE;CAEtE,MAAM,kBAAkB,gBAAgB,SAAS;CACjD,MAAM,gBAAgB,gBAAgB,OAAO;CAC7C,MAAM,eAAe,gBAAgB,QAAQ,KAAK,gBAAgB;CAClE,MAAM,aAAa,cAAc,QAAQ,KAAK,cAAc;CAC5D,MAAM,kBAAkB,qBAAqB,eAAe;CAC5D,MAAM,wBAAwB,qBAAqB,yBAAyB,eAAe;CAC3F,MAAM,eAAe,aAAa;CAClC,MAAM,eAAe,KAAK,KAAK,eAAe,eAAe,IAAI;CAOjE,MAAM,eAAe,aAClB,YAA4B;EAC3B,MAAM,UACJ,KAAK,OAAO,eAAe,WAAW,qBAAqB,IAAI;EACjE,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,cAAc,UAAU,YAAY,CAAC;CACnE,GACA;EAAC;EAAc;EAAuB;CAAY,CACpD;CAEA,MAAM,oBAAoB,aACvB,SAAiB,cAA2C;EAC3D,MAAM,UAAW,UAAU,MAAO;EAClC,MAAM,iBAAiB,aAAa,OAAO;EAC3C,MAAM,YAAY,eAAe;EACjC,MAAM,QAAQ,KAAK,MAAM,YAAY,EAAE;EACvC,MAAM,OAAO,YAAY;EACzB,OAAO,GAAG,UAAU,GAAG,OAAO,KAAK,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE,GAAG,OAAO,IAAI,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE;CACzF,GACA;EAAC;EAAc;EAAc;CAAY,CAC3C;CAEA,MAAM,kBAAkB,aACrB,SAAiB,cAAqC;EACrD,MAAM,WAAW,eAAe;EAChC,MAAM,UACJ,cAAc,OACV,KAAK,KAAK,WAAW,qBAAqB,IAAI,wBAC9C,KAAK,MAAM,WAAW,qBAAqB,IAAI;EACrD,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,cAAc,UAAU,YAAY,CAAC;CACnE,GACA;EAAC;EAAc;EAAuB;CAAY,CACpD;CAEA,MAAM,mBAAmB,aACtB,YAA6B,UAAU,MAAO,cAC/C,CAAC,YAAY,CACf;CAEA,MAAM,mBAAmB,aACtB,YAA6B,UAAU,eAAgB,KACxD,CAAC,YAAY,CACf;CAEA,MAAM,oBAAoB,aACvB,EACC,OACA,MACA,WACA,aACA,gBACA,WACA,mBASI;EACJ,IAAI,CAAC,WAAW,SAAS,UACvB;EAGF,aAAa,eAAe;EAC5B,aAAa,gBAAgB;EAE7B,MAAM,QAAqB;GACzB,SAAS,MAAM;GACf;GACA;GACA;GACA;GACA;GACA,YAAY;GACZ,eAAe;GACf;GACA,eAAe,MAAM,MAAM,KAAK,CAAC,CAAC,OAAO,qBAAqB;GAC9D,aAAa,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,qBAAqB;EAC5D;EAEA,UAAU,UAAU;EACpB,eAAe,KAAK;CACtB,GACA,CAAC,SAAS,IAAI,CAChB;CAEA,MAAM,aAAa,gBAAgB;CAEnC,gBAAgB;EACd,IAAI,CAAC,YACH;EAGF,MAAM,kBAAkB,SAAS,KAAK,MAAM;EAC5C,SAAS,KAAK,MAAM,aAAa;EAEjC,MAAM,qBAAqB,MAAoB;GAC7C,MAAM,QAAQ,UAAU;GACxB,IAAI,CAAC,OACH;GAGF,MAAM,gBAAgB,MAAM,UAAU,sBAAsB;GAC5D,MAAM,YAAY,EAAE,UAAU,cAAc;GAC5C,MAAM,aAAa,KAAK,IAAI,GAAG,KAAK,IAAI,KAAM,YAAY,cAAc,SAAU,GAAG,CAAC;GACtF,MAAM,iBAAiB,aAAa,iBAAiB,UAAU,CAAC;GAEhE,IAAI,SAAS,MAAM;GACnB,IAAI,YAAY,MAAM;GAEtB,IAAI,MAAM,SAAS,UAAU;IAC3B,MAAM,aAAa,iBAAiB,MAAM,WAAW;IACrD,MAAM,gBAAgB,gBAAgB,aAAa,uBAAuB,IAAI;IAE9E,YAAY,KAAK,IAAI,GAAG,iBADL,KAAK,IAAI,gBAAgB,aACM,CAAC,IAAI,MAAM,WAAW;GAC1E,OAAO;IACL,MAAM,gBAAgB,iBAAiB,MAAM,cAAc,MAAM,cAAc;IAC/E,MAAM,kBAAkB,gBAAgB,gBAAgB,uBAAuB,MAAM;IAErF,SAAS,iBADiB,KAAK,IAAI,gBAAgB,eACT,CAAC;IAC3C,YAAY,KAAK,IAAI,GAAG,MAAM,cAAc,MAAM,iBAAiB,MAAM;GAC3E;GAEA,UAAU,UAAU;IAAE,GAAG;IAAO,YAAY;IAAQ,eAAe;GAAU;GAC7E,eAAe,UAAU,OAAO;EAClC;EAEA,MAAM,wBAAwB;GAC5B,MAAM,QAAQ,UAAU;GACxB,IAAI,OAEA;QAAA,MAAM,eAAe,MAAM,eAC3B,MAAM,kBAAkB,MAAM,gBAC9B;KACA,IAAI;KACJ,IAAI;KAEJ,IAAI,MAAM,SAAS,OAAO;MACxB,WAAW,kBAAkB,MAAM,YAAY,MAAM,SAAS;MAC9D,SAAS,MAAM;KACjB,OAAO;MACL,WAAW,MAAM;MACjB,SAAS,kBAAkB,MAAM,aAAa,MAAM,eAAe,MAAM,SAAS;KACpF;KAEA,oBAAoB;MAClB,SAAS,MAAM;MACf;MACA;MACA,OAAO,MAAM;KACf,CAAC;IACH;;GAEF,UAAU,UAAU;GACpB,eAAe,IAAI;GACnB,eAAe,UAAU;GACzB,4BAA4B;IAC1B,eAAe,UAAU;GAC3B,CAAC;EACH;EAEA,SAAS,iBAAiB,eAAe,iBAAiB;EAC1D,SAAS,iBAAiB,aAAa,eAAe;EAEtD,aAAa;GACX,SAAS,KAAK,MAAM,aAAa;GACjC,SAAS,oBAAoB,eAAe,iBAAiB;GAC7D,SAAS,oBAAoB,aAAa,eAAe;EAC3D;CACF,GAAG,CAAC,UAAU,CAAC;CAEf,MAAM,oBAAoB,aACvB,SAA0B,cAAuB;EAChD,IAAI,CAAC,eAAe,YAAY,YAAY,SAC1C,OAAO;EAET,IAAI,cAAc,KAAA,KAAa,YAAY,cAAc,WACvD,OAAO;EAET,OAAO;GAAE,KAAK,YAAY;GAAY,QAAQ,YAAY;EAAc;CAC1E,GACA,CAAC,WAAW,CACd;CAEA,MAAM,mBAAmB,aACtB,UAA6B;EAC5B,IAAI,CAAC,WAAW,SAAS,UACvB,OAAO;EAET,IAAI,MAAM,YAAY,gBAAgB,CAAC,sBACrC,OAAO;EAET,OAAO,iBAAiB,eAAe,KAAK,IAAI;CAClD,GACA;EAAC;EAAS;EAAM;EAAgB;CAAoB,CACtD;CAEA,MAAM,cAAc,kBAAkB,eAAe,SAAS,CAAC,CAAC;CAEhE,OAAO;EACL;EACA;EACA,iBAAiB,aAAa,WAAW;EACzC;EACA;EACA;CACF;AACF"}
|
|
@@ -5,7 +5,7 @@ import { getDayRelativePercent } from "../utils/get-day-relative-percent/get-day
|
|
|
5
5
|
import dayjs from "dayjs";
|
|
6
6
|
import { useCallback, useEffect, useEffectEvent, useRef, useState } from "react";
|
|
7
7
|
//#region packages/@mantine/schedule/src/hooks/use-horizontal-event-resize.ts
|
|
8
|
-
function useHorizontalEventResize({ enabled = false, mode = "default", startTime, endTime, intervalMinutes, resizeIntervalMinutes, onEventResize, canResizeEvent }) {
|
|
8
|
+
function useHorizontalEventResize({ enabled = false, mode = "default", startTime, endTime, intervalMinutes, resizeIntervalMinutes, onEventResize, canResizeEvent, withBackgroundEvents = false }) {
|
|
9
9
|
const [resizeState, setResizeState] = useState(null);
|
|
10
10
|
const resizeRef = useRef(null);
|
|
11
11
|
const justResizedRef = useRef(false);
|
|
@@ -146,20 +146,23 @@ function useHorizontalEventResize({ enabled = false, mode = "default", startTime
|
|
|
146
146
|
document.removeEventListener("pointerup", handlePointerUp);
|
|
147
147
|
};
|
|
148
148
|
}, [isResizing]);
|
|
149
|
-
const getResizePosition = useCallback((eventId) => {
|
|
149
|
+
const getResizePosition = useCallback((eventId, eventDate) => {
|
|
150
150
|
if (!resizeState || resizeState.eventId !== eventId) return null;
|
|
151
|
+
if (eventDate !== void 0 && resizeState.eventDate !== eventDate) return null;
|
|
151
152
|
return {
|
|
152
153
|
left: resizeState.currentLeft,
|
|
153
154
|
width: resizeState.currentWidth
|
|
154
155
|
};
|
|
155
156
|
}, [resizeState]);
|
|
156
157
|
const isResizableEvent = useCallback((event) => {
|
|
157
|
-
if (!enabled || mode === "static"
|
|
158
|
+
if (!enabled || mode === "static") return false;
|
|
159
|
+
if (event.display === "background" && !withBackgroundEvents) return false;
|
|
158
160
|
return canResizeEvent ? canResizeEvent(event) : true;
|
|
159
161
|
}, [
|
|
160
162
|
enabled,
|
|
161
163
|
mode,
|
|
162
|
-
canResizeEvent
|
|
164
|
+
canResizeEvent,
|
|
165
|
+
withBackgroundEvents
|
|
163
166
|
]);
|
|
164
167
|
const wasResizing = useCallback(() => justResizedRef.current, []);
|
|
165
168
|
return {
|
|
@@ -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}\n\nexport function useHorizontalEventResize({\n enabled = false,\n mode = 'default',\n startTime,\n endTime,\n intervalMinutes,\n resizeIntervalMinutes,\n onEventResize,\n canResizeEvent,\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) => {\n if (!resizeState || resizeState.eventId !== eventId) {\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' || event.display === 'background') {\n return false;\n }\n return canResizeEvent ? canResizeEvent(event) : true;\n },\n [enabled, mode, canResizeEvent]\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';\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":";;;;;;;AA0CA,SAAgB,yBAAyB,EACvC,UAAU,OACV,OAAO,WACP,WACA,SACA,iBACA,uBACA,eACA,gBACA,uBAAuB,SACS;CAChC,MAAM,CAAC,aAAa,kBAAkB,SAA6B,IAAI;CACvE,MAAM,YAAY,OAA2B,IAAI;CACjD,MAAM,iBAAiB,OAAO,KAAK;CACnC,MAAM,sBAAsB,eAAe,wBAAwB,CAAC,EAAE;CAEtE,MAAM,kBAAkB,gBAAgB,SAAS;CACjD,MAAM,gBAAgB,gBAAgB,OAAO;CAC7C,MAAM,eAAe,gBAAgB,QAAQ,KAAK,gBAAgB;CAClE,MAAM,aAAa,cAAc,QAAQ,KAAK,cAAc;CAC5D,MAAM,kBAAkB,qBAAqB,eAAe;CAC5D,MAAM,wBAAwB,qBAAqB,yBAAyB,eAAe;CAC3F,MAAM,eAAe,aAAa;CAClC,MAAM,eAAe,KAAK,KAAK,eAAe,eAAe,IAAI;CAIjE,MAAM,eAAe,aAClB,YAA4B;EAC3B,MAAM,UACJ,KAAK,OAAO,eAAe,WAAW,qBAAqB,IAAI;EACjE,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,cAAc,UAAU,YAAY,CAAC;CACnE,GACA;EAAC;EAAc;EAAuB;CAAY,CACpD;CAEA,MAAM,oBAAoB,aACvB,SAAiB,cAA2C;EAC3D,MAAM,UAAW,UAAU,MAAO;EAClC,MAAM,iBAAiB,aAAa,OAAO;EAC3C,MAAM,YAAY,eAAe;EACjC,MAAM,QAAQ,KAAK,MAAM,YAAY,EAAE;EACvC,MAAM,OAAO,YAAY;EACzB,OAAO,GAAG,UAAU,GAAG,OAAO,KAAK,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE,GAAG,OAAO,IAAI,CAAC,CAAC,SAAS,GAAG,GAAG,EAAE;CACzF,GACA;EAAC;EAAc;EAAc;CAAY,CAC3C;CAEA,MAAM,kBAAkB,aACrB,SAAiB,cAAqC;EACrD,MAAM,WAAW,eAAe;EAChC,MAAM,UACJ,cAAc,OACV,KAAK,KAAK,WAAW,qBAAqB,IAAI,wBAC9C,KAAK,MAAM,WAAW,qBAAqB,IAAI;EACrD,OAAO,KAAK,IAAI,GAAG,KAAK,IAAI,cAAc,UAAU,YAAY,CAAC;CACnE,GACA;EAAC;EAAc;EAAuB;CAAY,CACpD;CAEA,MAAM,mBAAmB,aACtB,YAA6B,UAAU,MAAO,cAC/C,CAAC,YAAY,CACf;CAEA,MAAM,mBAAmB,aACtB,YAA6B,UAAU,eAAgB,KACxD,CAAC,YAAY,CACf;CAEA,MAAM,oBAAoB,aACvB,EACC,OACA,MACA,WACA,cACA,eACA,WACA,WAAW,GACX,WAAW,GACX,mBAWI;EACJ,IAAI,CAAC,WAAW,SAAS,UACvB;EAGF,aAAa,eAAe;EAC5B,aAAa,gBAAgB;EAE7B,MAAM,QAAqB;GACzB,SAAS,MAAM;GACf;GACA;GACA;GACA;GACA;GACA,aAAa;GACb,cAAc;GACd;GACA,eAAe,MAAM,MAAM,KAAK,CAAC,CAAC,OAAO,qBAAqB;GAC9D,aAAa,MAAM,MAAM,GAAG,CAAC,CAAC,OAAO,qBAAqB;GAC1D;GACA;EACF;EAEA,UAAU,UAAU;EACpB,eAAe,KAAK;CACtB,GACA,CAAC,SAAS,IAAI,CAChB;CAEA,MAAM,aAAa,gBAAgB;CAEnC,gBAAgB;EACd,IAAI,CAAC,YACH;EAGF,MAAM,kBAAkB,SAAS,KAAK,MAAM;EAC5C,MAAM,cAAc,SAAS,KAAK,MAAM;EACxC,SAAS,KAAK,MAAM,aAAa;EACjC,SAAS,KAAK,MAAM,SAAS;EAE7B,MAAM,qBAAqB,MAAoB;GAC7C,MAAM,QAAQ,UAAU;GACxB,IAAI,CAAC,OACH;GAGF,MAAM,gBAAgB,MAAM,UAAU,sBAAsB;GAC5D,MAAM,aAAa,sBAAsB;IACvC,SAAS,EAAE;IACX,eAAe,cAAc;IAC7B,gBAAgB,cAAc;IAC9B,UAAU,MAAM;IAChB,UAAU,MAAM;GAClB,CAAC;GACD,MAAM,iBAAiB,aAAa,iBAAiB,UAAU,CAAC;GAEhE,IAAI,UAAU,MAAM;GACpB,IAAI,WAAW,MAAM;GAErB,IAAI,MAAM,SAAS,OAAO;IACxB,MAAM,cAAc,iBAAiB,MAAM,YAAY;IACvD,MAAM,gBAAgB,gBAAgB,cAAc,uBAAuB,IAAI;IAE/E,WAAW,KAAK,IAAI,GAAG,iBADJ,KAAK,IAAI,gBAAgB,aACK,CAAC,IAAI,MAAM,YAAY;GAC1E,OAAO;IACL,MAAM,eAAe,iBAAiB,MAAM,eAAe,MAAM,aAAa;IAC9E,MAAM,kBAAkB,gBAAgB,eAAe,uBAAuB,MAAM;IAEpF,UAAU,iBADgB,KAAK,IAAI,gBAAgB,eACR,CAAC;IAC5C,WAAW,KAAK,IAAI,GAAG,MAAM,eAAe,MAAM,gBAAgB,OAAO;GAC3E;GAEA,UAAU,UAAU;IAAE,GAAG;IAAO,aAAa;IAAS,cAAc;GAAS;GAC7E,eAAe,UAAU,OAAO;EAClC;EAEA,MAAM,wBAAwB;GAC5B,MAAM,QAAQ,UAAU;GACxB,IAAI,OAEA;QAAA,MAAM,gBAAgB,MAAM,gBAC5B,MAAM,iBAAiB,MAAM,eAC7B;KACA,IAAI;KACJ,IAAI;KAEJ,IAAI,MAAM,SAAS,SAAS;MAC1B,WAAW,kBAAkB,MAAM,aAAa,MAAM,SAAS;MAC/D,SAAS,MAAM;KACjB,OAAO;MACL,WAAW,MAAM;MACjB,SAAS,kBAAkB,MAAM,cAAc,MAAM,cAAc,MAAM,SAAS;KACpF;KAEA,oBAAoB;MAClB,SAAS,MAAM;MACf;MACA;MACA,OAAO,MAAM;KACf,CAAC;IACH;;GAEF,UAAU,UAAU;GACpB,eAAe,IAAI;GACnB,eAAe,UAAU;GACzB,4BAA4B;IAC1B,eAAe,UAAU;GAC3B,CAAC;EACH;EAEA,SAAS,iBAAiB,eAAe,iBAAiB;EAC1D,SAAS,iBAAiB,aAAa,eAAe;EAEtD,aAAa;GACX,SAAS,KAAK,MAAM,aAAa;GACjC,SAAS,KAAK,MAAM,SAAS;GAC7B,SAAS,oBAAoB,eAAe,iBAAiB;GAC7D,SAAS,oBAAoB,aAAa,eAAe;EAC3D;CACF,GAAG,CAAC,UAAU,CAAC;CAEf,MAAM,oBAAoB,aACvB,SAA0B,cAAuB;EAChD,IAAI,CAAC,eAAe,YAAY,YAAY,SAC1C,OAAO;EAET,IAAI,cAAc,KAAA,KAAa,YAAY,cAAc,WACvD,OAAO;EAET,OAAO;GAAE,MAAM,YAAY;GAAa,OAAO,YAAY;EAAa;CAC1E,GACA,CAAC,WAAW,CACd;CAEA,MAAM,mBAAmB,aACtB,UAA6B;EAC5B,IAAI,CAAC,WAAW,SAAS,UACvB,OAAO;EAET,IAAI,MAAM,YAAY,gBAAgB,CAAC,sBACrC,OAAO;EAET,OAAO,iBAAiB,eAAe,KAAK,IAAI;CAClD,GACA;EAAC;EAAS;EAAM;EAAgB;CAAoB,CACtD;CAEA,MAAM,cAAc,kBAAkB,eAAe,SAAS,CAAC,CAAC;CAEhE,OAAO;EACL;EACA;EACA,iBAAiB,aAAa,WAAW;EACzC,cAAc,aAAa,QAAQ;EACnC;EACA;EACA;CACF;AACF"}
|
package/esm/index.mjs
CHANGED
|
@@ -41,6 +41,8 @@ import { handleResourcesGridKeyDown } from "./utils/handle-resources-grid-key-do
|
|
|
41
41
|
import { getGroupToResourceIdMap } from "./utils/get-group-to-resource-id-map/get-group-to-resource-id-map.mjs";
|
|
42
42
|
import { getIndexFromDragPoint } from "./utils/get-index-from-drag-point/get-index-from-drag-point.mjs";
|
|
43
43
|
import { getDayRelativePercent } from "./utils/get-day-relative-percent/get-day-relative-percent.mjs";
|
|
44
|
+
import { getOverlapClusters } from "./utils/get-overlap-clusters/get-overlap-clusters.mjs";
|
|
45
|
+
import { applyCascadeLayout } from "./utils/apply-cascade-layout/apply-cascade-layout.mjs";
|
|
44
46
|
import { AgendaView } from "./components/AgendaView/AgendaView.mjs";
|
|
45
47
|
import { CurrentTimeIndicator } from "./components/CurrentTimeIndicator/CurrentTimeIndicator.mjs";
|
|
46
48
|
import { DragContext } from "./components/DragContext/DragContext.mjs";
|
|
@@ -58,4 +60,4 @@ import { ResourcesDayView } from "./components/ResourcesDayView/ResourcesDayView
|
|
|
58
60
|
import { ResourcesWeekView } from "./components/ResourcesWeekView/ResourcesWeekView.mjs";
|
|
59
61
|
import { ResourcesMonthView } from "./components/ResourcesMonthView/ResourcesMonthView.mjs";
|
|
60
62
|
import { ResourcesSchedule } from "./components/ResourcesSchedule/ResourcesSchedule.mjs";
|
|
61
|
-
export { AgendaView, CurrentTimeIndicator, DEFAULT_SCHEDULE_LABELS, DayView, DragContext, MobileMonthView, MonthView, MoreEvents, ResourcesDayView, ResourcesMonthView, ResourcesSchedule, ResourcesWeekView, Schedule, ScheduleBackgroundEvent, ScheduleEvent, ScheduleHeader, WeekView, YearView, calculateDropTime, calculateMonthDropDate, clampIntervalMinutes, expandRecurringEvents, formatDate, getBusinessHoursMod, getCurrentTimePosition, getDayPosition, getDayRelativePercent, getDayTimeIntervals, getEndOfWeek, getGroupToResourceIdMap, getIndexFromDragPoint, getLabel, getMonthDays, getMonthRange, getMonthsByQuarter, getMonthsList, getOrderedResources, getStartOfWeek, getTimeAxisEventStyle, getVisibleEvents, getWeekDays, getWeekNumber, getWeekdaysNames, getYearsList, handleResourcesGridKeyDown, isAfterMinDate, isAllDayEvent, isBeforeMaxDate, isEventInTimeRange, isEventsOverlap, isInTimeRange, isMultidayEvent, isSameMonth, isWithinWeek, nextWeek, parseTimeString, previousWeek, sortEvents, toDateString, toTimeString, validateEvent };
|
|
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 };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { getOverlapClusters } from "../get-overlap-clusters/get-overlap-clusters.mjs";
|
|
3
|
+
//#region packages/@mantine/schedule/src/utils/apply-cascade-layout/apply-cascade-layout.ts
|
|
4
|
+
/** Indent applied per column before the cluster runs out of room, in percent */
|
|
5
|
+
const BASE_STEP = 20;
|
|
6
|
+
function applyCascadeLayout(events) {
|
|
7
|
+
const timedEvents = events.filter((event) => !event.position.allDay);
|
|
8
|
+
for (const cluster of getOverlapClusters(timedEvents)) {
|
|
9
|
+
const columns = Math.max(...cluster.map((event) => event.position.column)) + 1;
|
|
10
|
+
const step = columns > 1 ? Math.min(BASE_STEP, 40 / (columns - 1)) : 0;
|
|
11
|
+
for (const event of cluster) {
|
|
12
|
+
event.position.overlaps = columns;
|
|
13
|
+
event.position.offset = event.position.column * step;
|
|
14
|
+
event.position.width = 100 - event.position.offset;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { applyCascadeLayout };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=apply-cascade-layout.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply-cascade-layout.mjs","names":[],"sources":["../../../src/utils/apply-cascade-layout/apply-cascade-layout.ts"],"sourcesContent":["import { DayEventPositionData, ScheduleEventData } from '../../types';\nimport { getOverlapClusters } from '../get-overlap-clusters/get-overlap-clusters';\n\n/** Indent applied per column before the cluster runs out of room, in percent */\nconst BASE_STEP = 20;\n\n/** Width the last event in a cluster is guaranteed to keep, in percent */\nconst MIN_TOP_WIDTH = 60;\n\n/** Minimal shape `applyCascadeLayout` needs, shared by day and week positioned events */\nexport type CascadePositionedEvent = ScheduleEventData & {\n position: Pick<DayEventPositionData, 'allDay' | 'column' | 'width' | 'offset' | 'overlaps'>;\n};\n\nexport function applyCascadeLayout<T extends CascadePositionedEvent>(events: T[]): void {\n const timedEvents = events.filter((event) => !event.position.allDay);\n\n for (const cluster of getOverlapClusters(timedEvents)) {\n const columns = Math.max(...cluster.map((event) => event.position.column)) + 1;\n const step = columns > 1 ? Math.min(BASE_STEP, (100 - MIN_TOP_WIDTH) / (columns - 1)) : 0;\n\n for (const event of cluster) {\n event.position.overlaps = columns;\n event.position.offset = event.position.column * step;\n event.position.width = 100 - event.position.offset;\n }\n }\n}\n"],"mappings":";;;;AAIA,MAAM,YAAY;AAUlB,SAAgB,mBAAqD,QAAmB;CACtF,MAAM,cAAc,OAAO,QAAQ,UAAU,CAAC,MAAM,SAAS,MAAM;CAEnE,KAAK,MAAM,WAAW,mBAAmB,WAAW,GAAG;EACrD,MAAM,UAAU,KAAK,IAAI,GAAG,QAAQ,KAAK,UAAU,MAAM,SAAS,MAAM,CAAC,IAAI;EAC7E,MAAM,OAAO,UAAU,IAAI,KAAK,IAAI,WAAY,MAAwB,UAAU,EAAE,IAAI;EAExF,KAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,SAAS,WAAW;GAC1B,MAAM,SAAS,SAAS,MAAM,SAAS,SAAS;GAChD,MAAM,SAAS,QAAQ,MAAM,MAAM,SAAS;EAC9C;CACF;AACF"}
|
package/esm/{components/ResourcesDayView → utils}/get-overlap-clusters/get-overlap-clusters.mjs
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { isEventsOverlap } from "
|
|
3
|
-
//#region packages/@mantine/schedule/src/
|
|
2
|
+
import { isEventsOverlap } from "../is-events-overlap/is-events-overlap.mjs";
|
|
3
|
+
//#region packages/@mantine/schedule/src/utils/get-overlap-clusters/get-overlap-clusters.ts
|
|
4
4
|
function getOverlapClusters(events) {
|
|
5
5
|
const clusters = [];
|
|
6
6
|
const assigned = /* @__PURE__ */ new Set();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-overlap-clusters.mjs","names":[],"sources":["../../../src/utils/get-overlap-clusters/get-overlap-clusters.ts"],"sourcesContent":["import { ScheduleEventData } from '../../types';\nimport { isEventsOverlap } from '../is-events-overlap/is-events-overlap';\n\nexport function getOverlapClusters<T extends ScheduleEventData>(events: T[]): T[][] {\n const clusters: T[][] = [];\n const assigned = new Set<number>();\n\n for (let i = 0; i < events.length; i++) {\n if (assigned.has(i)) {\n continue;\n }\n\n const cluster: T[] = [events[i]];\n assigned.add(i);\n\n let j = 0;\n while (j < cluster.length) {\n for (let k = 0; k < events.length; k++) {\n if (!assigned.has(k) && isEventsOverlap(cluster[j], events[k])) {\n cluster.push(events[k]);\n assigned.add(k);\n }\n }\n j++;\n }\n\n clusters.push(cluster);\n }\n\n return clusters;\n}\n"],"mappings":";;;AAGA,SAAgB,mBAAgD,QAAoB;CAClF,MAAM,WAAkB,CAAC;CACzB,MAAM,2BAAW,IAAI,IAAY;CAEjC,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;EACtC,IAAI,SAAS,IAAI,CAAC,GAChB;EAGF,MAAM,UAAe,CAAC,OAAO,EAAE;EAC/B,SAAS,IAAI,CAAC;EAEd,IAAI,IAAI;EACR,OAAO,IAAI,QAAQ,QAAQ;GACzB,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KACjC,IAAI,CAAC,SAAS,IAAI,CAAC,KAAK,gBAAgB,QAAQ,IAAI,OAAO,EAAE,GAAG;IAC9D,QAAQ,KAAK,OAAO,EAAE;IACtB,SAAS,IAAI,CAAC;GAChB;GAEF;EACF;EAEA,SAAS,KAAK,OAAO;CACvB;CAEA,OAAO;AACT"}
|
|
@@ -18,8 +18,9 @@ function isEventInTimeRange({ event, startTime, endTime }) {
|
|
|
18
18
|
const timeWindowEndMinutes = endHour * 60 + endMinute;
|
|
19
19
|
const eventStart = dayjs(event.start);
|
|
20
20
|
const eventEnd = dayjs(event.end);
|
|
21
|
+
const endsAtNextDayStart = eventEnd.isSame(eventStart.startOf("day").add(1, "day"), "second");
|
|
21
22
|
const eventStartMinutes = eventStart.hour() * 60 + eventStart.minute();
|
|
22
|
-
return !(eventEnd.hour() * 60 + eventEnd.minute() <= timeWindowStartMinutes || eventStartMinutes >= timeWindowEndMinutes);
|
|
23
|
+
return !((endsAtNextDayStart ? 1440 : eventEnd.hour() * 60 + eventEnd.minute()) <= timeWindowStartMinutes || eventStartMinutes >= timeWindowEndMinutes);
|
|
23
24
|
}
|
|
24
25
|
//#endregion
|
|
25
26
|
export { isEventInTimeRange };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"is-event-in-time-range.mjs","names":[],"sources":["../../../src/utils/is-event-in-time-range/is-event-in-time-range.ts"],"sourcesContent":["import dayjs from 'dayjs';\nimport { ScheduleEventData } from '../../types';\nimport { isMultidayEvent } from '../is-multiday-event/is-multiday-event';\n\ninterface IsEventInTimeRangeInput {\n event: ScheduleEventData;\n startTime?: string;\n endTime?: string;\n}\n\nfunction spansFullDay(event: ScheduleEventData) {\n const start = dayjs(event.start);\n const end = dayjs(event.end);\n if (!start.isSame(start.startOf('day'))) {\n return false;\n }\n const nextDayStart = start.startOf('day').add(1, 'day');\n return end.isSame(nextDayStart) || end.isSame(nextDayStart.subtract(1, 'second'));\n}\n\nexport function isEventInTimeRange({ event, startTime, endTime }: IsEventInTimeRangeInput) {\n if (isMultidayEvent(event) || spansFullDay(event)) {\n return true;\n }\n\n if (!startTime || !endTime) {\n return true;\n }\n\n const [startHour, startMinute] = startTime.split(':').map(Number);\n const [endHour, endMinute] = endTime.split(':').map(Number);\n const timeWindowStartMinutes = startHour * 60 + startMinute;\n const timeWindowEndMinutes = endHour * 60 + endMinute;\n\n const eventStart = dayjs(event.start);\n const eventEnd = dayjs(event.end);\n const eventStartMinutes = eventStart.hour() * 60 + eventStart.minute();\n const eventEndMinutes = eventEnd.hour() * 60 + eventEnd.minute();\n\n return !(eventEndMinutes <= timeWindowStartMinutes || eventStartMinutes >= timeWindowEndMinutes);\n}\n"],"mappings":";;;;AAUA,SAAS,aAAa,OAA0B;CAC9C,MAAM,QAAQ,MAAM,MAAM,KAAK;CAC/B,MAAM,MAAM,MAAM,MAAM,GAAG;CAC3B,IAAI,CAAC,MAAM,OAAO,MAAM,QAAQ,KAAK,CAAC,GACpC,OAAO;CAET,MAAM,eAAe,MAAM,QAAQ,KAAK,CAAC,CAAC,IAAI,GAAG,KAAK;CACtD,OAAO,IAAI,OAAO,YAAY,KAAK,IAAI,OAAO,aAAa,SAAS,GAAG,QAAQ,CAAC;AAClF;AAEA,SAAgB,mBAAmB,EAAE,OAAO,WAAW,WAAoC;CACzF,IAAI,gBAAgB,KAAK,KAAK,aAAa,KAAK,GAC9C,OAAO;CAGT,IAAI,CAAC,aAAa,CAAC,SACjB,OAAO;CAGT,MAAM,CAAC,WAAW,eAAe,UAAU,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;CAChE,MAAM,CAAC,SAAS,aAAa,QAAQ,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;CAC1D,MAAM,yBAAyB,YAAY,KAAK;CAChD,MAAM,uBAAuB,UAAU,KAAK;CAE5C,MAAM,aAAa,MAAM,MAAM,KAAK;CACpC,MAAM,WAAW,MAAM,MAAM,GAAG;CAChC,MAAM,oBAAoB,WAAW,KAAK,IAAI,KAAK,WAAW,OAAO;CAGrE,OAAO,
|
|
1
|
+
{"version":3,"file":"is-event-in-time-range.mjs","names":[],"sources":["../../../src/utils/is-event-in-time-range/is-event-in-time-range.ts"],"sourcesContent":["import dayjs from 'dayjs';\nimport { ScheduleEventData } from '../../types';\nimport { isMultidayEvent } from '../is-multiday-event/is-multiday-event';\n\ninterface IsEventInTimeRangeInput {\n event: ScheduleEventData;\n startTime?: string;\n endTime?: string;\n}\n\nfunction spansFullDay(event: ScheduleEventData) {\n const start = dayjs(event.start);\n const end = dayjs(event.end);\n if (!start.isSame(start.startOf('day'))) {\n return false;\n }\n const nextDayStart = start.startOf('day').add(1, 'day');\n return end.isSame(nextDayStart) || end.isSame(nextDayStart.subtract(1, 'second'));\n}\n\nexport function isEventInTimeRange({ event, startTime, endTime }: IsEventInTimeRangeInput) {\n if (isMultidayEvent(event) || spansFullDay(event)) {\n return true;\n }\n\n if (!startTime || !endTime) {\n return true;\n }\n\n const [startHour, startMinute] = startTime.split(':').map(Number);\n const [endHour, endMinute] = endTime.split(':').map(Number);\n const timeWindowStartMinutes = startHour * 60 + startMinute;\n const timeWindowEndMinutes = endHour * 60 + endMinute;\n\n const eventStart = dayjs(event.start);\n const eventEnd = dayjs(event.end);\n const endsAtNextDayStart = eventEnd.isSame(eventStart.startOf('day').add(1, 'day'), 'second');\n const eventStartMinutes = eventStart.hour() * 60 + eventStart.minute();\n const eventEndMinutes = endsAtNextDayStart ? 24 * 60 : eventEnd.hour() * 60 + eventEnd.minute();\n\n return !(eventEndMinutes <= timeWindowStartMinutes || eventStartMinutes >= timeWindowEndMinutes);\n}\n"],"mappings":";;;;AAUA,SAAS,aAAa,OAA0B;CAC9C,MAAM,QAAQ,MAAM,MAAM,KAAK;CAC/B,MAAM,MAAM,MAAM,MAAM,GAAG;CAC3B,IAAI,CAAC,MAAM,OAAO,MAAM,QAAQ,KAAK,CAAC,GACpC,OAAO;CAET,MAAM,eAAe,MAAM,QAAQ,KAAK,CAAC,CAAC,IAAI,GAAG,KAAK;CACtD,OAAO,IAAI,OAAO,YAAY,KAAK,IAAI,OAAO,aAAa,SAAS,GAAG,QAAQ,CAAC;AAClF;AAEA,SAAgB,mBAAmB,EAAE,OAAO,WAAW,WAAoC;CACzF,IAAI,gBAAgB,KAAK,KAAK,aAAa,KAAK,GAC9C,OAAO;CAGT,IAAI,CAAC,aAAa,CAAC,SACjB,OAAO;CAGT,MAAM,CAAC,WAAW,eAAe,UAAU,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;CAChE,MAAM,CAAC,SAAS,aAAa,QAAQ,MAAM,GAAG,CAAC,CAAC,IAAI,MAAM;CAC1D,MAAM,yBAAyB,YAAY,KAAK;CAChD,MAAM,uBAAuB,UAAU,KAAK;CAE5C,MAAM,aAAa,MAAM,MAAM,KAAK;CACpC,MAAM,WAAW,MAAM,MAAM,GAAG;CAChC,MAAM,qBAAqB,SAAS,OAAO,WAAW,QAAQ,KAAK,CAAC,CAAC,IAAI,GAAG,KAAK,GAAG,QAAQ;CAC5F,MAAM,oBAAoB,WAAW,KAAK,IAAI,KAAK,WAAW,OAAO;CAGrE,OAAO,GAFiB,qBAAqB,OAAU,SAAS,KAAK,IAAI,KAAK,SAAS,OAAO,MAElE,0BAA0B,qBAAqB;AAC7E"}
|
|
@@ -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, ScheduleMode, ScheduleViewLevel } from '../../types';
|
|
3
|
+
import { AnyDateValue, DateLabelFormat, DateStringValue, DateTimeStringValue, ScheduleEventData, ScheduleEventOverlapMode, 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';
|
|
@@ -8,7 +8,7 @@ import { RenderEvent, RenderEventBody, ScheduleEventStylesNames } from '../Sched
|
|
|
8
8
|
import { MonthYearSelectStylesNames } from '../ScheduleHeader/MonthYearSelect/MonthYearSelect';
|
|
9
9
|
import { CombinedScheduleHeaderStylesNames } from '../ScheduleHeader/ScheduleHeader';
|
|
10
10
|
import { ViewSelectProps } from '../ScheduleHeader/ViewSelect/ViewSelect';
|
|
11
|
-
export type DayViewStylesNames = 'dayView' | 'dayViewInner' | 'dayViewScrollArea' | 'dayViewAllDay' | 'dayViewAllDayEvents' | 'dayViewSlot' | 'dayViewSlots' | 'dayViewTimeSlots' | 'dayViewSlotLabel' | 'dayViewSlotLabels' | 'dayViewBackgroundEvent' | 'dayViewDragPreview' | MoreEventsStylesNames | ScheduleEventStylesNames | Exclude<CombinedScheduleHeaderStylesNames, MonthYearSelectStylesNames> | CurrentTimeIndicatorStylesNames | AgendaViewStylesNames;
|
|
11
|
+
export type DayViewStylesNames = 'dayView' | 'dayViewInner' | 'dayViewScrollArea' | 'dayViewAllDay' | 'dayViewAllDayEvents' | 'dayViewSlot' | 'dayViewSlots' | 'dayViewTimeSlots' | 'dayViewSlotLabel' | 'dayViewSlotLabels' | 'dayViewBackgroundEvent' | 'dayViewBackgroundEventResizeHandle' | 'dayViewDragPreview' | MoreEventsStylesNames | ScheduleEventStylesNames | Exclude<CombinedScheduleHeaderStylesNames, MonthYearSelectStylesNames> | CurrentTimeIndicatorStylesNames | AgendaViewStylesNames;
|
|
12
12
|
export type DayViewCssVariables = {
|
|
13
13
|
dayView: '--day-view-radius' | '--day-view-slot-height' | '--day-view-all-day-slot-height';
|
|
14
14
|
};
|
|
@@ -28,6 +28,10 @@ export interface DayViewProps extends BoxProps, StylesApiProps<DayViewFactory>,
|
|
|
28
28
|
intervalMinutes?: number;
|
|
29
29
|
/** If set, grid lines are displayed for intervals smaller than one hour, for example 15 and 30 minutes intervals @default true */
|
|
30
30
|
withSubHourGridLines?: boolean;
|
|
31
|
+
/** Determines how events that overlap in time are laid out: `columns` splits the available width between them, `cascade` indents each event and stacks it over the previous one @default 'columns' */
|
|
32
|
+
eventOverlapMode?: ScheduleEventOverlapMode;
|
|
33
|
+
/** Time in ms the pointer must rest on an event before it is raised above the events covering it, only used with `eventOverlapMode="cascade"` @default 600 */
|
|
34
|
+
eventOverlapRaiseDelay?: number;
|
|
31
35
|
/** If set, the all-day slot is displayed below the header @default true */
|
|
32
36
|
withAllDaySlot?: boolean;
|
|
33
37
|
/** Locale passed down to dayjs, overrides value defined on `DatesProvider` */
|
|
@@ -101,7 +105,7 @@ export interface DayViewProps extends BoxProps, StylesApiProps<DayViewFactory>,
|
|
|
101
105
|
onAllDaySlotClick?: (date: DateStringValue, event: React.MouseEvent<HTMLButtonElement>) => void;
|
|
102
106
|
/** Called when event is clicked */
|
|
103
107
|
onEventClick?: (event: ScheduleEventData, e: React.MouseEvent<HTMLButtonElement>) => void;
|
|
104
|
-
/** If set, background events (`display: 'background'`) can be clicked and trigger `onEventClick` @default false */
|
|
108
|
+
/** If set, background events (`display: 'background'`) can be clicked and trigger `onEventClick`. Combined with `withEventResize`, timed background events can also be resized by dragging their edges. @default false */
|
|
105
109
|
withInteractiveBackgroundEvents?: boolean;
|
|
106
110
|
/** If set, enables drag-to-select time slot ranges @default false */
|
|
107
111
|
withDragSlotSelect?: boolean;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AnyDateValue, DayPositionedEventData, ScheduleEventData } from '../../../types';
|
|
1
|
+
import { AnyDateValue, DayPositionedEventData, ScheduleEventData, ScheduleEventOverlapMode } from '../../../types';
|
|
2
2
|
interface GetDayPositionedEventsInput {
|
|
3
3
|
/** Date at which events are positioned, used to check if events are all-day */
|
|
4
4
|
date: AnyDateValue;
|
|
@@ -10,6 +10,8 @@ interface GetDayPositionedEventsInput {
|
|
|
10
10
|
endTime?: string;
|
|
11
11
|
/** Number of minutes per time slot, used to align the canvas to whole slots */
|
|
12
12
|
intervalMinutes?: number;
|
|
13
|
+
/** Determines how events that overlap in time are laid out @default 'columns' */
|
|
14
|
+
eventOverlapMode?: ScheduleEventOverlapMode;
|
|
13
15
|
}
|
|
14
|
-
export declare function getDayPositionedEvents({ events, startTime, endTime, intervalMinutes, date, }: GetDayPositionedEventsInput): DayPositionedEventData[];
|
|
16
|
+
export declare function getDayPositionedEvents({ events, startTime, endTime, intervalMinutes, date, eventOverlapMode, }: GetDayPositionedEventsInput): DayPositionedEventData[];
|
|
15
17
|
export {};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { AnyDateValue, DayPositionedEventData, ScheduleEventData } from '../../../types';
|
|
1
|
+
import { AnyDateValue, DayPositionedEventData, ScheduleEventData, ScheduleEventOverlapMode } from '../../../types';
|
|
2
2
|
interface GetDayViewEventsInput {
|
|
3
3
|
events: ScheduleEventData[] | undefined;
|
|
4
4
|
date: AnyDateValue;
|
|
5
5
|
startTime?: string;
|
|
6
6
|
endTime?: string;
|
|
7
7
|
intervalMinutes?: number;
|
|
8
|
+
eventOverlapMode?: ScheduleEventOverlapMode;
|
|
8
9
|
}
|
|
9
|
-
export declare function getDayViewEvents({ events, date, startTime, endTime, intervalMinutes, }: GetDayViewEventsInput): {
|
|
10
|
+
export declare function getDayViewEvents({ events, date, startTime, endTime, intervalMinutes, eventOverlapMode, }: GetDayViewEventsInput): {
|
|
10
11
|
allDayEvents: DayPositionedEventData[];
|
|
11
12
|
regularEvents: DayPositionedEventData[];
|
|
12
13
|
backgroundTimedEvents: DayPositionedEventData[];
|
|
@@ -106,7 +106,7 @@ export interface ResourcesDayViewProps extends BoxProps, StylesApiProps<Resource
|
|
|
106
106
|
}) => void;
|
|
107
107
|
/** Called when event is clicked */
|
|
108
108
|
onEventClick?: (event: ScheduleEventData, e: React.MouseEvent<HTMLButtonElement>) => void;
|
|
109
|
-
/** If set, background events (`display: 'background'`) can be clicked and trigger `onEventClick` @default false */
|
|
109
|
+
/** If set, background events (`display: 'background'`) can be clicked and trigger `onEventClick`. Combined with `withEventResize`, timed background events can also be resized by dragging their edges. @default false */
|
|
110
110
|
withInteractiveBackgroundEvents?: boolean;
|
|
111
111
|
/** If set, enables drag-to-select time slot ranges @default false */
|
|
112
112
|
withDragSlotSelect?: boolean;
|
|
@@ -95,7 +95,7 @@ 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
|
-
/** 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. @default false */
|
|
98
|
+
/** 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
99
|
withInteractiveBackgroundEvents?: boolean;
|
|
100
100
|
/** Max number of generated recurring instances @default 2000 */
|
|
101
101
|
recurrenceExpansionLimit?: number;
|
|
@@ -76,7 +76,7 @@ export interface ResourcesWeekViewProps extends BoxProps, StylesApiProps<Resourc
|
|
|
76
76
|
resourceId?: string | number;
|
|
77
77
|
}) => void;
|
|
78
78
|
onEventClick?: (event: ScheduleEventData, e: React.MouseEvent<HTMLButtonElement>) => void;
|
|
79
|
-
/** If set, background events (`display: 'background'`) can be clicked and trigger `onEventClick` @default false */
|
|
79
|
+
/** If set, background events (`display: 'background'`) can be clicked and trigger `onEventClick`. Combined with `withEventResize`, timed background events can also be resized by dragging their edges. @default false */
|
|
80
80
|
withInteractiveBackgroundEvents?: boolean;
|
|
81
81
|
withDragSlotSelect?: boolean;
|
|
82
82
|
onSlotDragEnd?: (data: {
|
|
@@ -84,7 +84,7 @@ 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
|
-
/** 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. @default false */
|
|
87
|
+
/** 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
88
|
withInteractiveBackgroundEvents?: boolean;
|
|
89
89
|
/** Max number of generated recurring instances per recurring series @default 2000 */
|
|
90
90
|
recurrenceExpansionLimit?: number;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { BoxProps, ElementProps } from '@mantine/core';
|
|
2
2
|
import { ScheduleEventData } from '../../types';
|
|
3
3
|
import { RenderEvent, RenderEventBody } from '../ScheduleEvent/ScheduleEvent';
|
|
4
|
-
export
|
|
4
|
+
export type ScheduleBackgroundEventResizeEdge = 'top' | 'bottom' | 'start' | 'end';
|
|
5
|
+
export interface ScheduleBackgroundEventProps<Edge extends ScheduleBackgroundEventResizeEdge = ScheduleBackgroundEventResizeEdge> extends BoxProps, ElementProps<'div', 'onClick'> {
|
|
5
6
|
/** Background event to display */
|
|
6
7
|
event: ScheduleEventData;
|
|
7
8
|
/** If set, the root element is a `button` instead of a `div` and the event can be clicked @default false */
|
|
@@ -12,8 +13,20 @@ export interface ScheduleBackgroundEventProps extends BoxProps, ElementProps<'di
|
|
|
12
13
|
renderEvent?: RenderEvent;
|
|
13
14
|
/** Function to customize event body */
|
|
14
15
|
renderEventBody?: RenderEventBody;
|
|
16
|
+
/** If set, the event renders resize handles on both of its edges @default false */
|
|
17
|
+
withResize?: boolean;
|
|
18
|
+
/** Axis along which the event is resized, defines which edges render handles @default 'vertical' */
|
|
19
|
+
resizeAxis?: 'vertical' | 'horizontal';
|
|
20
|
+
/** Props passed down to both resize handles, usually a styles api props object */
|
|
21
|
+
resizeHandleProps?: Record<string, any>;
|
|
22
|
+
/** Called when resize starts on an edge */
|
|
23
|
+
onResizeStart?: (edge: Edge, e: React.PointerEvent) => void;
|
|
24
|
+
/** If set, the event is currently being resized @default false */
|
|
25
|
+
isResizing?: boolean;
|
|
26
|
+
/** Edge that is currently being dragged, only relevant when `isResizing` is set */
|
|
27
|
+
activeResizeEdge?: Edge | null;
|
|
15
28
|
}
|
|
16
|
-
export declare function ScheduleBackgroundEvent(props: ScheduleBackgroundEventProps): import("react").JSX.Element;
|
|
29
|
+
export declare function ScheduleBackgroundEvent<Edge extends ScheduleBackgroundEventResizeEdge = ScheduleBackgroundEventResizeEdge>(props: ScheduleBackgroundEventProps<Edge>): import("react").JSX.Element;
|
|
17
30
|
export declare namespace ScheduleBackgroundEvent {
|
|
18
31
|
var displayName: string;
|
|
19
32
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
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, ScheduleMode, ScheduleViewLevel } from '../../types';
|
|
3
|
+
import { AnyDateValue, DateLabelFormat, DateStringValue, DateTimeStringValue, DayOfWeek, ScheduleEventData, ScheduleEventOverlapMode, ScheduleMode, ScheduleViewLevel } from '../../types';
|
|
4
4
|
import { BusinessHoursValue } from '../../utils';
|
|
5
5
|
import { AgendaViewStylesNames } from '../AgendaView/AgendaView';
|
|
6
6
|
import { CurrentTimeIndicatorStylesNames } from '../CurrentTimeIndicator/CurrentTimeIndicator';
|
|
7
7
|
import { RenderEvent, RenderEventBody } from '../ScheduleEvent/ScheduleEvent';
|
|
8
8
|
import { CombinedScheduleHeaderStylesNames } from '../ScheduleHeader/ScheduleHeader';
|
|
9
9
|
import { ViewSelectProps } from '../ScheduleHeader/ViewSelect/ViewSelect';
|
|
10
|
-
export type WeekViewStylesNames = 'weekView' | 'weekViewRoot' | 'weekViewHeader' | 'weekViewInner' | 'weekViewAllDaySlotsEvents' | 'weekViewAllDaySlots' | 'weekViewAllDaySlotsList' | 'weekViewAllDaySlot' | 'weekViewAllDaySlotsLabel' | 'weekViewScrollArea' | 'weekViewCorner' | 'weekViewSlotLabels' | 'weekViewSlotLabel' | 'weekViewDayLabel' | 'weekViewDayWeekday' | 'weekViewDay' | 'weekViewDayNumber' | 'weekViewDaySlot' | 'weekViewDaySlots' | 'weekViewWeekLabel' | 'weekViewWeekNumber' | 'weekViewBackgroundEvent' | 'weekViewDragPreview' | CurrentTimeIndicatorStylesNames | CombinedScheduleHeaderStylesNames | AgendaViewStylesNames;
|
|
10
|
+
export type WeekViewStylesNames = 'weekView' | 'weekViewRoot' | 'weekViewHeader' | 'weekViewInner' | 'weekViewAllDaySlotsEvents' | 'weekViewAllDaySlots' | 'weekViewAllDaySlotsList' | 'weekViewAllDaySlot' | 'weekViewAllDaySlotsLabel' | 'weekViewScrollArea' | 'weekViewCorner' | 'weekViewSlotLabels' | 'weekViewSlotLabel' | 'weekViewDayLabel' | 'weekViewDayWeekday' | 'weekViewDay' | 'weekViewDayNumber' | 'weekViewDaySlot' | 'weekViewDaySlots' | 'weekViewWeekLabel' | 'weekViewWeekNumber' | 'weekViewBackgroundEvent' | 'weekViewBackgroundEventResizeHandle' | 'weekViewDragPreview' | CurrentTimeIndicatorStylesNames | CombinedScheduleHeaderStylesNames | AgendaViewStylesNames;
|
|
11
11
|
export type WeekViewCssVariables = {
|
|
12
12
|
weekView: '--week-view-radius' | '--week-view-slot-height' | '--week-view-all-day-slots-height';
|
|
13
13
|
};
|
|
@@ -35,6 +35,10 @@ export interface WeekViewProps extends BoxProps, StylesApiProps<WeekViewFactory>
|
|
|
35
35
|
weekendDays?: DayOfWeek[];
|
|
36
36
|
/** If set to false, weekend days are hidden @default true */
|
|
37
37
|
withWeekendDays?: boolean;
|
|
38
|
+
/** Determines how events that overlap in time are laid out: `columns` splits the available width between them, `cascade` indents each event and stacks it over the previous one @default 'columns' */
|
|
39
|
+
eventOverlapMode?: ScheduleEventOverlapMode;
|
|
40
|
+
/** Time in ms the pointer must rest on an event before it is raised above the events covering it, only used with `eventOverlapMode="cascade"` @default 600 */
|
|
41
|
+
eventOverlapRaiseDelay?: number;
|
|
38
42
|
/** If set to true, highlights today in the weekday row @default false */
|
|
39
43
|
highlightToday?: boolean;
|
|
40
44
|
/** Key of `theme.radius` or any valid CSS value to set `border-radius` @default theme.defaultRadius */
|
|
@@ -112,7 +116,7 @@ export interface WeekViewProps extends BoxProps, StylesApiProps<WeekViewFactory>
|
|
|
112
116
|
onAllDaySlotClick?: (day: DateStringValue, event: React.MouseEvent<HTMLButtonElement>) => void;
|
|
113
117
|
/** Called when event is clicked */
|
|
114
118
|
onEventClick?: (event: ScheduleEventData, e: React.MouseEvent<HTMLButtonElement>) => void;
|
|
115
|
-
/** If set, background events (`display: 'background'`) can be clicked and trigger `onEventClick` @default false */
|
|
119
|
+
/** If set, background events (`display: 'background'`) can be clicked and trigger `onEventClick`. Combined with `withEventResize`, timed background events can also be resized by dragging their edges. @default false */
|
|
116
120
|
withInteractiveBackgroundEvents?: boolean;
|
|
117
121
|
/** If set, enables drag-to-select time slot ranges @default false */
|
|
118
122
|
withDragSlotSelect?: boolean;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AnyDateValue, DateStringValue, DayOfWeek, ScheduleEventData, WeekPositionedEventData } from '../../../types';
|
|
1
|
+
import { AnyDateValue, DateStringValue, DayOfWeek, ScheduleEventData, ScheduleEventOverlapMode, WeekPositionedEventData } from '../../../types';
|
|
2
2
|
export interface GetWeekPositionedEventsInput {
|
|
3
3
|
/** Date (week start) at which events are positioned, used to check if events are all-day */
|
|
4
4
|
date: AnyDateValue;
|
|
@@ -16,6 +16,8 @@ export interface GetWeekPositionedEventsInput {
|
|
|
16
16
|
weekendDays?: DayOfWeek[];
|
|
17
17
|
/** If set to false, weekend days are hidden @default true */
|
|
18
18
|
withWeekendDays?: boolean;
|
|
19
|
+
/** Determines how events that overlap in time are laid out @default 'columns' */
|
|
20
|
+
eventOverlapMode?: ScheduleEventOverlapMode;
|
|
19
21
|
}
|
|
20
22
|
/** Events grouped by week day date (YYYY-MM-DD 00:00:00) and by columns */
|
|
21
23
|
export interface GroupedWeekEvents {
|
|
@@ -23,4 +25,4 @@ export interface GroupedWeekEvents {
|
|
|
23
25
|
regularEvents: Record<DateStringValue, WeekPositionedEventData[]>;
|
|
24
26
|
backgroundEvents: Record<DateStringValue, WeekPositionedEventData[]>;
|
|
25
27
|
}
|
|
26
|
-
export declare function getWeekPositionedEvents({ date, events, startTime, endTime, intervalMinutes, firstDayOfWeek, weekendDays, withWeekendDays, }: GetWeekPositionedEventsInput): GroupedWeekEvents;
|
|
28
|
+
export declare function getWeekPositionedEvents({ date, events, startTime, endTime, intervalMinutes, firstDayOfWeek, weekendDays, withWeekendDays, eventOverlapMode, }: GetWeekPositionedEventsInput): GroupedWeekEvents;
|
|
@@ -14,8 +14,9 @@ export interface UseEventResizeInput {
|
|
|
14
14
|
event: ScheduleEventData;
|
|
15
15
|
}) => void;
|
|
16
16
|
canResizeEvent?: (event: ScheduleEventData) => boolean;
|
|
17
|
+
withBackgroundEvents?: boolean;
|
|
17
18
|
}
|
|
18
|
-
export declare function useEventResize({ enabled, mode, startTime, endTime, intervalMinutes, resizeIntervalMinutes, onEventResize, canResizeEvent, }: UseEventResizeInput): {
|
|
19
|
+
export declare function useEventResize({ enabled, mode, startTime, endTime, intervalMinutes, resizeIntervalMinutes, onEventResize, canResizeEvent, withBackgroundEvents, }: UseEventResizeInput): {
|
|
19
20
|
handleResizeStart: ({ event, edge, container, originalTop, originalHeight, eventDate, pointerEvent, }: {
|
|
20
21
|
event: ScheduleEventData;
|
|
21
22
|
edge: ResizeEdge;
|
|
@@ -27,7 +28,7 @@ export declare function useEventResize({ enabled, mode, startTime, endTime, inte
|
|
|
27
28
|
}) => void;
|
|
28
29
|
isResizing: boolean;
|
|
29
30
|
resizingEventId: string | number | null;
|
|
30
|
-
getResizePosition: (eventId: string | number) => {
|
|
31
|
+
getResizePosition: (eventId: string | number, eventDate?: string) => {
|
|
31
32
|
top: number;
|
|
32
33
|
height: number;
|
|
33
34
|
} | null;
|
|
@@ -14,8 +14,9 @@ export interface UseHorizontalEventResizeInput {
|
|
|
14
14
|
event: ScheduleEventData;
|
|
15
15
|
}) => void;
|
|
16
16
|
canResizeEvent?: (event: ScheduleEventData) => boolean;
|
|
17
|
+
withBackgroundEvents?: boolean;
|
|
17
18
|
}
|
|
18
|
-
export declare function useHorizontalEventResize({ enabled, mode, startTime, endTime, intervalMinutes, resizeIntervalMinutes, onEventResize, canResizeEvent, }: UseHorizontalEventResizeInput): {
|
|
19
|
+
export declare function useHorizontalEventResize({ enabled, mode, startTime, endTime, intervalMinutes, resizeIntervalMinutes, onEventResize, canResizeEvent, withBackgroundEvents, }: UseHorizontalEventResizeInput): {
|
|
19
20
|
handleResizeStart: ({ event, edge, container, originalLeft, originalWidth, eventDate, dayIndex, dayCount, pointerEvent, }: {
|
|
20
21
|
event: ScheduleEventData;
|
|
21
22
|
edge: ResizeEdge;
|
|
@@ -30,7 +31,7 @@ export declare function useHorizontalEventResize({ enabled, mode, startTime, end
|
|
|
30
31
|
isResizing: boolean;
|
|
31
32
|
resizingEventId: string | number | null;
|
|
32
33
|
resizingEdge: ResizeEdge | null;
|
|
33
|
-
getResizePosition: (eventId: string | number) => {
|
|
34
|
+
getResizePosition: (eventId: string | number, eventDate?: string) => {
|
|
34
35
|
left: number;
|
|
35
36
|
width: number;
|
|
36
37
|
} | null;
|
package/lib/types.d.ts
CHANGED
|
@@ -102,6 +102,8 @@ interface ScheduleEventRuntimeMeta {
|
|
|
102
102
|
}
|
|
103
103
|
/** Event data object passed to all `@mantine/schedule` components */
|
|
104
104
|
export type ScheduleEventData<Payload extends EventPayload = EventPayload> = (ScheduleSingleEventData<Payload> & ScheduleEventRuntimeMeta) | (ScheduleRecurringSeriesEventData<Payload> & ScheduleEventRuntimeMeta) | (ScheduleRecurringOverrideEventData<Payload> & ScheduleEventRuntimeMeta);
|
|
105
|
+
/** Controls how events that overlap in time are laid out along the horizontal axis */
|
|
106
|
+
export type ScheduleEventOverlapMode = 'columns' | 'cascade';
|
|
105
107
|
export interface DayEventPositionData {
|
|
106
108
|
/** All day events */
|
|
107
109
|
allDay: boolean;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { DayEventPositionData, ScheduleEventData } from '../../types';
|
|
2
|
+
/** Minimal shape `applyCascadeLayout` needs, shared by day and week positioned events */
|
|
3
|
+
export type CascadePositionedEvent = ScheduleEventData & {
|
|
4
|
+
position: Pick<DayEventPositionData, 'allDay' | 'column' | 'width' | 'offset' | 'overlaps'>;
|
|
5
|
+
};
|
|
6
|
+
export declare function applyCascadeLayout<T extends CascadePositionedEvent>(events: T[]): void;
|
package/lib/utils/index.d.ts
CHANGED
|
@@ -47,3 +47,6 @@ export type { HandleResourcesGridKeyDownInput, ResourcesGridControlsRef, } from
|
|
|
47
47
|
export { getGroupToResourceIdMap } from './get-group-to-resource-id-map/get-group-to-resource-id-map';
|
|
48
48
|
export { getIndexFromDragPoint } from './get-index-from-drag-point/get-index-from-drag-point';
|
|
49
49
|
export { getDayRelativePercent } from './get-day-relative-percent/get-day-relative-percent';
|
|
50
|
+
export { getOverlapClusters } from './get-overlap-clusters/get-overlap-clusters';
|
|
51
|
+
export { applyCascadeLayout } from './apply-cascade-layout/apply-cascade-layout';
|
|
52
|
+
export type { CascadePositionedEvent } from './apply-cascade-layout/apply-cascade-layout';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mantine/schedule",
|
|
3
|
-
"version": "9.6.
|
|
3
|
+
"version": "9.6.2",
|
|
4
4
|
"description": "Schedule Mantine component",
|
|
5
5
|
"homepage": "https://mantine.dev/",
|
|
6
6
|
"license": "MIT",
|
|
@@ -35,9 +35,9 @@
|
|
|
35
35
|
"directory": "packages/@mantine/schedule"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|
|
38
|
-
"@mantine/core": "9.6.
|
|
39
|
-
"@mantine/dates": "9.6.
|
|
40
|
-
"@mantine/hooks": "9.6.
|
|
38
|
+
"@mantine/core": "9.6.2",
|
|
39
|
+
"@mantine/dates": "9.6.2",
|
|
40
|
+
"@mantine/hooks": "9.6.2",
|
|
41
41
|
"react": "^19.2.0",
|
|
42
42
|
"react-dom": "^19.2.0"
|
|
43
43
|
},
|