import {
Renderer,
SnappableProps,
SnappableState,
SnapGuideline,
ScalableProps,
SnapPosInfo,
RotatableProps,
RectInfo,
MoveableManagerInterface,
SnappableRenderType,
BoundType,
MoveableGroupInterface,
SnapDirectionInfo,
} from "../types";
import {
prefix,
calculatePoses,
getRect,
getAbsolutePosesByState,
getAbsolutePoses,
getClientRect,
getRefTarget,
getDragDistByState,
triggerEvent,
getDirectionCondition,
abs,
watchValue,
} from "../utils";
import {
find,
findIndex, hasClass, throttle,
} from "@daybrush/utils";
import {
getDragDist,
scaleMatrix,
getPosByDirection,
} from "../gesto/GestoUtils";
import { minus, rotate, plus } from "@scena/matrix";
import { dragControlCondition as rotatableDragControlCondtion } from "./Rotatable";
import { FLOAT_POINT_NUM } from "../consts";
import {
getInnerBoundInfo,
getCheckInnerBoundLineInfos,
checkRotateInnerBounds,
checkInnerBoundPoses,
} from "./snappable/innerBounds";
import {
checkBoundPoses,
checkRotateBounds,
getBounds,
} from "./snappable/bounds";
import {
checkSnaps,
getSnapInfosByDirection,
getNearOffsetInfo,
getCheckSnapDirections,
} from "./snappable/snap";
import {
renderSnapPoses,
renderGuidelines,
renderDashedGuidelines,
renderGapGuidelines,
} from "./snappable/render";
import {
getInitialBounds,
hasGuidelines,
} from "./snappable/utils";
import {
checkMaxBounds,
checkMoveableSnapBounds,
getSnapBoundInfo,
} from "./snappable/snapBounds";
import { getTotalGuidelines } from "./snappable/getTotalGuidelines";
import {
NAME_snapRotationThreshold, NAME_snapRotationDegrees,
NAME_snapHorizontalThreshold, NAME_snapVerticalThreshold,
} from "./snappable/names";
export interface SnapPoses {
vertical: number[];
horizontal: number[];
}
export function checkSnapInfo(
moveable: MoveableManagerInterface<SnappableProps, SnappableState>
) {
const state = moveable.state;
const container = state.container;
const snapContainer = moveable.props.snapContainer || container!;
if (state.snapContainer === snapContainer && state.guidelines && state.guidelines.length) {
return false;
}
const containerClientRect = state.containerClientRect;
const snapOffset = {
left: 0,
top: 0,
bottom: 0,
right: 0,
};
if (container !== snapContainer) {
const snapContainerTarget = getRefTarget(snapContainer, true);
if (snapContainerTarget) {
const snapContainerRect = getClientRect(snapContainerTarget);
const offset1 = getDragDistByState(state, [
snapContainerRect.left - containerClientRect.left,
snapContainerRect.top - containerClientRect.top,
]);
const offset2 = getDragDistByState(state, [
snapContainerRect.right - containerClientRect.right,
snapContainerRect.bottom - containerClientRect.bottom,
]);
snapOffset.left = throttle(offset1[0], 0.00001);
snapOffset.top = throttle(offset1[1], 0.00001);
snapOffset.right = throttle(offset2[0], 0.00001);
snapOffset.bottom = throttle(offset2[1], 0.00001);
}
}
state.snapContainer = snapContainer;
state.snapOffset = snapOffset;
state.guidelines = getTotalGuidelines(moveable);
state.enableSnap = true;
return true;
}
function getNextFixedPoses(
matrix: number[],
width: number,
height: number,
fixedDirection: number[],
fixedPos: number[],
is3d: boolean
) {
const nextPoses = calculatePoses(matrix, width, height, is3d ? 4 : 3);
const nextFixedPos = getPosByDirection(nextPoses, fixedDirection);
return getAbsolutePoses(nextPoses, minus(fixedPos, nextFixedPos));
}
export function normalized(value: number) {
return value ? value / abs(value) : 0;
}
export function getSizeOffsetInfo(
moveable: MoveableManagerInterface<SnappableProps, SnappableState>,
poses: number[][],
direction: number[],
keepRatio: boolean,
isRequest: boolean,
datas: any
) {
const { fixedDirection } = datas;
const directions = getCheckSnapDirections(direction, fixedDirection, keepRatio);
const innerBoundLineInfos = getCheckInnerBoundLineInfos(moveable, poses, direction, keepRatio);
const offsets = [
...getSnapBoundInfo(
moveable,
poses,
directions,
keepRatio,
isRequest,
datas
),
...getInnerBoundInfo(
moveable,
innerBoundLineInfos,
datas
),
];
const widthOffsetInfo = getNearOffsetInfo(offsets, 0);
const heightOffsetInfo = getNearOffsetInfo(offsets, 1);
return {
width: {
isBound: widthOffsetInfo.isBound,
offset: widthOffsetInfo.offset[0],
},
height: {
isBound: heightOffsetInfo.isBound,
offset: heightOffsetInfo.offset[1],
},
};
}
export function recheckSizeByTwoDirection(
moveable: MoveableManagerInterface<SnappableProps, SnappableState>,
poses: number[][],
width: number,
height: number,
maxWidth: number,
maxHeight: number,
direction: number[],
isRequest: boolean,
datas: any
) {
const snapPos = getPosByDirection(poses, direction);
const {
horizontal: { offset: horizontalOffset },
vertical: { offset: verticalOffset },
} = checkMoveableSnapBounds(moveable, isRequest, {
vertical: [snapPos[0]],
horizontal: [snapPos[1]],
});
if (throttle(verticalOffset, FLOAT_POINT_NUM) || throttle(horizontalOffset, FLOAT_POINT_NUM)) {
const [nextWidthOffset, nextHeightOffset] = getDragDist({
datas,
distX: -verticalOffset,
distY: -horizontalOffset,
});
const nextWidth = Math.min(
maxWidth || Infinity,
width + direction[0] * nextWidthOffset
);
const nextHeight = Math.min(
maxHeight || Infinity,
height + direction[1] * nextHeightOffset
);
return [nextWidth - width, nextHeight - height];
}
return [0, 0];
}
export function checkSizeDist(
moveable: MoveableManagerInterface<any, any>,
getNextPoses: (widthOffset: number, heightOffset: number) => number[][],
width: number,
height: number,
direction: number[],
fixedPosition: number[],
isRequest: boolean,
datas: any
) {
const poses = getAbsolutePosesByState(moveable.state);
const keepRatio = moveable.props.keepRatio;
let widthOffset = 0;
let heightOffset = 0;
for (let i = 0; i < 2; ++i) {
const nextPoses = getNextPoses(widthOffset, heightOffset);
const {
width: widthOffsetInfo,
height: heightOffsetInfo,
} = getSizeOffsetInfo(
moveable,
nextPoses,
direction,
keepRatio,
isRequest,
datas
);
const isWidthBound = widthOffsetInfo.isBound;
const isHeightBound = heightOffsetInfo.isBound;
let nextWidthOffset = widthOffsetInfo.offset;
let nextHeightOffset = heightOffsetInfo.offset;
if (i === 1) {
if (!isWidthBound) {
nextWidthOffset = 0;
}
if (!isHeightBound) {
nextHeightOffset = 0;
}
}
if (i === 0 && isRequest && !isWidthBound && !isHeightBound) {
return [0, 0];
}
if (keepRatio) {
const widthDist =
abs(nextWidthOffset) * (width ? 1 / width : 1);
const heightDist =
abs(nextHeightOffset) * (height ? 1 / height : 1);
const isGetWidthOffset =
isWidthBound && isHeightBound
? widthDist < heightDist
: isHeightBound ||
(!isWidthBound && widthDist < heightDist);
if (isGetWidthOffset) {
// width : height = ? : heightOffset
nextWidthOffset = (width * nextHeightOffset) / height;
} else {
// width : height = widthOffset : ?
nextHeightOffset = (height * nextWidthOffset) / width;
}
}
widthOffset += nextWidthOffset;
heightOffset += nextHeightOffset;
}
if (!keepRatio && direction[0] && direction[1]) {
const { maxWidth, maxHeight } = checkMaxBounds(
moveable,
poses,
direction,
fixedPosition,
datas
);
const [nextWidthOffset, nextHeightOffset] = recheckSizeByTwoDirection(
moveable,
getNextPoses(widthOffset, heightOffset).map(pos => pos.map(p => throttle(p, FLOAT_POINT_NUM))),
width + widthOffset,
height + heightOffset,
maxWidth,
maxHeight,
direction,
isRequest,
datas
);
widthOffset += nextWidthOffset;
heightOffset += nextHeightOffset;
}
return [widthOffset, heightOffset];
}
export function absDegree(deg: number) {
if (deg < 0) {
deg = deg % 360 + 360;
}
deg %= 360;
return deg;
}
export function bumpDegree(baseDeg: number, snapDeg: number) {
// baseDeg -80
// snapDeg 270
// return -90
snapDeg = absDegree(snapDeg);
const count = Math.floor(baseDeg / 360);
const deg1 = count * 360 + 360 - snapDeg;
const deg2 = count * 360 + snapDeg;
return abs(baseDeg - deg1) < abs(baseDeg - deg2) ? deg1 : deg2;
}
export function getMinDegreeDistance(deg1: number, deg2: number) {
deg1 = absDegree(deg1);
deg2 = absDegree(deg2);
const deg3 = absDegree(deg1 - deg2);
return Math.min(deg3, 360 - deg3);
}
export function checkSnapRotate(
moveable: MoveableManagerInterface<SnappableProps & RotatableProps, any>,
rect: RectInfo,
dist: number,
rotation: number,
) {
const props = moveable.props;
const snapRotationThreshold = props[NAME_snapRotationThreshold] ?? 5;
const snapRotationDegrees = props[NAME_snapRotationDegrees];
if (hasGuidelines(moveable, "rotatable")) {
const { pos1, pos2, pos3, pos4, origin: origin2 } = rect;
const rad = (dist * Math.PI) / 180;
const prevPoses = [pos1, pos2, pos3, pos4].map((pos) => minus(pos, origin2));
const nextPoses = prevPoses.map((pos) => rotate(pos, rad));
// console.log(moveable.state.left, moveable.state.top, moveable.state.origin);
// console.log(pos1, pos2, pos3, pos4, origin, rad, prevPoses, nextPoses);
const result = [
...checkRotateBounds(moveable, prevPoses, nextPoses, origin2, dist),
...checkRotateInnerBounds(
moveable,
prevPoses,
nextPoses,
origin2,
dist
),
];
result.sort((a, b) => abs(a - dist) - abs(b - dist));
const isSnap = result.length > 0;
if (isSnap) {
return {
isSnap,
dist: isSnap ? result[0] : dist,
};
}
}
if (snapRotationDegrees?.length && snapRotationThreshold) {
const sorted = snapRotationDegrees.slice().sort((a, b) => {
return getMinDegreeDistance(a, rotation) - getMinDegreeDistance(b, rotation);
});
const firstDegree = sorted[0];
if (getMinDegreeDistance(firstDegree, rotation) <= snapRotationThreshold) {
return {
isSnap: true,
dist: dist + bumpDegree(rotation, firstDegree) - rotation,
};
}
}
return {
isSnap: false,
dist,
};
}
export function checkSnapResize(
moveable: MoveableManagerInterface<{}, {}>,
width: number,
height: number,
direction: number[],
fixedPosition: number[],
isRequest: boolean,
datas: any
) {
if (!hasGuidelines(moveable, "resizable")) {
return [0, 0];
}
const { fixedDirection, nextAllMatrix } = datas;
const { allMatrix, is3d } = moveable.state;
return checkSizeDist(
moveable,
(widthOffset: number, heightOffset: number) => {
return getNextFixedPoses(
nextAllMatrix || allMatrix,
width + widthOffset,
height + heightOffset,
fixedDirection,
fixedPosition,
is3d
);
},
width,
height,
direction,
fixedPosition,
isRequest,
datas
);
}
export function checkSnapScale(
moveable: MoveableManagerInterface<ScalableProps, any>,
scale: number[],
direction: number[],
isRequest: boolean,
datas: any
) {
if (!hasGuidelines(moveable, "scalable")) {
return [0, 0];
}
const { startOffsetWidth, startOffsetHeight, fixedPosition, fixedDirection, is3d } = datas;
const sizeDist = checkSizeDist(
moveable,
(widthOffset: number, heightOffset: number) => {
return getNextFixedPoses(
scaleMatrix(
datas,
plus(scale, [widthOffset / startOffsetWidth, heightOffset / startOffsetHeight]),
),
startOffsetWidth,
startOffsetHeight,
fixedDirection,
fixedPosition,
is3d
);
},
startOffsetWidth,
startOffsetHeight,
direction,
fixedPosition,
isRequest,
datas
);
return [sizeDist[0] / startOffsetWidth, sizeDist[1] / startOffsetHeight];
}
export function startCheckSnapDrag(
moveable: MoveableManagerInterface<any, any>,
datas: any
) {
datas.absolutePoses = getAbsolutePosesByState(moveable.state);
}
function getSnapGuidelines(posInfos: SnapPosInfo[]) {
const guidelines: Array<{ guideline: SnapGuideline, posInfo: SnapPosInfo }> = [];
posInfos.forEach((posInfo) => {
posInfo.guidelineInfos.forEach(({ guideline }) => {
if (find(guidelines, info => info.guideline === guideline)) {
return;
}
guideline.direction = "";
guidelines.push({ guideline, posInfo });
});
});
return guidelines.map(({ guideline, posInfo }) => {
return {
...guideline,
direction: posInfo.direction,
};
});
}
function addBoundGuidelines(
moveable: MoveableManagerInterface<SnappableProps, SnappableState>,
verticalPoses: number[],
horizontalPoses: number[],
verticalSnapPoses: SnappableRenderType[],
horizontalSnapPoses: SnappableRenderType[],
externalBounds?: BoundType | false | null
) {
const {
vertical: verticalBoundInfos,
horizontal: horizontalBoundInfos,
} = checkBoundPoses(
getBounds(moveable, externalBounds),
verticalPoses,
horizontalPoses
);
const boundMap = getInitialBounds();
verticalBoundInfos.forEach((info) => {
if (info.isBound) {
if (info.direction === "start") {
boundMap.left = true;
}
if (info.direction === "end") {
boundMap.right = true;
}
verticalSnapPoses.push({
type: "bounds",
pos: info.pos,
});
}
});
horizontalBoundInfos.forEach((info) => {
if (info.isBound) {
if (info.direction === "start") {
boundMap.top = true;
}
if (info.direction === "end") {
boundMap.bottom = true;
}
horizontalSnapPoses.push({
type: "bounds",
pos: info.pos,
});
}
});
const {
boundMap: innerBoundMap,
vertical: verticalInnerBoundPoses,
horizontal: horizontalInnerBoundPoses,
} = checkInnerBoundPoses(moveable);
verticalInnerBoundPoses.forEach((innerPos) => {
if (
findIndex(
verticalSnapPoses,
({ type, pos }) => type === "bounds" && pos === innerPos
) >= 0
) {
return;
}
verticalSnapPoses.push({
type: "bounds",
pos: innerPos,
});
});
horizontalInnerBoundPoses.forEach((innerPos) => {
if (
findIndex(
horizontalSnapPoses,
({ type, pos }) => type === "bounds" && pos === innerPos
) >= 0
) {
return;
}
horizontalSnapPoses.push({
type: "bounds",
pos: innerPos,
});
});
return {
boundMap,
innerBoundMap,
};
}
const directionCondition = getDirectionCondition("", ["resizable", "scalable"]);
/**
* @namespace Moveable.Snappable
* @description Whether or not target can be snapped to the guideline. (default: false)
* @sort 2
*/
export default {
name: "snappable",
dragRelation: "strong",
props: [
"snappable",
"snapContainer",
"snapDirections",
"elementSnapDirections",
"snapGap",
"snapGridWidth",
"snapGridHeight",
"isDisplaySnapDigit",
"isDisplayInnerSnapDigit",
"isDisplayGridGuidelines",
"snapDigit",
"snapThreshold",
"snapRenderThreshold",
"snapGridAll",
NAME_snapRotationThreshold,
NAME_snapRotationDegrees,
NAME_snapHorizontalThreshold,
NAME_snapVerticalThreshold,
"horizontalGuidelines",
"verticalGuidelines",
"elementGuidelines",
"bounds",
"innerBounds",
"snapDistFormat",
"maxSnapElementGuidelineDistance",
"maxSnapElementGapDistance",
] as const,
events: ["snap", "bound"] as const,
css: [
`:host {
--bounds-color: #d66;
}
.guideline {
pointer-events: none;
z-index: 2;
}
.guideline.bounds {
background: #d66;
background: var(--bounds-color);
}
.guideline-group {
position: absolute;
top: 0;
left: 0;
}
.guideline-group .size-value {
position: absolute;
color: #f55;
font-size: 12px;
font-size: calc(12px * var(--zoom));
font-weight: bold;
}
.guideline-group.horizontal .size-value {
transform-origin: 50% 100%;
transform: translateX(-50%);
left: 50%;
bottom: 5px;
bottom: calc(2px + 3px * var(--zoom));
}
.guideline-group.vertical .size-value {
transform-origin: 0% 50%;
top: 50%;
transform: translateY(-50%);
left: 5px;
left: calc(2px + 3px * var(--zoom));
}
.guideline.gap {
background: #f55;
}
.size-value.gap {
color: #f55;
}
`,
],
render(
moveable: MoveableManagerInterface<SnappableProps, SnappableState>,
React: Renderer
): any[] {
const state = moveable.state;
const {
top: targetTop,
left: targetLeft,
pos1,
pos2,
pos3,
pos4,
snapRenderInfo,
} = state;
const {
snapRenderThreshold = 1,
} = moveable.props;
if (!snapRenderInfo || !snapRenderInfo.render || !hasGuidelines(moveable, "")) {
// reset store
watchValue(
moveable,
"boundMap",
getInitialBounds(),
v => JSON.stringify(v),
);
watchValue(
moveable,
"innerBoundMap",
getInitialBounds(),
v => JSON.stringify(v),
);
return [];
}
state.guidelines = getTotalGuidelines(moveable);
const minLeft = Math.min(pos1[0], pos2[0], pos3[0], pos4[0]);
const minTop = Math.min(pos1[1], pos2[1], pos3[1], pos4[1]);
const externalPoses = snapRenderInfo.externalPoses || [];
const poses = getAbsolutePosesByState(moveable.state);
const verticalSnapPoses: SnappableRenderType[] = [];
const horizontalSnapPoses: SnappableRenderType[] = [];
const verticalGuidelines: SnapGuideline[] = [];
const horizontalGuidelines: SnapGuideline[] = [];
const snapInfos: Array<{
vertical: SnapDirectionInfo;
horizontal: SnapDirectionInfo;
}> = [];
const { width, height, top, left, bottom, right } = getRect(poses);
const targetRect = { left, right, top, bottom, center: (left + right) / 2, middle: (top + bottom) / 2 };
const hasExternalPoses = externalPoses.length > 0;
const externalRect = hasExternalPoses
? getRect(externalPoses)
: ({} as ReturnType<typeof getRect>);
if (!snapRenderInfo.request) {
if (snapRenderInfo.direction) {
snapInfos.push(
getSnapInfosByDirection(
moveable,
poses,
snapRenderInfo.direction,
snapRenderThreshold,
snapRenderThreshold,
)
);
}
if (snapRenderInfo.snap) {
const rect = getRect(poses);
if (snapRenderInfo.center) {
(rect as any).middle = (rect.top + rect.bottom) / 2;
(rect as any).center = (rect.left + rect.right) / 2;
}
snapInfos.push(checkSnaps(moveable, rect, snapRenderThreshold, snapRenderThreshold));
}
if (hasExternalPoses) {
if (snapRenderInfo.center) {
(externalRect as any).middle =
(externalRect.top + externalRect.bottom) / 2;
(externalRect as any).center =
(externalRect.left + externalRect.right) / 2;
}
snapInfos.push(checkSnaps(moveable, externalRect, snapRenderThreshold, snapRenderThreshold));
}
snapInfos.forEach((snapInfo) => {
const {
vertical: { posInfos: verticalPosInfos },
horizontal: { posInfos: horizontalPosInfos },
} = snapInfo;
verticalSnapPoses.push(
...verticalPosInfos.filter(({ guidelineInfos }) => {
return guidelineInfos.some(({ guideline }) => !guideline.hide);
}).map(
(posInfo) => ({
type: "snap",
pos: posInfo.pos,
} as const)
)
);
horizontalSnapPoses.push(
...horizontalPosInfos.filter(({ guidelineInfos }) => {
return guidelineInfos.some(({ guideline }) => !guideline.hide);
}).map(
(posInfo) => ({
type: "snap",
pos: posInfo.pos,
} as const)
)
);
verticalGuidelines.push(...getSnapGuidelines(verticalPosInfos));
horizontalGuidelines.push(...getSnapGuidelines(horizontalPosInfos));
});
}
const {
boundMap,
innerBoundMap,
} = addBoundGuidelines(
moveable,
[left, right],
[top, bottom],
verticalSnapPoses,
horizontalSnapPoses
);
if (hasExternalPoses) {
addBoundGuidelines(
moveable,
[externalRect.left, externalRect.right],
[externalRect.top, externalRect.bottom],
verticalSnapPoses,
horizontalSnapPoses,
snapRenderInfo.externalBounds
);
}
const allGuidelines = [...verticalGuidelines, ...horizontalGuidelines];
const elementGuidelines = allGuidelines.filter(guideline => guideline.element && !guideline.gapRects);
const gapGuidelines = allGuidelines.filter(guideline => guideline.gapRects).sort((a, b) => {
return a.gap! - b.gap!;
});
triggerEvent(
moveable,
"onSnap",
{
guidelines: allGuidelines.filter(({ element }) => !element),
elements: elementGuidelines,
gaps: gapGuidelines,
},
true
);
const nextBoundMap = watchValue(
moveable,
"boundMap",
boundMap,
v => JSON.stringify(v),
getInitialBounds(),
);
const nextInnerBoundMap = watchValue(
moveable,
"innerBoundMap",
innerBoundMap,
v => JSON.stringify(v),
getInitialBounds(),
);
if (boundMap === nextBoundMap || innerBoundMap === nextInnerBoundMap) {
triggerEvent(
moveable,
"onBound",
{
bounds: boundMap,
innerBounds: innerBoundMap,
},
true
);
}
// verticalSnapPoses.
return [
...renderDashedGuidelines(
moveable,
elementGuidelines,
[minLeft, minTop],
targetRect,
React,
),
...renderGapGuidelines(
moveable,
gapGuidelines,
[minLeft, minTop],
targetRect,
React,
),
...renderGuidelines(
moveable,
"horizontal",
horizontalGuidelines,
[targetLeft, targetTop],
targetRect,
React
),
...renderGuidelines(
moveable,
"vertical",
verticalGuidelines,
[targetLeft, targetTop],
targetRect,
React
),
...renderSnapPoses(
moveable,
"horizontal",
horizontalSnapPoses,
minLeft,
targetTop,
width,
0,
React
),
...renderSnapPoses(
moveable,
"vertical",
verticalSnapPoses,
minTop,
targetLeft,
height,
1,
React
),
];
},
dragStart(
moveable: MoveableManagerInterface<SnappableProps, SnappableState>,
e: any
) {
moveable.state.snapRenderInfo = {
request: e.isRequest,
snap: true,
center: true,
};
checkSnapInfo(moveable);
},
drag(
moveable: MoveableManagerInterface<SnappableProps, SnappableState>
) {
const state = moveable.state;
if (!checkSnapInfo(moveable)) {
state.guidelines = getTotalGuidelines(moveable);
}
if (state.snapRenderInfo) {
state.snapRenderInfo.render = true;
}
},
pinchStart(
moveable: MoveableManagerInterface<SnappableProps, SnappableState>
) {
this.unset(moveable);
},
dragEnd(
moveable: MoveableManagerInterface<SnappableProps, SnappableState>
) {
this.unset(moveable);
},
dragControlCondition(moveable: MoveableManagerInterface, e: any) {
if (directionCondition(moveable, e) || rotatableDragControlCondtion(moveable, e)) {
return true;
}
if (!e.isRequest && e.inputEvent) {
return hasClass(e.inputEvent.target, prefix("snap-control"));
}
},
dragControlStart(
moveable: MoveableManagerInterface<SnappableProps, SnappableState>
) {
moveable.state.snapRenderInfo = null;
checkSnapInfo(moveable);
},
dragControl(
moveable: MoveableManagerInterface<SnappableProps, SnappableState>
) {
this.drag(moveable);
},
dragControlEnd(
moveable: MoveableManagerInterface<SnappableProps, SnappableState>
) {
this.unset(moveable);
},
dragGroupStart(moveable: any, e: any) {
this.dragStart(moveable, e);
},
dragGroup(
moveable: MoveableGroupInterface<SnappableProps, SnappableState>
) {
this.drag(moveable);
},
dragGroupEnd(
moveable: MoveableGroupInterface<SnappableProps, SnappableState>
) {
this.unset(moveable);
},
dragGroupControlStart(
moveable: MoveableGroupInterface<SnappableProps, SnappableState>
) {
moveable.state.snapRenderInfo = null;
checkSnapInfo(moveable);
},
dragGroupControl(
moveable: MoveableManagerInterface<SnappableProps, SnappableState>
) {
this.drag(moveable);
},
dragGroupControlEnd(
moveable: MoveableGroupInterface<SnappableProps, SnappableState>
) {
this.unset(moveable);
},
unset(moveable: any) {
const state = moveable.state;
state.enableSnap = false;
state.guidelines = [];
state.snapRenderInfo = null;
state.elementRects = [];
},
};
/**
* Whether or not target can be snapped to the guideline. (default: false)
* @name Moveable.Snappable#snappable
* @see {@link https://daybrush.com/moveable/release/latest/doc/Moveable.Snappable.html#.SnappableOptions}
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body);
*
* moveable.snappable = true;
*/
/**
* A snap container that is the basis for snap, bounds, and innerBounds. (default: null = container)
* @name Moveable.Snappable#snapContainer
* @see {@link https://daybrush.com/moveable/release/latest/doc/Moveable.Snappable.html#.SnappableOptions}
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.querySelector(".container"));
*
* moveable.snapContainer = document.body;
*/
/**
* You can specify the directions to snap to the target. (default: { left: true, top: true, right: true, bottom: true })
* @name Moveable.Snappable#snapDirections
* @see {@link https://daybrush.com/moveable/release/latest/doc/Moveable.Snappable.html#.SnappableOptions}
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body, {
* snappable: true,
* snapDirections: true,
* });
* // snap center
* moveable.snapDirections = { left: true, top: true, right: true, bottom: true, center: true, middle: true };
*/
/**
* You can specify the snap directions of elements. (default: { left: true, ftrue, right: true, bottom: true })
* @name Moveable.Snappable#elementSnapDirections
* @see {@link https://daybrush.com/moveable/release/latest/doc/Moveable.Snappable.html#.SnappableOptions}
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body, {
* snappable: true,
* elementSnapDirections: true,
* });
* // snap center
* moveable.elementSnapDirections = { left: true, top: true, right: true, bottom: true, center: true, middle: true };
*/
/**
* When you drag, make the gap snap in the element guidelines. (default: true)
* @name Moveable.Snappable#snapGap
* @see {@link https://daybrush.com/moveable/release/latest/doc/Moveable.Snappable.html#.SnappableOptions}
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body, {
* snappable: true,
* snapElement: true,
* snapGap: true,
* });
*
* moveable.snapGap = false;
*/
/**
* Distance value that can snap to guidelines. (default: 5)
* @name Moveable.Snappable#snapThreshold
* @see {@link https://daybrush.com/moveable/release/latest/doc/Moveable.Snappable.html#.SnappableOptions}
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body);
*
* moveable.snapThreshold = 5;
*/
/**
* Add guidelines in the horizontal direction. (default: [])
* @name Moveable.Snappable#horizontalGuidelines
* @see {@link https://daybrush.com/moveable/release/latest/doc/Moveable.Snappable.html#.SnappableOptions}
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body);
*
* moveable.horizontalGuidelines = [100, 200, 500];
*/
/**
* Add guidelines in the vertical direction. (default: [])
* @name Moveable.Snappable#verticalGuidelines
* @see {@link https://daybrush.com/moveable/release/latest/doc/Moveable.Snappable.html#.SnappableOptions}
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body);
*
* moveable.verticalGuidelines = [100, 200, 500];
*/
/**
* Add guidelines for the element. (default: [])
* @name Moveable.Snappable#elementGuidelines
* @see {@link https://daybrush.com/moveable/release/latest/doc/Moveable.Snappable.html#.SnappableOptions}
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body);
*
* moveable.elementGuidelines = [
* document.querySelector(".element"),
* ];
*/
/**
* You can set up boundaries.
* @name Moveable.Snappable#bounds
* @see {@link https://daybrush.com/moveable/release/latest/doc/Moveable.Snappable.html#.SnappableOptions}
* @default null
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body);
*
* moveable.bounds = { left: 0, right: 1000, top: 0, bottom: 1000};
*/
/**
* You can set up inner boundaries.
* @name Moveable.Snappable#innerBounds
* @see {@link https://daybrush.com/moveable/release/latest/doc/Moveable.Snappable.html#.SnappableOptions}
* @default null
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body);
*
* moveable.innerBounds = { left: 500, top: 500, width: 100, height: 100};
*/
/**
* snap distance digits (default: 0)
* @name Moveable.Snappable#snapDigit
* @see {@link https://daybrush.com/moveable/release/latest/doc/Moveable.Snappable.html#.SnappableOptions}
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body);
*
* moveable.snapDigit = 0
*/
/**
* If width size is greater than 0, you can vertical snap to the grid. (default: 0)
* @name Moveable.Snappable#snapGridWidth
* @see {@link https://daybrush.com/moveable/release/latest/doc/Moveable.Snappable.html#.SnappableOptions}
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body);
*
* moveable.snapGridWidth = 5;
*/
/**
* If height size is greater than 0, you can horizontal snap to the grid. (default: 0)
* @name Moveable.Snappable#snapGridHeight
* @see {@link https://daybrush.com/moveable/release/latest/doc/Moveable.Snappable.html#.SnappableOptions}
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body);
*
* moveable.snapGridHeight = 5;
*/
/**
* Whether to show snap distance (default: true)
* @name Moveable.Snappable#isDisplaySnapDigit
* @see {@link https://daybrush.com/moveable/release/latest/doc/Moveable.Snappable.html#.SnappableOptions}
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body);
*
* moveable.isDisplaySnapDigit = true;
*/
/**
* Whether to show element inner snap distance (default: false)
* @name Moveable.Snappable#isDisplayInnerSnapDigit
* @see {@link https://daybrush.com/moveable/release/latest/doc/Moveable.Snappable.html#.SnappableOptions}
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body);
*
* moveable.isDisplayInnerSnapDigit = true;
*/
/**
* You can set the text format of the distance shown in the guidelines. (default: self)
* @name Moveable.Snappable#snapDistFormat
* @see {@link https://daybrush.com/moveable/release/latest/doc/Moveable.Snappable.html#.SnappableOptions}
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body, {
* snappable: true,
* snapDistFormat: (v, type) => v,
* });
* moveable.snapDistFormat = (v, type) => `${v}px`;
*/
/**
* When you drag or dragControl, the `snap` event is called.
* @memberof Moveable.Snappable
* @event snap
* @param {Moveable.Snappable.OnSnap} - Parameters for the `snap` event
* @example
* import Moveable from "moveable";
*
* const moveable = new Moveable(document.body, {
* snappable: true
* });
* moveable.on("snap", e => {
* console.log("onSnap", e);
* });
*/