refactor: split mask snap dispatch

Made-with: Cursor
This commit is contained in:
Maze Winther 2026-04-29 18:23:08 +02:00
parent 401cc35743
commit 6ea5a156b6
4 changed files with 85 additions and 45 deletions

View File

@ -13,7 +13,7 @@ import {
import { getSplitMaskStrokeSegment } from "@/masks/definitions/split";
import { textMaskDefinition } from "@/masks/definitions/text";
import { getMaskSnapGeometry } from "@/masks/geometry";
import { snapMaskInteraction } from "@/masks/snap";
import { snapBoxMaskInteraction, snapSplitMaskInteraction } from "@/masks/snap";
import type { ElementBounds } from "@/preview/element-bounds";
import type {
CustomMaskParams,
@ -230,7 +230,7 @@ describe("mask geometry", () => {
describe("mask snapping", () => {
test("snaps split mask movement using the shared position pipeline", () => {
const result = snapMaskInteraction({
const result = snapSplitMaskInteraction({
handleId: "position",
startParams: buildSplitParams({
centerX: 0.03,
@ -254,7 +254,7 @@ describe("mask snapping", () => {
});
test("snaps box mask movement against element center and edges", () => {
const result = snapMaskInteraction({
const result = snapBoxMaskInteraction({
handleId: "position",
startParams: buildRectangleParams(),
proposedParams: buildRectangleParams({
@ -275,7 +275,7 @@ describe("mask snapping", () => {
});
test("snaps mask rotation through the shared rotation path", () => {
const result = snapMaskInteraction({
const result = snapBoxMaskInteraction({
handleId: "rotation",
startParams: buildRectangleParams(),
proposedParams: buildRectangleParams({
@ -291,7 +291,7 @@ describe("mask snapping", () => {
});
test("snaps edge resize for box masks", () => {
const result = snapMaskInteraction({
const result = snapBoxMaskInteraction({
handleId: "right",
startParams: buildRectangleParams(),
proposedParams: buildRectangleParams({
@ -307,7 +307,7 @@ describe("mask snapping", () => {
});
test("snaps corner resize for box masks", () => {
const result = snapMaskInteraction({
const result = snapBoxMaskInteraction({
handleId: "bottom-right",
startParams: buildRectangleParams(),
proposedParams: buildRectangleParams({

View File

@ -16,7 +16,7 @@ import {
getBoxMaskHandlePositions,
getBoxMaskOverlays,
} from "@/masks/handle-positions";
import { snapMaskInteraction } from "@/masks/snap";
import { snapBoxMaskInteraction } from "@/masks/snap";
const PERCENTAGE_DISPLAY: Pick<
NumberParamDefinition,
@ -198,7 +198,7 @@ export function buildBoxMaskInteraction({
};
},
snap(args) {
return snapMaskInteraction(args);
return snapBoxMaskInteraction(args);
},
};
}

View File

@ -9,7 +9,7 @@ import {
getLineMaskHandlePositions,
getLineMaskOverlay,
} from "@/masks/handle-positions";
import { snapMaskInteraction } from "@/masks/snap";
import { snapSplitMaskInteraction } from "@/masks/snap";
// cos(π/2) returns ~6e-17 in JS, not 0. Values below this threshold are snapped
// to exactly 0 to prevent opposite-sign float noise on canvas corners that lie
@ -216,7 +216,7 @@ export const splitMaskDefinition: MaskDefinition<SplitMaskParams> = {
};
},
snap(args) {
return snapMaskInteraction(args);
return snapSplitMaskInteraction(args);
},
},
buildDefault() {

View File

@ -6,23 +6,19 @@ import {
snapScale,
snapScaleAxes,
type ScaleEdgePreference,
type SnapLine,
} from "@/preview/preview-snap";
import type { RectangleMaskParams, SplitMaskParams } from "@/masks/types";
import type {
MaskSnapArgs,
MaskSnapResult,
RectangleMaskParams,
SplitMaskParams,
} from "@/masks/types";
import {
isRectangleMaskParams,
getMaskSnapGeometry,
setMaskLocalCenter,
toGlobalMaskSnapLines,
} from "./geometry";
type SharedMaskParams = SplitMaskParams | RectangleMaskParams;
type MaskSnapResult<TParams extends SharedMaskParams> = {
params: TParams;
activeLines: SnapLine[];
};
const CORNER_SIZE_HANDLES = new Set([
"top-left",
"top-right",
@ -75,11 +71,33 @@ function snapMaskPosition({
canvasSize,
snapThreshold,
}: {
proposedParams: SharedMaskParams;
proposedParams: RectangleMaskParams;
bounds: ElementBounds;
canvasSize: { width: number; height: number };
snapThreshold: { x: number; y: number };
}): MaskSnapResult<SharedMaskParams> {
}): MaskSnapResult<RectangleMaskParams>;
function snapMaskPosition({
proposedParams,
bounds,
canvasSize,
snapThreshold,
}: {
proposedParams: SplitMaskParams;
bounds: ElementBounds;
canvasSize: { width: number; height: number };
snapThreshold: { x: number; y: number };
}): MaskSnapResult<SplitMaskParams>;
function snapMaskPosition({
proposedParams,
bounds,
canvasSize,
snapThreshold,
}: {
proposedParams: RectangleMaskParams | SplitMaskParams;
bounds: ElementBounds;
canvasSize: { width: number; height: number };
snapThreshold: { x: number; y: number };
}): MaskSnapResult<RectangleMaskParams | SplitMaskParams> {
const geometry = getMaskSnapGeometry({
params: proposedParams,
bounds,
@ -115,12 +133,18 @@ function snapMaskPosition({
function snapMaskRotation({
proposedParams,
}: {
proposedParams: SharedMaskParams;
}): MaskSnapResult<SharedMaskParams> {
if (typeof proposedParams.rotation !== "number") {
return { params: proposedParams, activeLines: [] };
}
proposedParams: RectangleMaskParams;
}): MaskSnapResult<RectangleMaskParams>;
function snapMaskRotation({
proposedParams,
}: {
proposedParams: SplitMaskParams;
}): MaskSnapResult<SplitMaskParams>;
function snapMaskRotation({
proposedParams,
}: {
proposedParams: RectangleMaskParams | SplitMaskParams;
}): MaskSnapResult<RectangleMaskParams | SplitMaskParams> {
const { snappedRotation } = snapRotation({
proposedRotation: proposedParams.rotation,
});
@ -143,19 +167,12 @@ function snapBoxMaskSize({
snapThreshold,
}: {
handleId: string;
startParams: SharedMaskParams;
proposedParams: SharedMaskParams;
startParams: RectangleMaskParams;
proposedParams: RectangleMaskParams;
bounds: ElementBounds;
canvasSize: { width: number; height: number };
snapThreshold: { x: number; y: number };
}): MaskSnapResult<SharedMaskParams> {
if (
!isRectangleMaskParams(startParams) ||
!isRectangleMaskParams(proposedParams)
) {
return { params: proposedParams, activeLines: [] };
}
}): MaskSnapResult<RectangleMaskParams> {
const geometry = getMaskSnapGeometry({
params: proposedParams,
bounds,
@ -297,7 +314,7 @@ function snapBoxMaskSize({
return { params: proposedParams, activeLines: [] };
}
export function snapMaskInteraction<TParams extends SharedMaskParams>({
export function snapBoxMaskInteraction({
handleId,
startParams,
proposedParams,
@ -306,23 +323,23 @@ export function snapMaskInteraction<TParams extends SharedMaskParams>({
snapThreshold,
}: {
handleId: string;
startParams: TParams;
proposedParams: TParams;
startParams: RectangleMaskParams;
proposedParams: RectangleMaskParams;
bounds: ElementBounds;
canvasSize: { width: number; height: number };
snapThreshold: { x: number; y: number };
}): MaskSnapResult<TParams> {
}): MaskSnapResult<RectangleMaskParams> {
if (handleId === "position") {
return snapMaskPosition({
proposedParams,
bounds,
canvasSize,
snapThreshold,
}) as MaskSnapResult<TParams>;
});
}
if (handleId === "rotation") {
return snapMaskRotation({ proposedParams }) as MaskSnapResult<TParams>;
return snapMaskRotation({ proposedParams });
}
return snapBoxMaskSize({
@ -332,5 +349,28 @@ export function snapMaskInteraction<TParams extends SharedMaskParams>({
bounds,
canvasSize,
snapThreshold,
}) as MaskSnapResult<TParams>;
});
}
export function snapSplitMaskInteraction({
handleId,
proposedParams,
bounds,
canvasSize,
snapThreshold,
}: MaskSnapArgs<SplitMaskParams>): MaskSnapResult<SplitMaskParams> {
if (handleId === "position") {
return snapMaskPosition({
proposedParams,
bounds,
canvasSize,
snapThreshold,
});
}
if (handleId === "rotation") {
return snapMaskRotation({ proposedParams });
}
return { params: proposedParams, activeLines: [] };
}