This commit is contained in:
Oguzhan Cetinkaya 2026-05-18 21:42:35 +03:00 committed by GitHub
commit 0965c10aeb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 84 additions and 4 deletions

View File

@ -55,6 +55,10 @@ export interface ElementSelectionApi {
getSelected: () => readonly ElementRef[];
isSelected: (ref: ElementRef) => boolean;
select: (ref: ElementRef) => void;
selectRange: (args: {
anchor?: ElementRef | null;
target: ElementRef;
}) => readonly ElementRef[];
handleClick: (args: ElementRef & { isMultiKey: boolean }) => void;
clearKeyframeSelection: () => void;
}
@ -295,6 +299,7 @@ export class ElementInteractionController {
// has already returned to idle, so the "was this a drag?" answer must
// outlive the session. Reset on the next mousedown.
private lastGestureWasDrag = false;
private elementSelectionAnchor: ElementRef | null = null;
private readonly subscribers = new Set<() => void>();
private readonly depsRef: ElementInteractionDepsRef;
@ -372,13 +377,29 @@ export class ElementInteractionController {
const ref = { trackId: track.id, elementId: element.id };
if (event.metaKey || event.ctrlKey || event.shiftKey) {
let rangeSelection: readonly ElementRef[] | null = null;
if (event.shiftKey) {
const anchor =
this.elementSelectionAnchor?.trackId === ref.trackId
? this.elementSelectionAnchor
: null;
rangeSelection = this.deps.selection.selectRange({
anchor: anchor ?? ref,
target: ref,
});
if (!anchor) {
this.elementSelectionAnchor = ref;
}
} else if (event.metaKey || event.ctrlKey) {
this.deps.selection.handleClick({ ...ref, isMultiKey: true });
}
const selectedElements = this.deps.selection.isSelected(ref)
? this.deps.selection.getSelected()
: [ref];
const selectedElements =
rangeSelection ??
(this.deps.selection.isSelected(ref)
? this.deps.selection.getSelected()
: [ref]);
this.session = {
kind: "pending",
@ -423,9 +444,11 @@ export class ElementInteractionController {
this.deps.selection.getSelected().length > 1
) {
this.deps.selection.select(ref);
this.elementSelectionAnchor = ref;
return;
}
this.elementSelectionAnchor = ref;
this.deps.selection.clearKeyframeSelection();
};

View File

@ -50,6 +50,7 @@ export function useElementInteraction({
getSelected: () => selection.selectedElements,
isSelected: selection.isElementSelected,
select: selection.selectElement,
selectRange: selection.selectElementRange,
handleClick: selection.handleElementClick,
clearKeyframeSelection: () => editor.selection.clearKeyframeSelection(),
},

View File

@ -1,5 +1,6 @@
import { useCallback } from "react";
import { useEditor } from "@/editor/use-editor";
import { findTrackInSceneTracks } from "@/timeline/track-element-update";
import type { ElementRef } from "@/timeline/types";
export function useElementSelection() {
@ -101,6 +102,60 @@ export function useElementSelection() {
[selectedElements, editor],
);
/**
* Handles Shift-click range selection. Selects all clips between the anchor
* and target when both are on the same track; otherwise selects only target.
*/
const selectElementRange = useCallback(
({
anchor,
target,
}: {
anchor?: ElementRef | null;
target: ElementRef;
}) => {
const setSelection = (elements: ElementRef[]) => {
editor.selection.setSelectedElements({ elements });
return elements;
};
const track = findTrackInSceneTracks({
tracks: editor.scenes.getActiveScene().tracks,
trackId: target.trackId,
});
if (!track || anchor?.trackId !== target.trackId) {
return setSelection([target]);
}
const orderedElements = track.elements
.map((element, index) => ({ element, index }))
.sort((a, b) =>
a.element.startTime === b.element.startTime
? a.index - b.index
: a.element.startTime - b.element.startTime,
);
const anchorIndex = orderedElements.findIndex(
({ element }) => element.id === anchor.elementId,
);
const targetIndex = orderedElements.findIndex(
({ element }) => element.id === target.elementId,
);
if (anchorIndex === -1 || targetIndex === -1) {
return setSelection([target]);
}
const start = Math.min(anchorIndex, targetIndex);
const end = Math.max(anchorIndex, targetIndex);
return setSelection(
orderedElements.slice(start, end + 1).map(({ element }) => ({
trackId: target.trackId,
elementId: element.id,
})),
);
},
[editor],
);
/**
* Handles click interaction on an element.
@ -128,6 +183,7 @@ export function useElementSelection() {
selectElement,
setElementSelection,
mergeElementsIntoSelection,
selectElementRange,
addElementToSelection,
removeElementFromSelection,
toggleElementSelection,