import {
Renderer, ClippableProps, OnClip,
ClippableState, OnClipEnd, OnClipStart,
ControlPose, MoveableManagerInterface, DraggableProps,
} from "../types";
import { convertUnitSize, getRad } from "@daybrush/utils";
import {
prefix, calculatePosition, getDiagonalSize,
fillParams, triggerEvent,
makeMatrixCSS, getRect, fillEndParams,
getSizeDistByDist,
getProps,
fillCSSObject,
abs,
sign,
} from "../utils";
import { plus, minus, multiply } from "@scena/matrix";
import { getDragDist, calculatePointerDist, setDragStart } from "../gesto/GestoUtils";
import {
HORIZONTAL_RADIUS_ORDER, VERTICAL_RADIUS_ORDER, addRadiusPos, removeRadiusPos,
} from "./roundable/borderRadius";
import { renderLine } from "../renderDirections";
import { checkSnapBoundPriority } from "./snappable/snap";
import { checkSnapBounds } from "./snappable/snapBounds";
import { getDefaultGuidelines } from "./snappable/getTotalGuidelines";
import {
getControlSize, getClipPath,
getClipStyles, getRectPoses,
} from "./clippable/utils";
export function moveControlPos(
controlPoses: ControlPose[],
index: number,
dist: number[],
isRect?: boolean,
keepRatio?: boolean,
) {
const { direction, sub } = controlPoses[index];
const dists = controlPoses.map(() => [0, 0]);
const directions = direction ? direction.split("") : [];
if (isRect && index < 8) {
const verticalDirections = directions.filter(dir => dir === "w" || dir === "e");
const horizontalDirections = directions.filter(dir => dir === "n" || dir === "s");
const verticalDirection = verticalDirections[0];
const horizontalDirection = horizontalDirections[0];
dists[index] = dist;
const [width, height] = getControlSize(controlPoses);
const ratio = width && height ? width / height : 0;
if (ratio && keepRatio) {
// 0 1 2
// 7 3
// 6 5 4
const fixedIndex = (index + 4) % 8;
const fixedPosition = controlPoses[fixedIndex].pos;
const sizeDirection = [0, 0];
if (direction!.indexOf("w") > -1) {
sizeDirection[0] = -1;
} else if (direction!.indexOf("e") > -1) {
sizeDirection[0] = 1;
}
if (direction!.indexOf("n") > -1) {
sizeDirection[1] = -1;
} else if (direction!.indexOf("s") > -1) {
sizeDirection[1] = 1;
}
const nextDist = getSizeDistByDist(
[width, height],
dist,
ratio,
sizeDirection,
true,
);
const nextWidth = width + nextDist[0];
const nextHeight = height + nextDist[1];
let top = fixedPosition[1];
let bottom = fixedPosition[1];
let left = fixedPosition[0];
let right = fixedPosition[0];
if (sizeDirection[0] === -1) {
left = right - nextWidth;
} else if (sizeDirection[0] === 1) {
right = left + nextWidth;
} else {
left = left - nextWidth / 2;
right = right + nextWidth / 2;
}
if (sizeDirection[1] === -1) {
top = bottom - nextHeight;
} else if (sizeDirection[1] === 1) {
bottom = top + nextHeight;
} else {
top = bottom - nextHeight / 2;
bottom = top + nextHeight;
}
const nextControlPoses = getRectPoses(top, right, bottom, left);
controlPoses.forEach((controlPose, i) => {
dists[i][0] = nextControlPoses[i].pos[0] - controlPose.pos[0];
dists[i][1] = nextControlPoses[i].pos[1] - controlPose.pos[1];
});
} else {
controlPoses.forEach((controlPose, i) => {
const {
direction: controlDir,
} = controlPose;
if (!controlDir) {
return;
}
if (controlDir.indexOf(verticalDirection) > -1) {
dists[i][0] = dist[0];
}
if (controlDir.indexOf(horizontalDirection) > -1) {
dists[i][1] = dist[1];
}
});
if (verticalDirection) {
dists[1][0] = dist[0] / 2;
dists[5][0] = dist[0] / 2;
}
if (horizontalDirection) {
dists[3][1] = dist[1] / 2;
dists[7][1] = dist[1] / 2;
}
}
} else if (direction && !sub) {
directions.forEach(dir => {
const isVertical = dir === "n" || dir === "s";
controlPoses.forEach((controlPose, i) => {
const {
direction: dirDir,
horizontal: dirHorizontal,
vertical: dirVertical,
} = controlPose;
if (!dirDir || dirDir.indexOf(dir) === -1) {
return;
}
dists[i] = [
isVertical || !dirHorizontal ? 0 : dist[0],
!isVertical || !dirVertical ? 0 : dist[1],
];
});
});
} else {
dists[index] = dist;
}
return dists;
}
function addClipPath(moveable: MoveableManagerInterface<ClippableProps>, e: any) {
const [distX, distY] = calculatePointerDist(moveable, e);
const { clipPath, clipIndex } = e.datas;
const {
type: clipType,
poses: clipPoses,
splitter,
} = (clipPath as ReturnType<typeof getClipPath>)!;
const poses = clipPoses.map(pos => pos.pos);
if (clipType === "polygon") {
poses.splice(clipIndex, 0, [distX, distY]);
} else if (clipType === "inset") {
const horizontalIndex = HORIZONTAL_RADIUS_ORDER.indexOf(clipIndex);
const verticalIndex = VERTICAL_RADIUS_ORDER.indexOf(clipIndex);
const length = clipPoses.length;
addRadiusPos(
clipPoses,
poses,
8,
horizontalIndex,
verticalIndex,
distX,
distY,
poses[4][0],
poses[4][1],
poses[0][0],
poses[0][1],
);
if (length === clipPoses.length) {
return;
}
} else {
return;
}
const clipStyles = getClipStyles(moveable, clipPath, poses)!;
const clipStyle = `${clipType}(${clipStyles.join(splitter)})`;
triggerEvent(moveable, "onClip", fillParams<OnClip>(moveable, e, {
clipEventType: "added",
clipType,
poses,
clipStyles,
clipStyle,
distX: 0,
distY: 0,
...fillCSSObject({
clipPath: clipStyle,
}, e),
}));
}
function removeClipPath(moveable: MoveableManagerInterface<ClippableProps>, e: any) {
const { clipPath, clipIndex } = e.datas;
const {
type: clipType,
poses: clipPoses,
splitter,
} = (clipPath as ReturnType<typeof getClipPath>)!;
const poses = clipPoses.map(pos => pos.pos);
const length = poses.length;
if (clipType === "polygon") {
clipPoses.splice(clipIndex, 1);
poses.splice(clipIndex, 1);
} else if (clipType === "inset") {
if (clipIndex < 8) {
return;
}
removeRadiusPos(clipPoses, poses, clipIndex, 8, length);
if (length === clipPoses.length) {
return;
}
} else {
return;
}
const clipStyles = getClipStyles(moveable, clipPath, poses)!;
const clipStyle = `${clipType}(${clipStyles.join(splitter)})`;
triggerEvent(moveable, "onClip", fillParams<OnClip>(moveable, e, {
clipEventType: "removed",
clipType,
poses,
clipStyles,
clipStyle,
distX: 0,
distY: 0,
...fillCSSObject({
clipPath: clipStyle,
}, e),
}));
}
/**
* @namespace Moveable.Clippable
* @description Whether to clip the target.
*/
export default {
name: "clippable",
props: [
"clippable",
"defaultClipPath",
"customClipPath",
"keepRatio",
"clipRelative",
"clipArea",
"dragWithClip",
"clipTargetBounds",
"clipVerticalGuidelines",
"clipHorizontalGuidelines",
"clipSnapThreshold",
] as const,
events: [
"clipStart",
"clip",
"clipEnd",
] as const,
css: [
`.control.clip-control {
background: #6d6;
cursor: pointer;
}
.control.clip-control.clip-radius {
background: #d66;
}
.line.clip-line {
background: #6e6;
cursor: move;
z-index: 1;
}
.clip-area {
position: absolute;
top: 0;
left: 0;
}
.clip-ellipse {
position: absolute;
cursor: move;
border: 1px solid #6d6;
border: var(--zoompx) solid #6d6;
border-radius: 50%;
transform-origin: 0px 0px;
}`,
`:host {
--bounds-color: #d66;
}`,
`.guideline {
pointer-events: none;
z-index: 2;
}`,
`.line.guideline.bounds {
background: #d66;
background: var(--bounds-color);
}`,
],
render(moveable: MoveableManagerInterface<ClippableProps, ClippableState>, React: Renderer): any[] {
const {
customClipPath,
defaultClipPath,
clipArea, zoom,
groupable,
} = moveable.props;
const {
target, width, height, allMatrix, is3d, left, top,
pos1, pos2, pos3, pos4,
clipPathState,
snapBoundInfos,
rotation: rotationRad,
} = moveable.getState();
if (!target || groupable) {
return [];
}
const clipPath = getClipPath(
target, width, height, defaultClipPath || "inset", clipPathState || customClipPath);
if (!clipPath) {
return [];
}
const n = is3d ? 4 : 3;
const type = clipPath.type;
const clipPoses = clipPath.poses;
const poses = clipPoses.map(pos => {
// return [x, y];
const calculatedPos = calculatePosition(allMatrix, pos.pos, n);
return [
calculatedPos[0] - left,
calculatedPos[1] - top,
];
});
let controls: any[] = [];
let lines: any[] = [];
const isRect = type === "rect";
const isInset = type === "inset";
const isPolygon = type === "polygon";
if (isRect || isInset || isPolygon) {
const linePoses = isInset ? poses.slice(0, 8) : poses;
lines = linePoses.map((to, i) => {
const from = i === 0 ? linePoses[linePoses.length - 1] : linePoses[i - 1];
const rad = getRad(from, to);
const dist = getDiagonalSize(from, to);
return <div key={`clipLine${i}`} className={prefix("line", "clip-line", "snap-control")}
data-clip-index={i}
style={{
width: `${dist}px`,
transform: `translate(${from[0]}px, ${from[1]}px) rotate(${rad}rad) scaleY(${zoom})`,
}}></div>;
});
}
controls = poses.map((pos, i) => {
return <div key={`clipControl${i}`}
className={prefix("control", "clip-control", "snap-control")}
data-clip-index={i}
style={{
transform: `translate(${pos[0]}px, ${pos[1]}px) rotate(${rotationRad}rad) scale(${zoom})`,
}}></div>;
});
if (isInset) {
controls.push(...poses.slice(8).map((pos, i) => {
return <div key={`clipRadiusControl${i}`}
className={prefix("control", "clip-control", "clip-radius", "snap-control")}
data-clip-index={8 + i}
style={{
transform: `translate(${pos[0]}px, ${pos[1]}px) rotate(${rotationRad}rad) scale(${zoom})`,
}}></div>;
}));
}
if (type === "circle" || type === "ellipse") {
const {
left: clipLeft,
top: clipTop,
radiusX,
radiusY,
} = clipPath;
const [distLeft, distTop] = minus(
calculatePosition(allMatrix, [clipLeft!, clipTop!], n),
calculatePosition(allMatrix, [0, 0], n),
);
let ellipseClipPath = "none";
if (!clipArea) {
const piece = Math.max(10, radiusX! / 5, radiusY! / 5);
const areaPoses: number[][] = [];
for (let i = 0; i <= piece; ++i) {
const rad = Math.PI * 2 / piece * i;
areaPoses.push([
radiusX! + (radiusX! - zoom!) * Math.cos(rad),
radiusY! + (radiusY! - zoom!) * Math.sin(rad),
]);
}
areaPoses.push([radiusX!, -2]);
areaPoses.push([-2, -2]);
areaPoses.push([-2, radiusY! * 2 + 2]);
areaPoses.push([radiusX! * 2 + 2, radiusY! * 2 + 2]);
areaPoses.push([radiusX! * 2 + 2, -2]);
areaPoses.push([radiusX!, -2]);
ellipseClipPath = `polygon(${areaPoses.map(pos => `${pos[0]}px ${pos[1]}px`).join(", ")})`;
}
controls.push(<div key="clipEllipse" className={prefix("clip-ellipse", "snap-control")} style={{
width: `${radiusX! * 2}px`,
height: `${radiusY! * 2}px`,
clipPath: ellipseClipPath,
transform: `translate(${-left + distLeft}px, ${-top + distTop}px) ${makeMatrixCSS(allMatrix)}`,
}}></div>);
}
if (clipArea) {
const {
width: allWidth,
height: allHeight,
left: allLeft,
top: allTop,
} = getRect([pos1, pos2, pos3, pos4, ...poses]);
if (isPolygon || isRect || isInset) {
const areaPoses = isInset ? poses.slice(0, 8) : poses;
controls.push(<div key="clipArea" className={prefix("clip-area", "snap-control")} style={{
width: `${allWidth}px`,
height: `${allHeight}px`,
transform: `translate(${allLeft}px, ${allTop}px)`,
clipPath: `polygon(${areaPoses.map(pos => `${pos[0] - allLeft}px ${pos[1] - allTop}px`).join(", ")})`,
}}></div>);
}
}
if (snapBoundInfos) {
(["vertical", "horizontal"] as const).forEach(directionType => {
const info = snapBoundInfos[directionType];
const isHorizontal = directionType === "horizontal";
if (info.isSnap) {
lines.push(...info.snap.posInfos.map(({ pos }, i) => {
const snapPos1 = minus(calculatePosition(
allMatrix, isHorizontal ? [0, pos] : [pos, 0], n), [left, top]);
const snapPos2 = minus(calculatePosition(
allMatrix, isHorizontal ? [width, pos] : [pos, height], n), [left, top]);
return renderLine(
React, "", snapPos1, snapPos2, zoom!,
`clip${directionType}snap${i}`, "guideline");
}));
}
if (info.isBound) {
lines.push(...info.bounds.map(({ pos }, i) => {
const snapPos1 = minus(calculatePosition(
allMatrix, isHorizontal ? [0, pos] : [pos, 0], n), [left, top]);
const snapPos2 = minus(calculatePosition(
allMatrix, isHorizontal ? [width, pos] : [pos, height], n), [left, top]);
return renderLine(
React, "", snapPos1, snapPos2, zoom!,
`clip${directionType}bounds${i}`, "guideline", "bounds", "bold");
}));
}
});
}
return [
...controls,
...lines,
];
},
dragControlCondition(moveable: any, e: any) {
return e.inputEvent && (e.inputEvent.target.getAttribute("class") || "").indexOf("clip") > -1;
},
dragStart(moveable: MoveableManagerInterface<ClippableProps, ClippableState>, e: any) {
const props = moveable.props;
const {
dragWithClip = true,
} = props;
if (dragWithClip) {
return false;
}
return this.dragControlStart(moveable, e);
},
drag(moveable: MoveableManagerInterface<ClippableProps, ClippableState>, e: any) {
return this.dragControl(moveable, { ...e, isDragTarget: true });
},
dragEnd(moveable: MoveableManagerInterface<ClippableProps, ClippableState>, e: any) {
return this.dragControlEnd(moveable, e);
},
dragControlStart(moveable: MoveableManagerInterface<ClippableProps, ClippableState>, e: any) {
const state = moveable.state;
const { defaultClipPath, customClipPath } = moveable.props;
const { target, width, height } = state;
const inputTarget = e.inputEvent ? e.inputEvent.target : null;
const className = (inputTarget && inputTarget.getAttribute("class")) || "";
const datas = e.datas;
const clipPath = getClipPath(target!, width, height, defaultClipPath || "inset", customClipPath);
if (!clipPath) {
return false;
}
const { clipText, type, poses } = clipPath;
const result = triggerEvent(moveable, "onClipStart", fillParams<OnClipStart>(moveable, e, {
clipType: type,
clipStyle: clipText,
poses: poses.map(pos => pos.pos),
}));
if (result === false) {
datas.isClipStart = false;
return false;
}
datas.isControl = className && className.indexOf("clip-control") > -1;
datas.isLine = className.indexOf("clip-line") > -1;
datas.isArea = className.indexOf("clip-area") > -1 || className.indexOf("clip-ellipse") > -1;
datas.clipIndex = inputTarget ? parseInt(inputTarget.getAttribute("data-clip-index"), 10) : -1;
datas.clipPath = clipPath;
datas.isClipStart = true;
state.clipPathState = clipText;
setDragStart(moveable, e);
return true;
},
dragControl(moveable: MoveableManagerInterface<ClippableProps & DraggableProps, ClippableState>, e: any) {
const { datas, originalDatas, isDragTarget } = e;
if (!datas.isClipStart) {
return false;
}
const { isControl, isLine, isArea, clipIndex, clipPath } = datas as {
clipPath: ReturnType<typeof getClipPath>,
[key: string]: any,
};
if (!clipPath) {
return false;
}
const props = getProps(moveable.props, "clippable");
const { keepRatio } = props;
let distX = 0;
let distY = 0;
const originalDraggable = originalDatas.draggable;
const originalDist = getDragDist(e);
if (isDragTarget && originalDraggable) {
[distX, distY] = originalDraggable.prevBeforeDist;
} else {
[distX, distY] = originalDist;
}
const firstDist = [distX, distY];
const state = moveable.state;
const { width, height } = state;
const isDragWithTarget = !isArea && !isControl && !isLine;
const {
type: clipType,
poses: clipPoses,
splitter,
} = clipPath;
const poses = clipPoses.map(pos => pos.pos);
if (isDragWithTarget) {
distX = -distX;
distY = -distY;
}
const isAll = !isControl || clipPoses[clipIndex].direction === "nesw";
const isRect = clipType === "inset" || clipType === "rect";
let dists = clipPoses.map(() => [0, 0]);
if (isControl && !isAll) {
const { horizontal, vertical } = clipPoses[clipIndex];
const dist = [
distX * abs(horizontal),
distY * abs(vertical),
];
dists = moveControlPos(clipPoses, clipIndex, dist, isRect, keepRatio);
} else if (isAll) {
dists = poses.map(() => [distX, distY]);
}
const nextPoses: number[][] = poses.map((pos, i) => plus(pos, dists[i]));
const guidePoses = [...nextPoses];
state.snapBoundInfos = null;
const isCircle = clipPath.type === "circle";
const isEllipse = clipPath.type === "ellipse";
if (isCircle || isEllipse) {
const guideRect = getRect(nextPoses);
const ry = abs(guideRect.bottom - guideRect.top);
const rx = abs(isEllipse ? guideRect.right - guideRect.left : ry);
const bottom = nextPoses[0][1] + ry;
const left = nextPoses[0][0] - rx;
const right = nextPoses[0][0] + rx;
// right
if (isCircle) {
guidePoses.push([right, guideRect.bottom]);
dists.push([1, 0]);
}
// bottom
guidePoses.push([guideRect.left, bottom]);
dists.push([0, 1]);
// left
guidePoses.push([left, guideRect.bottom]);
dists.push([1, 0]);
}
const guidelines = getDefaultGuidelines(
(props.clipHorizontalGuidelines || []).map(v => convertUnitSize(`${v}`, height)),
(props.clipVerticalGuidelines || []).map(v => convertUnitSize(`${v}`, width)),
width!, height!,
);
let guideXPoses: number[] = [];
let guideYPoses: number[] = [];
if (isCircle || isEllipse) {
guideXPoses = [guidePoses[4][0], guidePoses[2][0]];
guideYPoses = [guidePoses[1][1], guidePoses[3][1]];
} else if (isRect) {
const rectPoses = [guidePoses[0], guidePoses[2], guidePoses[4], guidePoses[6]];
const rectDists = [dists[0], dists[2], dists[4], dists[6]];
guideXPoses = rectPoses.filter((_, i) => rectDists[i][0]).map(pos => pos[0]);
guideYPoses = rectPoses.filter((_, i) => rectDists[i][1]).map(pos => pos[1]);
} else {
guideXPoses = guidePoses.filter((_, i) => dists[i][0]).map(pos => pos[0]);
guideYPoses = guidePoses.filter((_, i) => dists[i][1]).map(pos => pos[1]);
}
const boundDelta = [0, 0];
const {
horizontal: horizontalSnapInfo,
vertical: verticalSnapInfo,
} = checkSnapBounds(
guidelines,
props.clipTargetBounds && { left: 0, top: 0, right: width, bottom: height },
guideXPoses,
guideYPoses,
5,
5,
);
let snapOffsetY = horizontalSnapInfo.offset;
let snapOffsetX = verticalSnapInfo.offset;
if (horizontalSnapInfo.isBound) {
boundDelta[1] += snapOffsetY;
}
if (verticalSnapInfo.isBound) {
boundDelta[0] += snapOffsetX;
}
if ((isEllipse || isCircle) && dists[0][0] === 0 && dists[0][1] === 0) {
const guideRect = getRect(nextPoses);
let cy = guideRect.bottom - guideRect.top;
let cx = isEllipse ? guideRect.right - guideRect.left : cy;
const distSnapX = verticalSnapInfo.isBound
? abs(snapOffsetX)
: (verticalSnapInfo.snapIndex === 0 ? -snapOffsetX : snapOffsetX);
const distSnapY = horizontalSnapInfo.isBound
? abs(snapOffsetY)
: (horizontalSnapInfo.snapIndex === 0 ? -snapOffsetY : snapOffsetY);
cx -= distSnapX;
cy -= distSnapY;
if (isCircle) {
cy = checkSnapBoundPriority(verticalSnapInfo, horizontalSnapInfo) > 0 ? cy : cx;
cx = cy;
}
const center = guidePoses[0];
guidePoses[1][1] = center[1] - cy;
guidePoses[2][0] = center[0] + cx;
guidePoses[3][1] = center[1] + cy;
guidePoses[4][0] = center[0] - cx;
} else if (isRect && keepRatio && isControl) {
const [width, height] = getControlSize(clipPoses);
const ratio = width && height ? width / height : 0;
const clipPose = clipPoses[clipIndex];
const direction = clipPose.direction! || "";
let top = guidePoses[1][1];
let bottom = guidePoses[5][1];
let left = guidePoses[7][0];
let right = guidePoses[3][0];
if (abs(snapOffsetY) <= abs(snapOffsetX)) {
snapOffsetY = sign(snapOffsetY) * abs(snapOffsetX) / ratio;
} else {
snapOffsetX = sign(snapOffsetX) * abs(snapOffsetY) * ratio;
}
if (direction!.indexOf("w") > -1) {
left -= snapOffsetX;
} else if (direction!.indexOf("e") > -1) {
right -= snapOffsetX;
} else {
left += snapOffsetX / 2;
right -= snapOffsetX / 2;
}
if (direction!.indexOf("n") > -1) {
top -= snapOffsetY;
} else if (direction!.indexOf("s") > -1) {
bottom -= snapOffsetY;
} else {
top += snapOffsetY / 2;
bottom -= snapOffsetY / 2;
}
const nextControlPoses = getRectPoses(top, right, bottom, left);
guidePoses.forEach((pos, i) => {
[pos[0], pos[1]] = nextControlPoses[i].pos;
});
} else {
guidePoses.forEach((pos, j) => {
const dist = dists[j];
if (dist[0]) {
pos[0] -= snapOffsetX;
}
if (dist[1]) {
pos[1] -= snapOffsetY;
}
});
}
const nextClipStyles = getClipStyles(moveable, clipPath, nextPoses)!;
const clipStyle = `${clipType}(${nextClipStyles.join(splitter)})`;
state.clipPathState = clipStyle;
if (isCircle || isEllipse) {
guideXPoses = [guidePoses[4][0], guidePoses[2][0]];
guideYPoses = [guidePoses[1][1], guidePoses[3][1]];
} else if (isRect) {
const rectPoses = [guidePoses[0], guidePoses[2], guidePoses[4], guidePoses[6]];
guideXPoses = rectPoses.map(pos => pos[0]);
guideYPoses = rectPoses.map(pos => pos[1]);
} else {
guideXPoses = guidePoses.map(pos => pos[0]);
guideYPoses = guidePoses.map(pos => pos[1]);
}
state.snapBoundInfos = checkSnapBounds(
guidelines,
props.clipTargetBounds && { left: 0, top: 0, right: width, bottom: height },
guideXPoses,
guideYPoses,
1,
1,
);
if (originalDraggable) {
const {
is3d,
allMatrix,
} = state;
const n = is3d ? 4 : 3;
let dragDist = boundDelta;
if (isDragTarget) {
dragDist = [
firstDist[0] + boundDelta[0] - originalDist[0],
firstDist[1] + boundDelta[1] - originalDist[1],
];
}
originalDraggable.deltaOffset = multiply(allMatrix, [dragDist[0], dragDist[1], 0, 0], n);
}
triggerEvent(moveable, "onClip", fillParams<OnClip>(moveable, e, {
clipEventType: "changed",
clipType,
poses: nextPoses,
clipStyle,
clipStyles: nextClipStyles,
distX,
distY,
...fillCSSObject({
[clipType === "rect" ? "clip" : "clipPath"]: clipStyle,
}, e),
}));
return true;
},
dragControlEnd(moveable: MoveableManagerInterface<ClippableProps, ClippableState>, e: any) {
this.unset(moveable);
const { isDrag, datas, isDouble } = e;
const { isLine, isClipStart, isControl } = datas;
if (!isClipStart) {
return false;
}
triggerEvent(moveable, "onClipEnd", fillEndParams<OnClipEnd>(moveable, e, {}));
if (isDouble) {
if (isControl) {
removeClipPath(moveable, e);
} else if (isLine) {
// add
addClipPath(moveable, e);
}
}
return isDouble || isDrag;
},
unset(moveable: MoveableManagerInterface<ClippableProps, ClippableState>) {
moveable.state.clipPathState = "";
moveable.state.snapBoundInfos = null;
},
};
/**
* Whether to clip the target. (default: false)
* @name Moveable.Clippable#clippable
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body, {
* clippable: true,
* defaultClipPath: "inset",
* customClipPath: "",
* clipRelative: false,
* clipArea: false,
* dragWithClip: true,
* });
* moveable.on("clipStart", e => {
* console.log(e);
* }).on("clip", e => {
* if (e.clipType === "rect") {
* e.target.style.clip = e.clipStyle;
* } else {
* e.target.style.clipPath = e.clipStyle;
* }
* }).on("clipEnd", e => {
* console.log(e);
* });
*/
/**
* If clippath is not set, the default value can be set. (defaultClipPath < style < customClipPath < dragging clipPath)
* @name Moveable.Clippable#defaultClipPath
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body, {
* clippable: true,
* defaultClipPath: "inset",
* customClipPath: "",
* clipRelative: false,
* clipArea: false,
* dragWithClip: true,
* });
* moveable.on("clipStart", e => {
* console.log(e);
* }).on("clip", e => {
* if (e.clipType === "rect") {
* e.target.style.clip = e.clipStyle;
* } else {
* e.target.style.clipPath = e.clipStyle;
* }
* }).on("clipEnd", e => {
* console.log(e);
* });
*/
/**
* % Can be used instead of the absolute px (`rect` not possible) (default: false)
* @name Moveable.Clippable#clipRelative
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body, {
* clippable: true,
* defaultClipPath: "inset",
* customClipPath: "",
* clipRelative: false,
* clipArea: false,
* dragWithClip: true,
* });
* moveable.on("clipStart", e => {
* console.log(e);
* }).on("clip", e => {
* if (e.clipType === "rect") {
* e.target.style.clip = e.clipStyle;
* } else {
* e.target.style.clipPath = e.clipStyle;
* }
* }).on("clipEnd", e => {
* console.log(e);
* });
*/
/**
* You can force the custom clipPath. (defaultClipPath < style < customClipPath < dragging clipPath)
* @name Moveable.Clippable#customClipPath
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body, {
* clippable: true,
* defaultClipPath: "inset",
* customClipPath: "",
* clipRelative: false,
* clipArea: false,
* dragWithClip: true,
* });
* moveable.on("clipStart", e => {
* console.log(e);
* }).on("clip", e => {
* if (e.clipType === "rect") {
* e.target.style.clip = e.clipStyle;
* } else {
* e.target.style.clipPath = e.clipStyle;
* }
* }).on("clipEnd", e => {
* console.log(e);
* });
*/
/**
* When dragging the target, the clip also moves. (default: true)
* @name Moveable.Clippable#dragWithClip
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body, {
* clippable: true,
* defaultClipPath: "inset",
* customClipPath: "",
* clipRelative: false,
* clipArea: false,
* dragWithClip: true,
* });
* moveable.on("clipStart", e => {
* console.log(e);
* }).on("clip", e => {
* if (e.clipType === "rect") {
* e.target.style.clip = e.clipStyle;
* } else {
* e.target.style.clipPath = e.clipStyle;
* }
* }).on("clipEnd", e => {
* console.log(e);
* });
*/
/**
* You can drag the clip by setting clipArea.
* @name Moveable.Clippable#clipArea
* @default false
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body, {
* clippable: true,
* defaultClipPath: "inset",
* customClipPath: "",
* clipRelative: false,
* clipArea: false,
* dragWithClip: true,
* });
* moveable.on("clipStart", e => {
* console.log(e);
* }).on("clip", e => {
* if (e.clipType === "rect") {
* e.target.style.clip = e.clipStyle;
* } else {
* e.target.style.clipPath = e.clipStyle;
* }
* }).on("clipEnd", e => {
* console.log(e);
* });
*/
/**
* Whether the clip is bound to the target.
* @name Moveable.Clippable#clipTargetBounds
* @default false
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body, {
* clippable: true,
* defaultClipPath: "inset",
* customClipPath: "",
* clipRelative: false,
* clipArea: false,
* dragWithClip: true,
* clipTargetBounds: true,
* });
* moveable.on("clipStart", e => {
* console.log(e);
* }).on("clip", e => {
* if (e.clipType === "rect") {
* e.target.style.clip = e.clipStyle;
* } else {
* e.target.style.clipPath = e.clipStyle;
* }
* }).on("clipEnd", e => {
* console.log(e);
* });
*/
/**
* Add clip guidelines in the vertical direction.
* @name Moveable.Clippable#clipVerticalGuidelines
* @default 0
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body, {
* clippable: true,
* defaultClipPath: "inset",
* customClipPath: "",
* clipRelative: false,
* clipArea: false,
* dragWithClip: true,
* clipVerticalGuidelines: [0, 100, 200],
* clipHorizontalGuidelines: [0, 100, 200],
* clipSnapThreshold: 5,
* });
*/
/**
* Add clip guidelines in the horizontal direction.
* @name Moveable.Clippable#clipHorizontalGuidelines
* @default []
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body, {
* clippable: true,
* defaultClipPath: "inset",
* customClipPath: "",
* clipRelative: false,
* clipArea: false,
* dragWithClip: true,
* clipVerticalGuidelines: [0, 100, 200],
* clipHorizontalGuidelines: [0, 100, 200],
* clipSnapThreshold: 5,
* });
*/
/**
* istance value that can snap to clip guidelines.
* @name Moveable.Clippable#clipSnapThreshold
* @default 5
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body, {
* clippable: true,
* defaultClipPath: "inset",
* customClipPath: "",
* clipRelative: false,
* clipArea: false,
* dragWithClip: true,
* clipVerticalGuidelines: [0, 100, 200],
* clipHorizontalGuidelines: [0, 100, 200],
* clipSnapThreshold: 5,
* });
*/
/**
* When drag start the clip area or controls, the `clipStart` event is called.
* @memberof Moveable.Clippable
* @event clipStart
* @param {Moveable.Clippable.OnClipStart} - Parameters for the `clipStart` event
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body, {
* clippable: true,
* defaultClipPath: "inset",
* customClipPath: "",
* clipRelative: false,
* clipArea: false,
* dragWithClip: true,
* });
* moveable.on("clipStart", e => {
* console.log(e);
* }).on("clip", e => {
* if (e.clipType === "rect") {
* e.target.style.clip = e.clipStyle;
* } else {
* e.target.style.clipPath = e.clipStyle;
* }
* }).on("clipEnd", e => {
* console.log(e);
* });
*/
/**
* When drag the clip area or controls, the `clip` event is called.
* @memberof Moveable.Clippable
* @event clip
* @param {Moveable.Clippable.OnClip} - Parameters for the `clip` event
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body, {
* clippable: true,
* defaultClipPath: "inset",
* customClipPath: "",
* clipRelative: false,
* clipArea: false,
* dragWithClip: true,
* });
* moveable.on("clipStart", e => {
* console.log(e);
* }).on("clip", e => {
* if (e.clipType === "rect") {
* e.target.style.clip = e.clipStyle;
* } else {
* e.target.style.clipPath = e.clipStyle;
* }
* }).on("clipEnd", e => {
* console.log(e);
* });
*/
/**
* When drag end the clip area or controls, the `clipEnd` event is called.
* @memberof Moveable.Clippable
* @event clipEnd
* @param {Moveable.Clippable.OnClipEnd} - Parameters for the `clipEnd` event
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body, {
* clippable: true,
* defaultClipPath: "inset",
* customClipPath: "",
* clipRelative: false,
* clipArea: false,
* dragWithClip: true,
* });
* moveable.on("clipStart", e => {
* console.log(e);
* }).on("clip", e => {
* if (e.clipType === "rect") {
* e.target.style.clip = e.clipStyle;
* } else {
* e.target.style.clipPath = e.clipStyle;
* }
* }).on("clipEnd", e => {
* console.log(e);
* });
*/