Skip to content

Commit 77bccf8

Browse files
committed
fix(web): keep multi-week drag source
1 parent 5a51f59 commit 77bccf8

2 files changed

Lines changed: 75 additions & 1 deletion

File tree

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { EventIdSchema } from "@core/types/domain-primitives";
2+
import { EventScheduleSchema } from "@core/types/event.contracts";
3+
import { createMockEvent } from "@web/__tests__/utils/factories/event.factory";
4+
import { resolveInteractionSourceEvent } from "./WeekInteractionCoordinator";
5+
import { describe, expect, it } from "bun:test";
6+
7+
const createSourceEvent = (kind: "allDay" | "timed") =>
8+
createMockEvent({
9+
id: EventIdSchema.parse(
10+
kind === "timed"
11+
? "aaaaaaaaaaaaaaaaaaaaaaaa"
12+
: "bbbbbbbbbbbbbbbbbbbbbbbb",
13+
),
14+
schedule: EventScheduleSchema.parse(
15+
kind === "timed"
16+
? {
17+
kind,
18+
start: "2026-05-20T09:00:00.000Z",
19+
end: "2026-05-20T10:00:00.000Z",
20+
timeZone: "UTC",
21+
}
22+
: {
23+
kind,
24+
start: "2026-05-20",
25+
end: "2026-05-21",
26+
},
27+
),
28+
});
29+
30+
describe("resolveInteractionSourceEvent", () => {
31+
for (const kind of ["timed", "allDay"] as const) {
32+
it(`retains the ${kind} source after navigation replaces the visible week`, () => {
33+
const activeEvent = createSourceEvent(kind);
34+
35+
expect(
36+
resolveInteractionSourceEvent(activeEvent.id, new Map(), activeEvent),
37+
).toBe(activeEvent);
38+
});
39+
}
40+
41+
it("does not reuse an active event for a different interaction", () => {
42+
const activeEvent = createSourceEvent("timed");
43+
44+
expect(
45+
resolveInteractionSourceEvent(
46+
"cccccccccccccccccccccccc",
47+
new Map(),
48+
activeEvent,
49+
),
50+
).toBeUndefined();
51+
});
52+
});

packages/web/src/views/Week/interaction/WeekInteractionCoordinator.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export const WeekInteractionCoordinator: FC<Props> = ({
4242
});
4343
const { actions, confirmation, setters, state } = useDraftContext();
4444
const mutations = useEventMutations();
45+
const activeInteractionEventRef = useRef<Event | null>(null);
4546
const layoutSourcesRef = useRef(getLayoutSources);
4647
const timedEventsById = useMemo(() => {
4748
return mapEventsById(timedEvents);
@@ -96,7 +97,13 @@ export const WeekInteractionCoordinator: FC<Props> = ({
9697
// draft from the query cache's source Event plus the engine's resulting
9798
// dates.
9899
const gridEventDraftFromSavedResult = (event: GridEvent) => {
99-
const sourceEvent = event._id ? eventsById.get(event._id) : undefined;
100+
const sourceEvent = event._id
101+
? resolveInteractionSourceEvent(
102+
event._id,
103+
eventsById,
104+
activeInteractionEventRef.current,
105+
)
106+
: undefined;
100107
const draft = sourceEvent ? editGridEventDraft(sourceEvent, "this") : null;
101108

102109
if (!draft) return null;
@@ -176,6 +183,13 @@ export const WeekInteractionCoordinator: FC<Props> = ({
176183
onCommitTimedDrag: commitSavedMutation,
177184
onCommitTimedResize: commitSavedMutation,
178185
onMotionActivation: (target) => {
186+
// Edge navigation replaces the visible query before pointer-up. Retain
187+
// the canonical source so the destination-week commit can still build
188+
// its strict mutation input.
189+
activeInteractionEventRef.current = target.event._id
190+
? (eventsById.get(target.event._id) ?? null)
191+
: null;
192+
179193
if (target.hadFormOpenBeforeInteraction) {
180194
actions.closeForm();
181195
}
@@ -208,3 +222,11 @@ const mapEventsById = (events: GridEvent[]) => {
208222

209223
return eventsById;
210224
};
225+
226+
export const resolveInteractionSourceEvent = (
227+
eventId: string,
228+
visibleEventsById: ReadonlyMap<string, Event>,
229+
activeInteractionEvent: Event | null,
230+
) =>
231+
visibleEventsById.get(eventId) ??
232+
(activeInteractionEvent?.id === eventId ? activeInteractionEvent : undefined);

0 commit comments

Comments
 (0)