diff --git a/packages/web/src/common/utils/shortcut/data/shortcuts.data.test.ts b/packages/web/src/common/utils/shortcut/data/shortcuts.data.test.ts index cbcd6a804..d104aac55 100644 --- a/packages/web/src/common/utils/shortcut/data/shortcuts.data.test.ts +++ b/packages/web/src/common/utils/shortcut/data/shortcuts.data.test.ts @@ -27,6 +27,12 @@ describe("shortcuts.data", () => { label: "Focus on calendar", }); expect(shortcuts.dayAgendaShortcuts[1]).toEqual({ + k: "n", + label: "Create event", + }); + + expect(shortcuts.dayShortcuts).toHaveLength(3); + expect(shortcuts.dayShortcuts[2]).toEqual({ k: "t", label: "Go to today", }); @@ -38,7 +44,7 @@ describe("shortcuts.data", () => { currentDate: dayjs(), }); - const tShortcut = shortcuts.dayAgendaShortcuts.find((s) => s.k === "t"); + const tShortcut = shortcuts.dayShortcuts.find((s) => s.k === "t"); expect(tShortcut).toBeDefined(); expect(tShortcut?.label).toBe("Scroll to now"); }); @@ -51,7 +57,7 @@ describe("shortcuts.data", () => { currentDate: yesterday, }); - const tShortcut = shortcuts.dayAgendaShortcuts.find((s) => s.k === "t"); + const tShortcut = shortcuts.dayShortcuts.find((s) => s.k === "t"); expect(tShortcut).toBeDefined(); expect(tShortcut?.label).toBe("Go to today"); }); @@ -64,7 +70,7 @@ describe("shortcuts.data", () => { currentDate: tomorrow, }); - const tShortcut = shortcuts.dayAgendaShortcuts.find((s) => s.k === "t"); + const tShortcut = shortcuts.dayShortcuts.find((s) => s.k === "t"); expect(tShortcut).toBeDefined(); expect(tShortcut?.label).toBe("Go to today"); }); @@ -75,7 +81,7 @@ describe("shortcuts.data", () => { currentDate: undefined, }); - const tShortcut = shortcuts.dayAgendaShortcuts.find((s) => s.k === "t"); + const tShortcut = shortcuts.dayShortcuts.find((s) => s.k === "t"); expect(tShortcut).toBeDefined(); expect(tShortcut?.label).toBe("Go to today"); }); diff --git a/packages/web/src/common/utils/shortcut/data/shortcuts.data.ts b/packages/web/src/common/utils/shortcut/data/shortcuts.data.ts index efe9aea38..ce1e6003a 100644 --- a/packages/web/src/common/utils/shortcut/data/shortcuts.data.ts +++ b/packages/web/src/common/utils/shortcut/data/shortcuts.data.ts @@ -22,6 +22,7 @@ export const getShortcuts = (config: ShortcutsConfig = {}) => { ]; let homeShortcuts: Shortcut[] = []; + let dayShortcuts: Shortcut[] = []; let dayTaskShortcuts: Shortcut[] = []; let dayAgendaShortcuts: Shortcut[] = []; let nowShortcuts: Shortcut[] = []; @@ -35,14 +36,9 @@ export const getShortcuts = (config: ShortcutsConfig = {}) => { } if (isToday) { - dayTaskShortcuts = [ - { k: "u", label: "Focus on tasks" }, - { k: "c", label: "Create task" }, - { k: "e", label: "Edit task" }, - { k: "Delete", label: "Delete task" }, - ]; - dayAgendaShortcuts = [ - { k: "i", label: "Focus on calendar" }, + dayShortcuts = [ + { k: "j", label: "Previous day" }, + { k: "k", label: "Next day" }, { k: "t", label: (() => { @@ -54,6 +50,17 @@ export const getShortcuts = (config: ShortcutsConfig = {}) => { })(), }, ]; + + dayTaskShortcuts = [ + { k: "u", label: "Focus on tasks" }, + { k: "c", label: "Create task" }, + { k: "e", label: "Edit task" }, + { k: "Delete", label: "Delete task" }, + ]; + dayAgendaShortcuts = [ + { k: "i", label: "Focus on calendar" }, + { k: "n", label: "Create event" }, + ]; } if (isNow) { nowShortcuts = [ @@ -69,6 +76,7 @@ export const getShortcuts = (config: ShortcutsConfig = {}) => { return { globalShortcuts, homeShortcuts, + dayShortcuts, dayTaskShortcuts, dayAgendaShortcuts, nowShortcuts, diff --git a/packages/web/src/views/Calendar/components/Draft/context/DraftProviderV2.tsx b/packages/web/src/views/Calendar/components/Draft/context/DraftProviderV2.tsx index 5894efdf6..8f3cbb4ba 100644 --- a/packages/web/src/views/Calendar/components/Draft/context/DraftProviderV2.tsx +++ b/packages/web/src/views/Calendar/components/Draft/context/DraftProviderV2.tsx @@ -13,7 +13,7 @@ import { useSaveEventForm } from "@web/views/Forms/hooks/useSaveEventForm"; interface DraftProviderV2Props { draft: Schema_Event | null; setDraft: Dispatch>; - openEventForm: () => void; + openEventForm: (create?: boolean) => void; closeEventForm: () => void; onDelete: () => void; onSave: (draft: Schema_Event | null) => void; diff --git a/packages/web/src/views/Calendar/components/Draft/context/__tests__/DraftProviderV2.test.tsx b/packages/web/src/views/Calendar/components/Draft/context/__tests__/DraftProviderV2.test.tsx new file mode 100644 index 000000000..bff02bd70 --- /dev/null +++ b/packages/web/src/views/Calendar/components/Draft/context/__tests__/DraftProviderV2.test.tsx @@ -0,0 +1,56 @@ +import { useContext } from "react"; +import { render, screen } from "@testing-library/react"; +import { useCloseEventForm } from "@web/views/Forms/hooks/useCloseEventForm"; +import { useOpenEventForm } from "@web/views/Forms/hooks/useOpenEventForm"; +import { useSaveEventForm } from "@web/views/Forms/hooks/useSaveEventForm"; +import { DraftContextV2, DraftProviderV2 } from "../DraftProviderV2"; + +jest.mock("@web/views/Forms/hooks/useOpenEventForm"); +jest.mock("@web/views/Forms/hooks/useCloseEventForm"); +jest.mock("@web/views/Forms/hooks/useSaveEventForm"); + +const TestComponent = () => { + const context = useContext(DraftContextV2); + if (!context) return
No Context
; + return ( +
+
+ {context.draft ? "Draft Exists" : "No Draft"} +
+ + + +
+ ); +}; + +describe("DraftProviderV2", () => { + const mockOpenEventForm = jest.fn(); + const mockCloseEventForm = jest.fn(); + const mockOnSave = jest.fn(); + + beforeEach(() => { + (useOpenEventForm as jest.Mock).mockReturnValue(mockOpenEventForm); + (useCloseEventForm as jest.Mock).mockReturnValue(mockCloseEventForm); + (useSaveEventForm as jest.Mock).mockReturnValue(mockOnSave); + }); + + it("should provide draft context values", () => { + render( + + + , + ); + + expect(screen.getByTestId("draft")).toHaveTextContent("No Draft"); + + screen.getByText("Open").click(); + expect(mockOpenEventForm).toHaveBeenCalled(); + + screen.getByText("Close").click(); + expect(mockCloseEventForm).toHaveBeenCalled(); + + screen.getByText("Save").click(); + expect(mockOnSave).toHaveBeenCalled(); + }); +}); diff --git a/packages/web/src/views/Day/hooks/shortcuts/useDayViewShortcuts.test.ts b/packages/web/src/views/Day/hooks/shortcuts/useDayViewShortcuts.test.ts index e98bfc01c..45ef3b175 100644 --- a/packages/web/src/views/Day/hooks/shortcuts/useDayViewShortcuts.test.ts +++ b/packages/web/src/views/Day/hooks/shortcuts/useDayViewShortcuts.test.ts @@ -49,6 +49,7 @@ describe.each([ const defaultConfig = { onAddTask: jest.fn(), + onCreateEvent: jest.fn(), onEditTask: jest.fn(), onCompleteTask: jest.fn(), onDeleteTask: jest.fn(), @@ -67,6 +68,16 @@ describe.each([ expect(config.onFocusTasks).toHaveBeenCalled(); }); + it("should call onCreateEvent when 'n' is pressed", async () => { + const config = { ...defaultConfig }; + await act(() => renderHook(() => useDayViewShortcuts(config))); + + pressKey("n"); + + expect(config.onCreateEvent).toHaveBeenCalled(); + expect(config.onAddTask).not.toHaveBeenCalled(); + }); + it("should call onAddTask when 'c' is pressed", async () => { const config = { ...defaultConfig }; await act(() => renderHook(() => useDayViewShortcuts(config))); @@ -74,6 +85,7 @@ describe.each([ pressKey("c"); expect(config.onAddTask).toHaveBeenCalled(); + expect(config.onCreateEvent).not.toHaveBeenCalled(); }); it("should call onEditTask when 'e' is pressed", async () => { @@ -181,6 +193,7 @@ describe.each([ pressKey("c", {}, textarea); expect(config.onAddTask).not.toHaveBeenCalled(); + expect(config.onCreateEvent).not.toHaveBeenCalled(); }); it("should not handle shortcuts when typing in contenteditable elements", async () => { diff --git a/packages/web/src/views/Day/hooks/shortcuts/useDayViewShortcuts.ts b/packages/web/src/views/Day/hooks/shortcuts/useDayViewShortcuts.ts index 2e049abed..c3d3f8fe0 100644 --- a/packages/web/src/views/Day/hooks/shortcuts/useDayViewShortcuts.ts +++ b/packages/web/src/views/Day/hooks/shortcuts/useDayViewShortcuts.ts @@ -37,6 +37,9 @@ interface KeyboardShortcutsConfig { // Agenda navigation onFocusAgenda?: () => void; + // Event management + onCreateEvent?: () => void; + // Event undo onRestoreEvent?: () => void; @@ -65,6 +68,7 @@ export function useDayViewShortcuts(config: KeyboardShortcutsConfig) { onPrevDay, onGoToToday, onFocusAgenda, + onCreateEvent, isEditingTask, hasFocusedTask, undoToastId, @@ -124,6 +128,8 @@ export function useDayViewShortcuts(config: KeyboardShortcutsConfig) { useKeyUpEvent({ combination: ["e"], handler: onEditTask }); + useKeyUpEvent({ combination: ["n"], handler: onCreateEvent }); + useKeyUpEvent({ combination: ["Delete"], handler: handleDeleteTask }); useKeyUpEvent({ combination: ["Backspace"], handler: handleDeleteTask }); diff --git a/packages/web/src/views/Day/view/DayView.test.tsx b/packages/web/src/views/Day/view/DayView.test.tsx index e15c5518a..865c9dd68 100644 --- a/packages/web/src/views/Day/view/DayView.test.tsx +++ b/packages/web/src/views/Day/view/DayView.test.tsx @@ -31,7 +31,7 @@ describe("DayView", () => { // Check that CMD+K shortcut is displayed in the shortcuts overlay expect(await screen.findByText("Global")).toBeInTheDocument(); expect(screen.getByTestId(getModifierKeyTestId())).toBeInTheDocument(); - expect(screen.getByTestId("k-icon")).toBeInTheDocument(); + expect(screen.getAllByTestId("k-icon").length).toBeGreaterThan(1); expect(screen.getByText("Command Palette")).toBeInTheDocument(); }); }); diff --git a/packages/web/src/views/Day/view/DayViewContent.tsx b/packages/web/src/views/Day/view/DayViewContent.tsx index c770a9919..575ce17f3 100644 --- a/packages/web/src/views/Day/view/DayViewContent.tsx +++ b/packages/web/src/views/Day/view/DayViewContent.tsx @@ -8,6 +8,7 @@ import { selectDayEvents } from "@web/ducks/events/selectors/event.selectors"; import { useAppSelector } from "@web/store/store.hooks"; import { Dedication } from "@web/views/Calendar/components/Dedication"; import { DraftProviderV2 } from "@web/views/Calendar/components/Draft/context/DraftProviderV2"; +import { useDraftContextV2 } from "@web/views/Calendar/components/Draft/context/useDraftContextV2"; import { useRefetch } from "@web/views/Calendar/hooks/useRefetch"; import { StyledCalendar } from "@web/views/Calendar/styled"; import { Agenda } from "@web/views/Day/components/Agenda/Agenda"; @@ -27,7 +28,7 @@ import { focusOnFirstTask, } from "@web/views/Day/util/day.shortcut.util"; -export const DayViewContent = () => { +const DayViewContentInner = () => { useRefetch(); const { @@ -110,6 +111,12 @@ export const DayViewContent = () => { } }; + const { openEventForm } = useDraftContextV2(); + + const onCreateEvent = useCallback(() => { + openEventForm(true); + }, [openEventForm]); + useDayViewShortcuts({ onAddTask: focusOnAddTaskInput, onEditTask: handleEditTask, @@ -118,6 +125,7 @@ export const DayViewContent = () => { onMigrateTask: migrateTask, onFocusTasks: focusOnFirstTask, onFocusAgenda: handleFocusAgenda, + onCreateEvent: onCreateEvent, onNextDay: navigateToNextDay, onPrevDay: navigateToPreviousDay, onGoToToday: handleGoToToday, @@ -125,36 +133,44 @@ export const DayViewContent = () => { undoToastId, }); + return ( + <> + + + + +
+ +
+ + + +
+ + + + + + + + + ); +}; + +export const DayViewContent = () => { return ( - - - - -
- -
- - - -
- - - - - - - + ); diff --git a/packages/web/src/views/Forms/hooks/__tests__/useOpenEventForm.test.ts b/packages/web/src/views/Forms/hooks/__tests__/useOpenEventForm.test.ts new file mode 100644 index 000000000..94a68f5d1 --- /dev/null +++ b/packages/web/src/views/Forms/hooks/__tests__/useOpenEventForm.test.ts @@ -0,0 +1,209 @@ +import { act } from "react"; +import { renderHook } from "@testing-library/react"; +import { Origin, Priorities } from "@core/constants/core.constants"; +import dayjs from "@core/util/date/dayjs"; +import { getUserId } from "@web/auth/auth.util"; +import { + CLASS_TIMED_CALENDAR_EVENT, + DATA_EVENT_ELEMENT_ID, +} from "@web/common/constants/web.constants"; +import { useMousePosition } from "@web/common/hooks/useMousePosition"; +import { selectEventById } from "@web/ducks/events/selectors/event.selectors"; +import { SLOT_HEIGHT } from "@web/views/Day/constants/day.constants"; +import { useDateInView } from "@web/views/Day/hooks/navigation/useDateInView"; +import { getEventTimeFromPosition } from "@web/views/Day/util/agenda/agenda.util"; +import { useOpenEventForm } from "../useOpenEventForm"; + +jest.mock("@web/common/hooks/useMousePosition"); +jest.mock("@web/views/Day/hooks/navigation/useDateInView"); +jest.mock("@web/auth/auth.util"); +jest.mock("@web/ducks/events/selectors/event.selectors"); +jest.mock("@web/views/Day/util/agenda/agenda.util"); +jest.mock("@web/store", () => ({ + store: { + getState: jest.fn(), + }, +})); + +describe("useOpenEventForm", () => { + const mockSetDraft = jest.fn(); + const mockSetExisting = jest.fn(); + const mockSetOpenAtMousePosition = jest.fn(); + const mockSetReference = jest.fn(); + const mockDateInView = dayjs("2023-01-01T12:00:00.000Z"); + + beforeEach(() => { + jest.clearAllMocks(); + (useDateInView as jest.Mock).mockReturnValue(mockDateInView); + (getUserId as jest.Mock).mockResolvedValue("user-123"); + (useMousePosition as jest.Mock).mockReturnValue({ + element: null, + mousePointRef: { getBoundingClientRect: jest.fn(() => ({ top: 100 })) }, + floating: { refs: { setReference: mockSetReference } }, + setOpenAtMousePosition: mockSetOpenAtMousePosition, + isOverAllDayRow: false, + isOverMainGrid: false, + isOverSidebar: false, + isOverSomedayWeek: false, + isOverSomedayMonth: false, + }); + }); + + it("should not open form if user is not logged in", async () => { + (getUserId as jest.Mock).mockResolvedValue(null); + const { result } = renderHook(() => + useOpenEventForm({ + setDraft: mockSetDraft, + setExisting: mockSetExisting, + }), + ); + + await act(async () => { + await result.current(); + }); + + expect(mockSetDraft).not.toHaveBeenCalled(); + }); + + it("should create a new draft event when not hovering over existing event", async () => { + const { result } = renderHook(() => + useOpenEventForm({ + setDraft: mockSetDraft, + setExisting: mockSetExisting, + }), + ); + + await act(async () => { + await result.current(); + }); + + expect(mockSetExisting).toHaveBeenCalledWith(false); + expect(mockSetDraft).toHaveBeenCalledWith( + expect.objectContaining({ + title: "", + user: "user-123", + origin: Origin.COMPASS, + priority: Priorities.UNASSIGNED, + }), + ); + expect(mockSetOpenAtMousePosition).toHaveBeenCalledWith(true); + }); + + it("should create all-day event when hovering over all-day row", async () => { + (useMousePosition as jest.Mock).mockReturnValue({ + ...useMousePosition(), + isOverAllDayRow: true, + }); + + const { result } = renderHook(() => + useOpenEventForm({ + setDraft: mockSetDraft, + setExisting: mockSetExisting, + }), + ); + + await act(async () => { + await result.current(); + }); + + expect(mockSetDraft).toHaveBeenCalledWith( + expect.objectContaining({ + isAllDay: true, + startDate: mockDateInView.startOf("day").toISOString(), + endDate: mockDateInView.startOf("day").add(1, "day").toISOString(), + }), + ); + }); + + it("should create timed event when hovering over main grid", async () => { + (useMousePosition as jest.Mock).mockReturnValue({ + ...useMousePosition(), + isOverMainGrid: true, + mousePointRef: { getBoundingClientRect: () => ({ top: 100 }) }, + }); + (getEventTimeFromPosition as jest.Mock).mockImplementation((y) => { + if (y === 100) return mockDateInView.hour(10).minute(0); + if (y === 100 + SLOT_HEIGHT) return mockDateInView.hour(10).minute(15); + return mockDateInView; + }); + + const { result } = renderHook(() => + useOpenEventForm({ + setDraft: mockSetDraft, + setExisting: mockSetExisting, + }), + ); + + await act(async () => { + await result.current(); + }); + + expect(mockSetDraft).toHaveBeenCalledWith( + expect.objectContaining({ + isAllDay: false, + startDate: mockDateInView.hour(10).minute(0).toISOString(), + endDate: mockDateInView.hour(10).minute(15).toISOString(), + }), + ); + }); + + it("should open existing event when hovering over one", async () => { + const mockEventElement = document.createElement("div"); + mockEventElement.setAttribute(DATA_EVENT_ELEMENT_ID, "event-123"); + mockEventElement.classList.add(CLASS_TIMED_CALENDAR_EVENT); + + (useMousePosition as jest.Mock).mockReturnValue({ + ...useMousePosition(), + isOverMainGrid: true, + element: { closest: jest.fn().mockReturnValue(mockEventElement) }, + }); + const mockEvent = { _id: "event-123", title: "Existing Event" }; + (selectEventById as jest.Mock).mockReturnValue(mockEvent); + + const { result } = renderHook(() => + useOpenEventForm({ + setDraft: mockSetDraft, + setExisting: mockSetExisting, + }), + ); + + await act(async () => { + await result.current(); + }); + + expect(mockSetExisting).toHaveBeenCalledWith(true); + expect(mockSetDraft).toHaveBeenCalledWith(mockEvent); + expect(mockSetOpenAtMousePosition).toHaveBeenCalledWith(true); + }); + + it("should create new event even if hovering over existing event when create=true is passed", async () => { + const mockEventElement = document.createElement("div"); + mockEventElement.setAttribute(DATA_EVENT_ELEMENT_ID, "event-123"); + mockEventElement.classList.add(CLASS_TIMED_CALENDAR_EVENT); + + (useMousePosition as jest.Mock).mockReturnValue({ + ...useMousePosition(), + isOverMainGrid: true, + element: { closest: jest.fn().mockReturnValue(mockEventElement) }, + }); + + const { result } = renderHook(() => + useOpenEventForm({ + setDraft: mockSetDraft, + setExisting: mockSetExisting, + }), + ); + + await act(async () => { + await result.current(true); // Pass create=true + }); + + expect(mockSetExisting).toHaveBeenCalledWith(false); + expect(mockSetDraft).toHaveBeenCalledWith( + expect.objectContaining({ + title: "", + user: "user-123", + }), + ); + }); +}); diff --git a/packages/web/src/views/Forms/hooks/useOpenEventForm.ts b/packages/web/src/views/Forms/hooks/useOpenEventForm.ts index bf9238ce7..7e8c39457 100644 --- a/packages/web/src/views/Forms/hooks/useOpenEventForm.ts +++ b/packages/web/src/views/Forms/hooks/useOpenEventForm.ts @@ -48,74 +48,77 @@ export function useOpenEventForm({ } }, [isOverAllDayRow, isOverSomedayWeek, isOverSomedayMonth, isOverMainGrid]); - const openEventForm = useCallback(async () => { - const user = await getUserId(); + const openEventForm = useCallback( + async (create?: boolean) => { + const user = await getUserId(); - if (!user) return; + if (!user) return; - const event = element?.closest(`.${eventClass}`); - const existingEventId = event?.getAttribute(DATA_EVENT_ELEMENT_ID); + const event = element?.closest(`.${eventClass}`); + const existingEventId = event?.getAttribute(DATA_EVENT_ELEMENT_ID); - let draftEvent: Schema_Event; + let draftEvent: Schema_Event; - if (existingEventId) { - draftEvent = selectEventById(store.getState(), existingEventId); - setExisting(true); - } else { - let startTime: Dayjs = dayjs(); - let endTime: Dayjs = dayjs().add(15, "minutes"); + if (existingEventId && !create) { + draftEvent = selectEventById(store.getState(), existingEventId); + setExisting(true); + } else { + let startTime: Dayjs = dayjs(); + let endTime: Dayjs = dayjs().add(15, "minutes"); - if (isOverAllDayRow) { - const date = dateInView.startOf("day"); - startTime = date; - endTime = date.add(1, "day"); - } else if (isOverSomedayWeek || isOverSomedayMonth) { - const now = dayjs(); - const date = dateInView.hour(now.hour()).minute(now.minute()); - startTime = date; - endTime = date.add(15, "minutes"); - } else if (isOverMainGrid) { - const boundingRect = mousePointRef?.getBoundingClientRect(); - const startTimeY = boundingRect?.top ?? 0; - const endTimeY = startTimeY + SLOT_HEIGHT; - startTime = getEventTimeFromPosition(startTimeY, dateInView); - endTime = getEventTimeFromPosition(endTimeY, dateInView); - } + if (isOverAllDayRow) { + const date = dateInView.startOf("day"); + startTime = date; + endTime = date.add(1, "day"); + } else if (isOverSomedayWeek || isOverSomedayMonth) { + const now = dayjs(); + const date = dateInView.hour(now.hour()).minute(now.minute()); + startTime = date; + endTime = date.add(15, "minutes"); + } else if (isOverMainGrid) { + const boundingRect = mousePointRef?.getBoundingClientRect(); + const startTimeY = boundingRect?.top ?? 0; + const endTimeY = startTimeY + SLOT_HEIGHT; + startTime = getEventTimeFromPosition(startTimeY, dateInView); + endTime = getEventTimeFromPosition(endTimeY, dateInView); + } - draftEvent = { - title: "", - description: "", - startDate: startTime.toISOString(), - endDate: endTime.toISOString(), - isAllDay: isOverAllDayRow, - isSomeday: isOverSidebar, - user, - priority: Priorities.UNASSIGNED, - origin: Origin.COMPASS, - }; + draftEvent = { + title: "", + description: "", + startDate: startTime.toISOString(), + endDate: endTime.toISOString(), + isAllDay: isOverAllDayRow, + isSomeday: isOverSidebar, + user, + priority: Priorities.UNASSIGNED, + origin: Origin.COMPASS, + }; - setExisting(false); - } + setExisting(false); + } - setReference?.(mousePointRef); + setReference?.(mousePointRef); - setDraft(draftEvent); - setOpenAtMousePosition(true); - }, [ - element, - eventClass, - setReference, - mousePointRef, - setDraft, - setExisting, - setOpenAtMousePosition, - isOverAllDayRow, - isOverSomedayWeek, - isOverSomedayMonth, - isOverMainGrid, - isOverSidebar, - dateInView, - ]); + setDraft(draftEvent); + setOpenAtMousePosition(true); + }, + [ + element, + eventClass, + setReference, + mousePointRef, + setDraft, + setExisting, + setOpenAtMousePosition, + isOverAllDayRow, + isOverSomedayWeek, + isOverSomedayMonth, + isOverMainGrid, + isOverSidebar, + dateInView, + ], + ); return openEventForm; }