From 805ec3aceada66310274199a308c45102fbc7900 Mon Sep 17 00:00:00 2001 From: Andy Cork Date: Fri, 21 Feb 2025 15:36:15 +0000 Subject: [PATCH 1/7] Allow an event to have its length changed with a drag handle --- src/events.tsx | 13 + src/lib/components/events/EventItem.tsx | 48 ++- src/lib/components/events/TodayEvents.tsx | 2 +- src/lib/components/week/WeekTable.tsx | 16 +- src/lib/helpers/generals.tsx | 5 + src/lib/hooks/useEventPermissions.ts | 23 +- src/lib/hooks/useResizeAttributes.ts | 50 +++ src/lib/store/default.ts | 4 + src/lib/store/provider.tsx | 397 +++++++++++++--------- src/lib/store/types.ts | 8 + src/lib/styles/styles.ts | 11 + src/lib/types.ts | 14 + 12 files changed, 424 insertions(+), 167 deletions(-) create mode 100644 src/lib/hooks/useResizeAttributes.ts diff --git a/src/events.tsx b/src/events.tsx index f28a57b3..56fc62fa 100644 --- a/src/events.tsx +++ b/src/events.tsx @@ -148,6 +148,19 @@ export const EVENTS: ProcessedEvent[] = [ }), color: "#dc4552", }, + { + event_id: 11, + title: "Event 11", + subtitle: "This event is not resizable", + start: new Date( + new Date(new Date(new Date().setHours(10)).setMinutes(30)).setDate(new Date().getDate() - 4) + ), + end: new Date( + new Date(new Date(new Date().setHours(12)).setMinutes(30)).setDate(new Date().getDate() - 4) + ), + admin_id: 1, + resizable: false, + }, ]; export const RESOURCES = [ diff --git a/src/lib/components/events/EventItem.tsx b/src/lib/components/events/EventItem.tsx index 69abfa75..5ddfbb15 100644 --- a/src/lib/components/events/EventItem.tsx +++ b/src/lib/components/events/EventItem.tsx @@ -1,15 +1,16 @@ -import { Fragment, MouseEvent, useCallback, useMemo, useState } from "react"; -import { Typography, ButtonBase, useTheme } from "@mui/material"; +import { Fragment, MouseEvent, useCallback, useMemo, useRef, useState } from "react"; +import { Typography, ButtonBase, useTheme, Popper } from "@mui/material"; import { format } from "date-fns"; import { ProcessedEvent } from "../../types"; import ArrowRightRoundedIcon from "@mui/icons-material/ArrowRightRounded"; import ArrowLeftRoundedIcon from "@mui/icons-material/ArrowLeftRounded"; -import { EventItemPaper } from "../../styles/styles"; +import { DragHandle, EventItemPaper } from "../../styles/styles"; import { differenceInDaysOmitTime, getHourFormat } from "../../helpers/generals"; import useStore from "../../hooks/useStore"; import useDragAttributes from "../../hooks/useDragAttributes"; import EventItemPopover from "./EventItemPopover"; import useEventPermissions from "../../hooks/useEventPermissions"; +import useResizeAttributes from "../../hooks/useResizeAttributes"; interface EventItemProps { event: ProcessedEvent; @@ -17,12 +18,27 @@ interface EventItemProps { hasPrev?: boolean; hasNext?: boolean; showdate?: boolean; + minuteHeight?: number; } -const EventItem = ({ event, multiday, hasPrev, hasNext, showdate = true }: EventItemProps) => { +const EventItem = ({ + event, + multiday, + hasPrev, + hasNext, + showdate = true, + minuteHeight, +}: EventItemProps) => { const { direction, locale, hourFormat, eventRenderer, onEventClick, view, disableViewer } = useStore(); + const dragHandleRef = useRef(null); + const [dragTime, setDragTime] = useState(); + const onDragMove = useCallback((time: Date | undefined) => { + setDragTime(time); + }, []); + const dragProps = useDragAttributes(event); + const resizeProps = useResizeAttributes(event, minuteHeight, onDragMove); const [anchorEl, setAnchorEl] = useState(null); const [deleteConfirm, setDeleteConfirm] = useState(false); const theme = useTheme(); @@ -32,7 +48,11 @@ const EventItem = ({ event, multiday, hasPrev, hasNext, showdate = true }: Event const PrevArrow = direction === "rtl" ? ArrowRightRoundedIcon : ArrowLeftRoundedIcon; const hideDates = differenceInDaysOmitTime(event.start, event.end) <= 0 && event.allDay; - const { canDrag } = useEventPermissions(event); + const { canDrag, canResize } = useEventPermissions(event); + const resizable = useMemo( + () => !!canResize && !!minuteHeight && !multiday, + [canResize, minuteHeight, multiday] + ); const triggerViewer = useCallback( (el?: MouseEvent) => { @@ -53,6 +73,7 @@ const EventItem = ({ event, multiday, hasPrev, hasNext, showdate = true }: Event return ( {custom} + ); } @@ -124,9 +145,7 @@ const EventItem = ({ event, multiday, hasPrev, hasNext, showdate = true }: Event if (!disableViewer) { triggerViewer(e); } - if (typeof onEventClick === "function") { - onEventClick(event); - } + onEventClick?.(event); }} focusRipple tabIndex={disableViewer ? -1 : 0} @@ -137,6 +156,7 @@ const EventItem = ({ event, multiday, hasPrev, hasNext, showdate = true }: Event {item} + ); }, [ @@ -159,6 +179,8 @@ const EventItem = ({ event, multiday, hasPrev, hasNext, showdate = true }: Event hasNext, NextArrow, onEventClick, + resizable, + resizeProps, ]); return ( @@ -167,6 +189,16 @@ const EventItem = ({ event, multiday, hasPrev, hasNext, showdate = true }: Event {/* Viewer */} + + + {dragTime ? format(dragTime, hFormat, { locale }) : null} + + ); }; diff --git a/src/lib/components/events/TodayEvents.tsx b/src/lib/components/events/TodayEvents.tsx index 8b20b87b..8d16bd55 100644 --- a/src/lib/components/events/TodayEvents.tsx +++ b/src/lib/components/events/TodayEvents.tsx @@ -80,7 +80,7 @@ const TodayEvents = ({ : "", }} > - + ); })} diff --git a/src/lib/components/week/WeekTable.tsx b/src/lib/components/week/WeekTable.tsx index 510604b1..46a26813 100644 --- a/src/lib/components/week/WeekTable.tsx +++ b/src/lib/components/week/WeekTable.tsx @@ -6,6 +6,7 @@ import { filterMultiDaySlot, filterTodayEvents, getHourFormat, + preventDragEvent, } from "../../helpers/generals"; import { MULTI_DAY_EVENT_HEIGHT } from "../../helpers/constants"; import { DefaultResource, ProcessedEvent } from "../../types"; @@ -119,7 +120,13 @@ const WeekTable = ({ overflowX: "hidden", }} > - + ); }); @@ -170,7 +177,12 @@ const WeekTable = ({ const end = addMinutes(start, step); const field = resourceFields.idField; return ( - + {/* Events of each day - run once on the top hour column */} {i === 0 && ( ): View => { if (state.month) { @@ -126,6 +127,10 @@ export const differenceInDaysOmitTime = (start: Date, end: Date) => { return differenceInDays(endOfDay(addSeconds(end, -1)), startOfDay(start)); }; +export const preventDragEvent = (ev: DragEvent) => { + ev.preventDefault(); +}; + export const convertDateToRRuleDate = (date: Date) => { return datetime( date.getFullYear(), diff --git a/src/lib/hooks/useEventPermissions.ts b/src/lib/hooks/useEventPermissions.ts index 268f09ab..9f7657c1 100644 --- a/src/lib/hooks/useEventPermissions.ts +++ b/src/lib/hooks/useEventPermissions.ts @@ -2,8 +2,15 @@ import { useMemo } from "react"; import { ProcessedEvent } from "../types"; import useStore from "./useStore"; -const useEventPermissions = (event: ProcessedEvent) => { - const { editable, deletable, draggable } = useStore(); +type UseEventPermissions = { + canEdit?: boolean; + canDelete?: boolean; + canDrag?: boolean; + canResize?: boolean; +}; + +const useEventPermissions = (event: ProcessedEvent): UseEventPermissions => { + const { editable, deletable, draggable, resizable } = useStore(); const canEdit = useMemo(() => { // Priority control to event specific editable value @@ -32,10 +39,22 @@ const useEventPermissions = (event: ProcessedEvent) => { return draggable; }, [draggable, event.draggable, canEdit]); + const canResize = useMemo(() => { + if (!canEdit) { + return; + } + // Priority control to event specific draggable value + if (typeof event.resizable !== "undefined") { + return event.resizable; + } + return resizable; + }, [resizable, event.resizable, canEdit]); + return { canEdit, canDelete, canDrag, + canResize, }; }; diff --git a/src/lib/hooks/useResizeAttributes.ts b/src/lib/hooks/useResizeAttributes.ts new file mode 100644 index 00000000..32e485ec --- /dev/null +++ b/src/lib/hooks/useResizeAttributes.ts @@ -0,0 +1,50 @@ +import { DragEvent, useMemo } from "react"; +import { ProcessedEvent } from "../types"; +import useStore from "./useStore"; + +const img = new Image(); +img.style.pointerEvents = "none"; +img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="; + +const useResizeAttributes = ( + event: ProcessedEvent, + minuteHeight?: number, + onDragMove?: (time: Date | undefined) => void +) => { + const { setCurrentResize, onResize, onResizeEnd } = useStore(); + const handlers = useMemo( + () => + minuteHeight + ? { + draggable: true, + onDragStart: (e: DragEvent) => { + e.stopPropagation(); + setCurrentResize(event); + e.dataTransfer.setDragImage(img, 0, 0); + }, + onDragEnd: (e: DragEvent) => { + setCurrentResize(); + onResizeEnd(e, event, minuteHeight); + onDragMove?.(undefined); + }, + onDrag: (e: DragEvent) => { + e.stopPropagation(); + e.preventDefault(); + onDragMove?.(onResize(e, event, minuteHeight)); + }, + onDragOver: (e: DragEvent) => { + e.stopPropagation(); + e.preventDefault(); + }, + onDragEnter: (e: DragEvent) => { + e.stopPropagation(); + e.preventDefault(); + }, + } + : { draggable: false }, + [event, minuteHeight, onResize, setCurrentResize, onResizeEnd, onDragMove] + ); + return handlers; +}; + +export default useResizeAttributes; diff --git a/src/lib/store/default.ts b/src/lib/store/default.ts index fc06a39a..cc92c7a2 100644 --- a/src/lib/store/default.ts +++ b/src/lib/store/default.ts @@ -130,6 +130,7 @@ export const defaultProps = (props: Partial) => { locale: enUS, deletable: true, editable: true, + resizable: true, hourFormat: "12", draggable: true, agenda, @@ -155,5 +156,8 @@ export const initialStore = { handleGotoDay: () => {}, confirmEvent: () => {}, setCurrentDragged: () => {}, + setCurrentResize: () => {}, onDrop: () => {}, + onResize: () => undefined, + onResizeEnd: () => {}, }; diff --git a/src/lib/store/provider.tsx b/src/lib/store/provider.tsx index e095f241..3786bac0 100644 --- a/src/lib/store/provider.tsx +++ b/src/lib/store/provider.tsx @@ -1,4 +1,4 @@ -import { DragEvent, useEffect, useState } from "react"; +import { DragEvent, useCallback, useEffect, useMemo, useState } from "react"; import { EventActions, ProcessedEvent, SchedulerProps } from "../types"; import { defaultProps, initialStore } from "./default"; import { StoreContext } from "./context"; @@ -20,19 +20,23 @@ export const StoreProvider = ({ children, initial }: Props) => { ...prev, onEventDrop: initial.onEventDrop, customEditor: initial.customEditor, + onEventResize: initial.onEventResize, events: initial.events || [], })); - }, [initial.onEventDrop, initial.customEditor, initial.events]); + }, [initial.onEventDrop, initial.customEditor, initial.events, initial.onEventResize]); - const handleState = (value: SchedulerState[keyof SchedulerState], name: keyof SchedulerState) => { - set((prev) => ({ ...prev, [name]: value })); - }; + const handleState = useCallback( + (value: SchedulerState[keyof SchedulerState], name: keyof SchedulerState) => { + set((prev) => ({ ...prev, [name]: value })); + }, + [] + ); - const getViews = () => { + const getViews = useCallback(() => { return getAvailableViews(state); - }; + }, [state]); - const toggleAgenda = () => { + const toggleAgenda = useCallback(() => { set((prev) => { const newStatus = !prev.agenda; @@ -42,163 +46,248 @@ export const StoreProvider = ({ children, initial }: Props) => { return { ...prev, agenda: newStatus }; }); - }; + }, [state]); - const triggerDialog = (status: boolean, selected?: SelectedRange | ProcessedEvent) => { - const isEvent = selected as ProcessedEvent; + const triggerDialog = useCallback( + (status: boolean, selected?: SelectedRange | ProcessedEvent) => { + const isEvent = selected as ProcessedEvent; - set((prev) => ({ - ...prev, - dialog: status, - selectedRange: isEvent?.event_id - ? undefined - : isEvent || { - start: new Date(), - end: new Date(Date.now() + 60 * 60 * 1000), - }, - selectedEvent: isEvent?.event_id ? isEvent : undefined, - selectedResource: prev.selectedResource || isEvent?.[state.resourceFields?.idField], - })); - }; - - const triggerLoading = (status: boolean) => { - // Trigger if not out-sourced by props - if (typeof initial.loading === "undefined") { - set((prev) => ({ ...prev, loading: status })); - } - }; - - const handleGotoDay = (day: Date) => { - const currentViews = getViews(); - let view: View | undefined; - if (currentViews.includes("day")) { - view = "day"; - set((prev) => ({ ...prev, view: "day", selectedDate: day })); - } else if (currentViews.includes("week")) { - view = "week"; - set((prev) => ({ ...prev, view: "week", selectedDate: day })); - } else { - console.warn("No Day/Week views available"); - } - - if (!!view && state.onViewChange && typeof state.onViewChange === "function") { - state.onViewChange(view, state.agenda); - } - - if (!!view && state.onSelectedDateChange && typeof state.onSelectedDateChange === "function") { - state.onSelectedDateChange(day); - } - }; - - const confirmEvent = (event: ProcessedEvent | ProcessedEvent[], action: EventActions) => { - let updatedEvents: ProcessedEvent[]; - if (action === "edit") { - if (Array.isArray(event)) { - updatedEvents = state.events.map((e) => { - const exist = event.find((ex) => ex.event_id === e.event_id); - return exist ? { ...e, ...exist } : e; - }); + set((prev) => ({ + ...prev, + dialog: status, + selectedRange: isEvent?.event_id + ? undefined + : isEvent || { + start: new Date(), + end: new Date(Date.now() + 60 * 60 * 1000), + }, + selectedEvent: isEvent?.event_id ? isEvent : undefined, + selectedResource: prev.selectedResource || isEvent?.[state.resourceFields?.idField], + })); + }, + [state.resourceFields?.idField] + ); + + const triggerLoading = useCallback( + (status: boolean) => { + // Trigger if not out-sourced by props + if (typeof initial.loading === "undefined") { + set((prev) => ({ ...prev, loading: status })); + } + }, + [initial.loading] + ); + + const handleGotoDay = useCallback( + (day: Date) => { + const currentViews = getViews(); + let view: View | undefined; + if (currentViews.includes("day")) { + view = "day"; + set((prev) => ({ ...prev, view: "day", selectedDate: day })); + } else if (currentViews.includes("week")) { + view = "week"; + set((prev) => ({ ...prev, view: "week", selectedDate: day })); } else { - updatedEvents = state.events.map((e) => - e.event_id === event.event_id ? { ...e, ...event } : e - ); + console.warn("No Day/Week views available"); } - } else { - updatedEvents = state.events.concat(event); - } - set((prev) => ({ ...prev, events: updatedEvents })); - }; - const setCurrentDragged = (event?: ProcessedEvent) => { - set((prev) => ({ ...prev, currentDragged: event })); - }; - - const onDrop = async ( - event: DragEvent, - eventId: string, - startTime: Date, - resKey?: string, - resVal?: string | number - ) => { - // Get dropped event - const droppedEvent = state.events.find((e) => { - if (typeof e.event_id === "number") { - return e.event_id === +eventId; + if (!!view && state.onViewChange && typeof state.onViewChange === "function") { + state.onViewChange(view, state.agenda); } - return e.event_id === eventId; - }) as ProcessedEvent; - - // Check if has resource and if is multiple - const resField = state.fields.find((f) => f.name === resKey); - const isMultiple = !!resField?.config?.multiple; - let newResource = resVal as string | number | string[] | number[]; - if (resField) { - const eResource = droppedEvent[resKey as string]; - const currentRes = arraytizeFieldVal(resField, eResource, droppedEvent).value; - if (isMultiple) { - // if dropped on already owned resource - if (currentRes.includes(resVal)) { - // Omit if dropped on same time slot for multiple event - if (isEqual(droppedEvent.start, startTime)) { - return; - } - newResource = currentRes; + + if ( + !!view && + state.onSelectedDateChange && + typeof state.onSelectedDateChange === "function" + ) { + state.onSelectedDateChange(day); + } + }, + [getViews, state] + ); + + const confirmEvent = useCallback( + (event: ProcessedEvent | ProcessedEvent[], action: EventActions) => { + let updatedEvents: ProcessedEvent[]; + if (action === "edit") { + if (Array.isArray(event)) { + updatedEvents = state.events.map((e) => { + const exist = event.find((ex) => ex.event_id === e.event_id); + return exist ? { ...e, ...exist } : e; + }); } else { - // if have multiple resource ? add other : move to other - newResource = currentRes.length > 1 ? [...currentRes, resVal] : [resVal]; + updatedEvents = state.events.map((e) => + e.event_id === event.event_id ? { ...e, ...event } : e + ); } + } else { + updatedEvents = state.events.concat(event); } - } + set((prev) => ({ ...prev, events: updatedEvents })); + }, + [state.events] + ); - // Omit if dropped on same time slot for non multiple events - if (isEqual(droppedEvent.start, startTime)) { - if (!newResource || (!isMultiple && newResource === droppedEvent[resKey as string])) { - return; + const setCurrentDragged = useCallback((event?: ProcessedEvent) => { + set((prev) => ({ ...prev, currentDragged: event })); + }, []); + + const setCurrentResize = useCallback((event?: ProcessedEvent) => { + set((prev) => ({ ...prev, currentResize: event })); + }, []); + + const onDrop = useCallback( + async ( + event: DragEvent, + eventId: string, + startTime: Date, + resKey?: string, + resVal?: string | number + ) => { + // Get dropped event + const droppedEvent = state.events.find((e) => { + if (typeof e.event_id === "number") { + return e.event_id === +eventId; + } + return e.event_id === eventId; + }) as ProcessedEvent; + + // Check if has resource and if is multiple + const resField = state.fields.find((f) => f.name === resKey); + const isMultiple = !!resField?.config?.multiple; + let newResource = resVal as string | number | string[] | number[]; + if (resField) { + const eResource = droppedEvent[resKey as string]; + const currentRes = arraytizeFieldVal(resField, eResource, droppedEvent).value; + if (isMultiple) { + // if dropped on already owned resource + if (currentRes.includes(resVal)) { + // Omit if dropped on same time slot for multiple event + if (isEqual(droppedEvent.start, startTime)) { + return; + } + newResource = currentRes; + } else { + // if have multiple resource ? add other : move to other + newResource = currentRes.length > 1 ? [...currentRes, resVal] : [resVal]; + } + } + } + + // Omit if dropped on same time slot for non multiple events + if (isEqual(droppedEvent.start, startTime)) { + if (!newResource || (!isMultiple && newResource === droppedEvent[resKey as string])) { + return; + } + } + + // Update event time according to original duration & update resources/owners + const diff = differenceInMinutes(droppedEvent.end, droppedEvent.start); + const updatedEvent: ProcessedEvent = { + ...droppedEvent, + start: startTime, + end: addMinutes(startTime, diff), + recurring: undefined, + [resKey as string]: newResource || "", + }; + + // Local + if (!state.onEventDrop || typeof state.onEventDrop !== "function") { + return confirmEvent(updatedEvent, "edit"); + } + // Remote + try { + triggerLoading(true); + const _event = await state.onEventDrop(event, startTime, updatedEvent, droppedEvent); + if (_event) { + confirmEvent(_event, "edit"); + } + } finally { + triggerLoading(false); + } + }, + [confirmEvent, state, triggerLoading] + ); + + const onResize = useCallback( + (ev: DragEvent, event: ProcessedEvent, minuteHeight: number): Date | undefined => { + const eventItem = ev.currentTarget.closest("div.rs__event__item") as HTMLDivElement | null; + if (eventItem) { + const { top } = eventItem.getBoundingClientRect(); + const diff = ev.clientY - top; + const minutes = diff / minuteHeight; + eventItem.style.height = `${diff}px`; + return addMinutes(event.start, minutes); } - } - - // Update event time according to original duration & update resources/owners - const diff = differenceInMinutes(droppedEvent.end, droppedEvent.start); - const updatedEvent: ProcessedEvent = { - ...droppedEvent, - start: startTime, - end: addMinutes(startTime, diff), - recurring: undefined, - [resKey as string]: newResource || "", - }; - - // Local - if (!state.onEventDrop || typeof state.onEventDrop !== "function") { - return confirmEvent(updatedEvent, "edit"); - } - // Remote - try { - triggerLoading(true); - const _event = await state.onEventDrop(event, startTime, updatedEvent, droppedEvent); - if (_event) { - confirmEvent(_event, "edit"); + }, + [] + ); + + const onResizeEnd = useCallback( + (ev: DragEvent, event: ProcessedEvent, minuteHeight: number) => { + const eventItem = ev.currentTarget.closest("div.rs__event__item") as HTMLDivElement | null; + if (eventItem) { + const { height } = eventItem.getBoundingClientRect(); + const minutes = height / minuteHeight; + + const updatedEvent: ProcessedEvent = { + ...event, + end: addMinutes(event.start, minutes), + recurring: undefined, + }; + + // Local + if (!state.onEventResize || typeof state.onEventResize !== "function") { + return confirmEvent(updatedEvent, "edit"); + } + // Remote + try { + triggerLoading(true); + const _event = state.onEventResize(ev, updatedEvent, event); + if (_event) { + confirmEvent(_event, "edit"); + } + } finally { + triggerLoading(false); + } } - } finally { - triggerLoading(false); - } - }; - - return ( - - {children} - + }, + [confirmEvent, state, triggerLoading] ); + + const value = useMemo( + () => ({ + ...state, + handleState, + getViews, + toggleAgenda, + triggerDialog, + triggerLoading, + handleGotoDay, + confirmEvent, + setCurrentDragged, + setCurrentResize, + onDrop, + onResize, + onResizeEnd, + }), + [ + confirmEvent, + getViews, + handleGotoDay, + handleState, + onDrop, + onResize, + setCurrentDragged, + setCurrentResize, + state, + toggleAgenda, + triggerDialog, + triggerLoading, + onResizeEnd, + ] + ); + + return {children}; }; diff --git a/src/lib/store/types.ts b/src/lib/store/types.ts index dd1e0d0b..e4c83743 100644 --- a/src/lib/store/types.ts +++ b/src/lib/store/types.ts @@ -10,6 +10,7 @@ export interface SchedulerState extends SchedulerProps { selectedEvent?: ProcessedEvent; selectedResource?: DefaultResource["assignee"]; currentDragged?: ProcessedEvent; + currentResize?: ProcessedEvent; enableAgenda?: boolean; } @@ -22,6 +23,7 @@ export interface Store extends SchedulerState { handleGotoDay(day: Date): void; confirmEvent(event: ProcessedEvent | ProcessedEvent[], action: EventActions): void; setCurrentDragged(event?: ProcessedEvent): void; + setCurrentResize(event?: ProcessedEvent): void; onDrop( event: DragEvent, eventId: string, @@ -29,4 +31,10 @@ export interface Store extends SchedulerState { resourceKey?: string, resourceVal?: string | number ): void; + onResize( + ev: DragEvent, + event: ProcessedEvent, + minuteHeight: number + ): Date | undefined; + onResizeEnd(ev: DragEvent, event: ProcessedEvent, minuteHeight: number): void; } diff --git a/src/lib/styles/styles.ts b/src/lib/styles/styles.ts index 041e0cce..2d2fc7ba 100644 --- a/src/lib/styles/styles.ts +++ b/src/lib/styles/styles.ts @@ -177,6 +177,7 @@ export const EventItemPaper = styled(Paper)<{ disabled?: boolean }>(({ disabled display: "block", cursor: disabled ? "not-allowed" : "pointer", overflow: "hidden", + position: "relative", "& .MuiButtonBase-root": { width: "100%", height: "100%", @@ -238,3 +239,13 @@ export const TimeIndicatorBar = styled("div")(({ theme }) => ({ width: "100%", }, })); + +export const DragHandle = styled("div")(({ draggable }) => ({ + position: "absolute", + bottom: 0, + left: 0, + width: "100%", + cursor: draggable ? "ns-resize" : "unset", + backgroundColor: "rgba(0,0,0,0.0001)", + height: "5px", +})); diff --git a/src/lib/types.ts b/src/lib/types.ts index 35e8ad97..1ff3d34e 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -63,6 +63,7 @@ interface CalendarEvent { editable?: boolean; deletable?: boolean; draggable?: boolean; + resizable?: boolean; allDay?: boolean; /** * @default " " @@ -295,6 +296,14 @@ export interface SchedulerProps { updatedEvent: ProcessedEvent, originalEvent: ProcessedEvent ): Promise; + /** + * Triggered when event is resized. + */ + onEventResize?( + event: DragEvent, + updatedEvent: ProcessedEvent, + originalEvent: ProcessedEvent + ): ProcessedEvent | void; /** * */ @@ -318,6 +327,11 @@ export interface SchedulerProps { * @default true */ draggable?: boolean; + /** + * If event is resizable, applied to all events globally, overridden by event specific resizable prop + * @default true + */ + resizable?: boolean; /** * Triggered when the `selectedDate` prop changes by navigation date picker or `today` button. */ From c399ec9335be5b3c1968c80cc8c07883e67067ea Mon Sep 17 00:00:00 2001 From: Andy Cork Date: Fri, 21 Feb 2025 16:02:06 +0000 Subject: [PATCH 2/7] Tidy the dates --- src/events.tsx | 114 +++++++++++++++++-------------------------------- 1 file changed, 40 insertions(+), 74 deletions(-) diff --git a/src/events.tsx b/src/events.tsx index 56fc62fa..17a9a619 100644 --- a/src/events.tsx +++ b/src/events.tsx @@ -2,13 +2,27 @@ import { RRule } from "rrule"; import { ProcessedEvent } from "./lib/types"; import { convertDateToRRuleDate } from "./lib/helpers/generals"; +const createDate = (startHour: number, startMinutes?: number, days?: number, months?: number) => { + let date = new Date(new Date().setHours(startHour)).setMinutes(0); + if (startMinutes) { + date = new Date(date).setMinutes(startMinutes); + } + if (days) { + date = new Date(date).setDate(new Date().getDate() + days); + } + if (months) { + date = new Date(date).setMonth(new Date().getMonth() + months); + } + return new Date(date); +}; + export const EVENTS: ProcessedEvent[] = [ { event_id: 1, title: "Event 1 (Disabled)", subtitle: "This event is disabled", - start: new Date(new Date(new Date().setHours(9)).setMinutes(0)), - end: new Date(new Date(new Date().setHours(10)).setMinutes(0)), + start: createDate(9), + end: createDate(10), disabled: true, admin_id: [1, 2, 3, 4], }, @@ -16,8 +30,8 @@ export const EVENTS: ProcessedEvent[] = [ event_id: 2, title: "Event 2", subtitle: "This event is draggable", - start: new Date(new Date(new Date().setHours(10)).setMinutes(0)), - end: new Date(new Date(new Date().setHours(12)).setMinutes(0)), + start: createDate(10), + end: createDate(12), admin_id: 2, color: "#50b500", agendaAvatar: "E", @@ -26,8 +40,8 @@ export const EVENTS: ProcessedEvent[] = [ event_id: 3, title: "Event 3", subtitle: "This event is not editable", - start: new Date(new Date(new Date().setHours(11)).setMinutes(0)), - end: new Date(new Date(new Date().setHours(12)).setMinutes(0)), + start: createDate(11), + end: createDate(12), admin_id: 1, editable: false, deletable: false, @@ -35,12 +49,8 @@ export const EVENTS: ProcessedEvent[] = [ { event_id: 4, title: "Event 4", - start: new Date( - new Date(new Date(new Date().setHours(9)).setMinutes(30)).setDate(new Date().getDate() - 2) - ), - end: new Date( - new Date(new Date(new Date().setHours(11)).setMinutes(0)).setDate(new Date().getDate() - 2) - ), + start: createDate(9, 30, -2), + end: createDate(11, 0, -2), admin_id: [2, 3], color: "#900000", allDay: true, @@ -49,12 +59,8 @@ export const EVENTS: ProcessedEvent[] = [ event_id: 5, title: "Event 5", subtitle: "This event is editable", - start: new Date( - new Date(new Date(new Date().setHours(10)).setMinutes(30)).setDate(new Date().getDate() - 2) - ), - end: new Date( - new Date(new Date(new Date().setHours(14)).setMinutes(0)).setDate(new Date().getDate() - 2) - ), + start: createDate(10, 30, -2), + end: createDate(14, 0, -2), admin_id: 2, editable: true, }, @@ -62,10 +68,8 @@ export const EVENTS: ProcessedEvent[] = [ event_id: 6, title: "Event 6", subtitle: "This event is all day", - start: new Date( - new Date(new Date(new Date().setHours(20)).setMinutes(30)).setDate(new Date().getDate() - 3) - ), - end: new Date(new Date(new Date().setHours(23)).setMinutes(0)), + start: createDate(20, 30, -3), + end: createDate(23), admin_id: 2, allDay: true, sx: { color: "purple" }, @@ -74,12 +78,8 @@ export const EVENTS: ProcessedEvent[] = [ event_id: 7, title: "Event 7 (Not draggable)", subtitle: "This event is not draggable", - start: new Date( - new Date(new Date(new Date().setHours(10)).setMinutes(30)).setDate(new Date().getDate() - 3) - ), - end: new Date( - new Date(new Date(new Date().setHours(14)).setMinutes(30)).setDate(new Date().getDate() - 3) - ), + start: createDate(10, 30, -3), + end: createDate(14, 30, -3), admin_id: 1, draggable: false, color: "#8000cc", @@ -88,63 +88,33 @@ export const EVENTS: ProcessedEvent[] = [ event_id: 8, title: "Event 8", subtitle: "This event has a custom color", - start: new Date( - new Date(new Date(new Date().setHours(10)).setMinutes(30)).setDate(new Date().getDate() + 30) - ), - end: new Date( - new Date(new Date(new Date().setHours(14)).setMinutes(30)).setDate(new Date().getDate() + 30) - ), + start: createDate(10, 30, 30), + end: createDate(14, 30, 30), admin_id: 1, color: "#8000cc", }, { event_id: 9, title: "Event 9", - subtitle: `This event is a recurring weekly until ${new Date( - new Date().setMonth( - new Date( - new Date(new Date(new Date().setHours(11)).setMinutes(0)).setDate( - new Date().getDate() + 1 - ) - ).getMonth() + 1 - ) - ).toDateString()}`, - start: new Date( - new Date(new Date(new Date().setHours(10)).setMinutes(0)).setDate(new Date().getDate() + 1) - ), - end: new Date( - new Date(new Date(new Date().setHours(11)).setMinutes(0)).setDate(new Date().getDate() + 1) - ), + subtitle: `This event is a recurring weekly until ${createDate(11, 0, 1, 1).toDateString()}`, + start: createDate(10, 0, 1), + end: createDate(11, 0, 1), recurring: new RRule({ freq: RRule.WEEKLY, - dtstart: convertDateToRRuleDate( - new Date( - new Date(new Date(new Date().setHours(10)).setMinutes(0)).setDate( - new Date().getDate() - 20 - ) - ) - ), - until: new Date( - new Date().setMonth( - new Date( - new Date(new Date(new Date().setHours(11)).setMinutes(0)).setDate( - new Date().getDate() + 1 - ) - ).getMonth() + 1 - ) - ), + dtstart: convertDateToRRuleDate(createDate(11, 0, -20)), + until: createDate(11, 0, 1, 1), }), }, { event_id: 10, title: "Event 10", subtitle: "This event is a recurring hourly 3 times", - start: new Date(new Date(new Date().setHours(14)).setMinutes(15)), - end: new Date(new Date(new Date().setHours(14)).setMinutes(45)), + start: createDate(14, 15), + end: createDate(14, 45), recurring: new RRule({ freq: RRule.HOURLY, count: 3, - dtstart: convertDateToRRuleDate(new Date(new Date(new Date().setHours(14)).setMinutes(15))), + dtstart: convertDateToRRuleDate(createDate(14, 15)), }), color: "#dc4552", }, @@ -152,12 +122,8 @@ export const EVENTS: ProcessedEvent[] = [ event_id: 11, title: "Event 11", subtitle: "This event is not resizable", - start: new Date( - new Date(new Date(new Date().setHours(10)).setMinutes(30)).setDate(new Date().getDate() - 4) - ), - end: new Date( - new Date(new Date(new Date().setHours(12)).setMinutes(30)).setDate(new Date().getDate() - 4) - ), + start: createDate(10, 30, -4), + end: createDate(12, 30, -4), admin_id: 1, resizable: false, }, From 47196b8952230cb51dde957f018696430b7b1a1f Mon Sep 17 00:00:00 2001 From: Andy Cork Date: Fri, 21 Feb 2025 16:22:51 +0000 Subject: [PATCH 3/7] Tidy date using mutation --- src/events.tsx | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/events.tsx b/src/events.tsx index 17a9a619..d7f52709 100644 --- a/src/events.tsx +++ b/src/events.tsx @@ -2,17 +2,18 @@ import { RRule } from "rrule"; import { ProcessedEvent } from "./lib/types"; import { convertDateToRRuleDate } from "./lib/helpers/generals"; -const createDate = (startHour: number, startMinutes?: number, days?: number, months?: number) => { - let date = new Date(new Date().setHours(startHour)).setMinutes(0); - if (startMinutes) { - date = new Date(date).setMinutes(startMinutes); - } - if (days) { - date = new Date(date).setDate(new Date().getDate() + days); - } - if (months) { - date = new Date(date).setMonth(new Date().getMonth() + months); - } +const createDate = ( + startHour: number, + startMinutes: number = 0, + days: number = 0, + months: number = 0 +) => { + const date = new Date(); + date.setHours(startHour); + date.setMinutes(startMinutes); + date.setDate(date.getDate() + days); + date.setMonth(date.getMonth() + months); + return new Date(date); }; From 48fe362a8f3334365e96db51007e37461bf25fe7 Mon Sep 17 00:00:00 2001 From: Andy Cork Date: Fri, 21 Feb 2025 16:29:56 +0000 Subject: [PATCH 4/7] No need to return new Date() --- src/events.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/events.tsx b/src/events.tsx index d7f52709..b13859e0 100644 --- a/src/events.tsx +++ b/src/events.tsx @@ -14,7 +14,7 @@ const createDate = ( date.setDate(date.getDate() + days); date.setMonth(date.getMonth() + months); - return new Date(date); + return date; }; export const EVENTS: ProcessedEvent[] = [ From ae51fcb66a4e3e1edc6d33182a3d7b6eb1f81dae Mon Sep 17 00:00:00 2001 From: Andy Cork Date: Sat, 22 Feb 2025 10:48:00 +0000 Subject: [PATCH 5/7] Don't resize below 1 minute length --- src/lib/store/provider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/store/provider.tsx b/src/lib/store/provider.tsx index 3786bac0..9bfe118b 100644 --- a/src/lib/store/provider.tsx +++ b/src/lib/store/provider.tsx @@ -215,7 +215,7 @@ export const StoreProvider = ({ children, initial }: Props) => { const eventItem = ev.currentTarget.closest("div.rs__event__item") as HTMLDivElement | null; if (eventItem) { const { top } = eventItem.getBoundingClientRect(); - const diff = ev.clientY - top; + const diff = Math.max(ev.clientY - top, minuteHeight); const minutes = diff / minuteHeight; eventItem.style.height = `${diff}px`; return addMinutes(event.start, minutes); From 949e49ab07a55656f7c5cd0433d0600e49605c6b Mon Sep 17 00:00:00 2001 From: Andy Cork Date: Sat, 22 Feb 2025 20:31:11 +0000 Subject: [PATCH 6/7] Fix couple minor bugs in resize: * Always call onResize * Move Drag Image to constants --- src/lib/helpers/constants.ts | 4 ++++ src/lib/hooks/useResizeAttributes.ts | 10 ++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/lib/helpers/constants.ts b/src/lib/helpers/constants.ts index 3529a2cd..5da38f3c 100644 --- a/src/lib/helpers/constants.ts +++ b/src/lib/helpers/constants.ts @@ -2,3 +2,7 @@ export const BORDER_HEIGHT = 1; export const MULTI_DAY_EVENT_HEIGHT = 28; export const MONTH_NUMBER_HEIGHT = 27; export const MONTH_BAR_HEIGHT = 23; + +export const DRAG_IMAGE = new Image(); +DRAG_IMAGE.style.pointerEvents = "none"; +DRAG_IMAGE.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="; diff --git a/src/lib/hooks/useResizeAttributes.ts b/src/lib/hooks/useResizeAttributes.ts index 32e485ec..26fe6f07 100644 --- a/src/lib/hooks/useResizeAttributes.ts +++ b/src/lib/hooks/useResizeAttributes.ts @@ -1,10 +1,7 @@ import { DragEvent, useMemo } from "react"; import { ProcessedEvent } from "../types"; import useStore from "./useStore"; - -const img = new Image(); -img.style.pointerEvents = "none"; -img.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="; +import { DRAG_IMAGE } from "../helpers/constants"; const useResizeAttributes = ( event: ProcessedEvent, @@ -20,7 +17,7 @@ const useResizeAttributes = ( onDragStart: (e: DragEvent) => { e.stopPropagation(); setCurrentResize(event); - e.dataTransfer.setDragImage(img, 0, 0); + e.dataTransfer.setDragImage(DRAG_IMAGE, 0, 0); }, onDragEnd: (e: DragEvent) => { setCurrentResize(); @@ -30,7 +27,8 @@ const useResizeAttributes = ( onDrag: (e: DragEvent) => { e.stopPropagation(); e.preventDefault(); - onDragMove?.(onResize(e, event, minuteHeight)); + const date = onResize(e, event, minuteHeight); + onDragMove?.(date); }, onDragOver: (e: DragEvent) => { e.stopPropagation(); From 206183c8ac28281719537c563b6267f5a2c6237c Mon Sep 17 00:00:00 2001 From: Andy Cork Date: Thu, 20 Mar 2025 13:01:22 +0000 Subject: [PATCH 7/7] Merge upstream --- eslint.config.js | 2 +- package.json | 7 ++-- src/App.tsx | 17 +++++++--- src/Page1.tsx | 27 ++++++++++++++++ src/events.tsx | 13 ++++++-- src/index.tsx | 9 +++++- src/lib/helpers/generals.tsx | 11 ------- src/lib/store/default.ts | 62 +++++++++++++++++------------------- src/lib/views/Day.tsx | 3 +- src/lib/views/Month.tsx | 3 +- src/lib/views/Week.tsx | 3 +- vite.config.js | 14 ++------ 12 files changed, 101 insertions(+), 70 deletions(-) create mode 100644 src/Page1.tsx diff --git a/eslint.config.js b/eslint.config.js index 88ee9b94..7d67287f 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -2,7 +2,7 @@ import react from "eslint-plugin-react"; import tseslint, { configs as tseslintConfigs } from "typescript-eslint"; import globals from "globals"; import js from "@eslint/js"; -import reactHooks from "eslint-plugin-react-hooks"; +import * as reactHooks from "eslint-plugin-react-hooks"; import reactRefresh from "eslint-plugin-react-refresh"; import pluginImport from "eslint-plugin-import"; import pluginJsxA11y from "eslint-plugin-jsx-a11y"; diff --git a/package.json b/package.json index 461fd083..f115bc7e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@aldabil/react-scheduler", - "version": "3.0.3", + "version": "3.0.5", "description": "React scheduler component based on Material-UI & date-fns", "files": [ "*" @@ -69,6 +69,7 @@ "@types/jest": "^29.5.14", "@types/react": "^19.0.10", "@types/react-dom": "^19.0.4", + "@types/rollup-plugin-peer-deps-external": "^2", "@typescript-eslint/parser": "^8.24.1", "@vitejs/plugin-react": "^4.3.4", "date-fns": ">=4.1.0", @@ -78,7 +79,7 @@ "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-promise": "^7.2.1", "eslint-plugin-react": "^7.37.4", - "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.19", "globals": "^16.0.0", "husky": "^9.1.7", @@ -88,6 +89,8 @@ "prettier": "^3.5.1", "react": ">=19.0.0", "react-dom": "^19.0.0", + "react-router": "^7.3.0", + "rollup-plugin-peer-deps-external": "^2.2.4", "rrule": "^2.8.1", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", diff --git a/src/App.tsx b/src/App.tsx index c5db2aea..ad042867 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,16 +2,23 @@ import { Scheduler } from "./lib"; import { EVENTS } from "./events"; import { useRef } from "react"; import { SchedulerRef } from "./lib/types"; +import { Link } from "react-router"; function App() { const calendarRef = useRef(null); return ( - + <> +
+ Go to page 1 +
+ + + ); } diff --git a/src/Page1.tsx b/src/Page1.tsx new file mode 100644 index 00000000..d7a9a61a --- /dev/null +++ b/src/Page1.tsx @@ -0,0 +1,27 @@ +import { Scheduler } from "./lib"; +import { EVENTS } from "./events"; +import { useRef } from "react"; +import { SchedulerRef } from "./lib/types"; +import { Link } from "react-router"; + +const events = EVENTS.slice(3, 6); + +function Page1() { + const calendarRef = useRef(null); + + return ( + <> +
+ Go to home +
+ + + + ); +} + +export default Page1; diff --git a/src/events.tsx b/src/events.tsx index b13859e0..b98ee1ea 100644 --- a/src/events.tsx +++ b/src/events.tsx @@ -1,6 +1,5 @@ -import { RRule } from "rrule"; +import { datetime, RRule } from "rrule"; import { ProcessedEvent } from "./lib/types"; -import { convertDateToRRuleDate } from "./lib/helpers/generals"; const createDate = ( startHour: number, @@ -184,3 +183,13 @@ export const generateRandomEvents = (total = 300) => { return events; }; + +function convertDateToRRuleDate(date: Date) { + return datetime( + date.getFullYear(), + date.getMonth() + 1, + date.getDate(), + date.getHours(), + date.getMinutes() + ); +} diff --git a/src/index.tsx b/src/index.tsx index 2ebc200c..f9a1df50 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,13 +2,20 @@ import * as React from "react"; import { CssBaseline, ThemeProvider, createTheme } from "@mui/material"; import { createRoot } from "react-dom/client"; import App from "./App"; +import { BrowserRouter, Route, Routes } from "react-router"; +import Page1 from "./Page1"; const root = createRoot(document.getElementById("root") as HTMLElement); root.render( - + + + } /> + } /> + + ); diff --git a/src/lib/helpers/generals.tsx b/src/lib/helpers/generals.tsx index b0522187..f441c7b4 100644 --- a/src/lib/helpers/generals.tsx +++ b/src/lib/helpers/generals.tsx @@ -21,7 +21,6 @@ import { SchedulerProps, } from "../types"; import { StateEvent } from "../views/Editor"; -import { datetime } from "rrule"; import { DragEvent } from "react"; export const getOneView = (state: Partial): View => { @@ -131,16 +130,6 @@ export const preventDragEvent = (ev: DragEvent) => { ev.preventDefault(); }; -export const convertDateToRRuleDate = (date: Date) => { - return datetime( - date.getFullYear(), - date.getMonth() + 1, - date.getDate(), - date.getHours(), - date.getMinutes() - ); -}; - export const convertRRuleDateToDate = (rruleDate: Date) => { return new Date( rruleDate.getUTCFullYear(), diff --git a/src/lib/store/default.ts b/src/lib/store/default.ts index cc92c7a2..8c0e96d5 100644 --- a/src/lib/store/default.ts +++ b/src/lib/store/default.ts @@ -88,19 +88,19 @@ const defaultViews = (props: Partial) => { export const defaultProps = (props: Partial) => { // We're pulling values out of props that we don't want to // pass on, so there are 'unused' ones here. - /* eslint-disable @typescript-eslint/no-unused-vars */ const { - month, - week, - day, translations, resourceFields, view, agenda, selectedDate, + resourceViewMode, + direction, + dialogMaxWidth, + hourFormat, ...otherProps } = props; - /* eslint-enable @typescript-eslint/no-unused-vars */ + const views = defaultViews(props); const defaultView = view || "week"; const initialView = views[defaultView] ? defaultView : getOneView(views); @@ -110,34 +110,30 @@ export const defaultProps = (props: Partial) => { resourceFields: Object.assign(defaultResourceFields, resourceFields), view: initialView, selectedDate: getTimeZonedDate(selectedDate || new Date(), props.timeZone), - ...Object.assign( - { - height: 600, - navigation: true, - disableViewNavigator: false, - events: [], - fields: [], - loading: undefined, - customEditor: undefined, - onConfirm: undefined, - onDelete: undefined, - viewerExtraComponent: undefined, - resources: [], - resourceHeaderComponent: undefined, - resourceViewMode: "default", - direction: "ltr", - dialogMaxWidth: "md", - locale: enUS, - deletable: true, - editable: true, - resizable: true, - hourFormat: "12", - draggable: true, - agenda, - enableAgenda: typeof agenda === "undefined" || agenda, - }, - otherProps - ), + height: 600, + navigation: true, + disableViewNavigator: false, + events: [], + fields: [], + loading: undefined, + customEditor: undefined, + onConfirm: undefined, + onDelete: undefined, + viewerExtraComponent: undefined, + resources: [], + resourceHeaderComponent: undefined, + resourceViewMode: resourceViewMode || "default", + direction: direction || "ltr", + dialogMaxWidth: dialogMaxWidth || "md", + locale: enUS, + deletable: true, + editable: true, + resizable: true, + hourFormat: hourFormat || "12", + draggable: true, + agenda, + enableAgenda: typeof agenda === "undefined" || agenda, + ...otherProps, }; }; diff --git a/src/lib/views/Day.tsx b/src/lib/views/Day.tsx index e84d70b2..6e6ad399 100644 --- a/src/lib/views/Day.tsx +++ b/src/lib/views/Day.tsx @@ -94,7 +94,8 @@ const Day = () => { } finally { triggerLoading(false); } - }, [triggerLoading, START_TIME, END_TIME, getRemoteEvents, handleState]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [getRemoteEvents]); useEffect(() => { if (getRemoteEvents instanceof Function) { diff --git a/src/lib/views/Month.tsx b/src/lib/views/Month.tsx index ecdd82ea..4c6454f9 100644 --- a/src/lib/views/Month.tsx +++ b/src/lib/views/Month.tsx @@ -63,7 +63,8 @@ const Month = () => { } finally { triggerLoading(false); } - }, [triggerLoading, eachWeekStart, daysList.length, getRemoteEvents, handleState]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [daysList.length, getRemoteEvents]); useEffect(() => { if (getRemoteEvents instanceof Function) { diff --git a/src/lib/views/Week.tsx b/src/lib/views/Week.tsx index 3ac36fb6..62aac917 100644 --- a/src/lib/views/Week.tsx +++ b/src/lib/views/Week.tsx @@ -69,7 +69,8 @@ const Week = () => { } finally { triggerLoading(false); } - }, [triggerLoading, getRemoteEvents, weekStart, weekEnd, handleState]); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [getRemoteEvents]); useEffect(() => { if (getRemoteEvents instanceof Function) { diff --git a/vite.config.js b/vite.config.js index 3701c813..300be830 100644 --- a/vite.config.js +++ b/vite.config.js @@ -4,7 +4,7 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import dts from "vite-plugin-dts"; import tsconfigPaths from "vite-tsconfig-paths"; -import { peerDependencies } from "./package.json"; +import peerDepsExternal from "rollup-plugin-peer-deps-external"; const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -15,6 +15,7 @@ export default defineConfig(() => ({ configNames: ["tsconfig.json"], }), dts({ tsconfigPath: "./tsconfig.build.json" }), + peerDepsExternal(), ], server: { port: 3000, @@ -31,17 +32,6 @@ export default defineConfig(() => ({ name: "Scheduler", formats: ["es"], }, - rollupOptions: { - external: (path) => { - const nodeModules = path.includes("node_modules"); - const isPeer = Object.keys(peerDependencies).some((dep) => path.startsWith(dep)); - const isExternal = nodeModules || isPeer; - return isExternal; - }, - output: { - globals: (path) => path, - }, - }, copyPublicDir: false, }, resolve: {