Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,10 @@ export class InteractionController implements TimelineInteractionRegistration {
const clip = this.stateManager.getClipAt(clipRef.trackIndex, clipRef.clipIndex);
if (!clip) return;

this.beginPointerInteraction(e);
// No pointer capture yet: capturing retargets the eventual click event to
// the tracks container, which would break click handlers on clip children
// (e.g. the mask badge). Capture happens if this becomes a real drag.
this.activePointerId = e.pointerId;
this.state = createPendingState({ x: e.clientX, y: e.clientY }, clipRef, clip.config.start);
}

Expand Down Expand Up @@ -297,6 +300,7 @@ export class InteractionController implements TimelineInteractionRegistration {
const ghost = createDragGhost(clip.config.length, clipAssetType, sourceTrackHeight, pps);
this.feedbackElements.container.appendChild(ghost);

this.beginPointerInteraction(e);
this.state = createDraggingState(state, clipElement, ghost, dragOffsetX, dragOffsetY, originalStyles, clip.config.length, e.altKey);

// Position ghost at current clip position initially
Expand Down
16 changes: 16 additions & 0 deletions tests/interaction-controller.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,22 @@ describe("InteractionController", () => {
document.dispatchEvent(createPointerEvent("pointermove", { clientX: 100, clientY: 20 }));
};

it("captures the pointer only when a drag starts, not on plain pointerdown", () => {
createController();
const clipElement = mockDOM.tracksContainer.querySelector(".ss-clip") as HTMLElement;
const setPointerCapture = jest.fn();
mockDOM.tracksContainer.setPointerCapture = setPointerCapture;

// Plain pointerdown (a click on the clip or a child like the mask badge)
// must not capture, or the resulting click event retargets away from the child
clipElement.dispatchEvent(createPointerEvent("pointerdown", { clientX: 50, clientY: 20 }));
expect(setPointerCapture).not.toHaveBeenCalled();

document.dispatchEvent(createPointerEvent("pointermove", { clientX: 100, clientY: 20 }));
expect(controller.isDragging(0, 0)).toBe(true);
expect(setPointerCapture).toHaveBeenCalled();
});

it("cancels drag on pointercancel, restoring the clip without executing commands", () => {
createController();
const clipElement = mockDOM.tracksContainer.querySelector(".ss-clip") as HTMLElement;
Expand Down
Loading