Skip to content

Commit

Permalink
feat: add gapSnap props #200
Browse files Browse the repository at this point in the history
  • Loading branch information
daybrush committed Mar 26, 2020
1 parent 53b3b7e commit 4cecb27
Show file tree
Hide file tree
Showing 7 changed files with 328 additions and 18 deletions.
17 changes: 15 additions & 2 deletions packages/react-moveable/src/App.css
Expand Up @@ -48,7 +48,7 @@ img {
top: 100px;
height: 800px;
border: 1px solid #f55;
transform: scale(0.5);
/* transform: scale(0.5); */
}
.guides {
position: absolute;
Expand Down Expand Up @@ -91,6 +91,7 @@ img {
position: absolute;
top: 100px;
left: 100px;
display: none;
}
.App svg * {
transform-origin: 20% 50%;
Expand All @@ -105,12 +106,24 @@ img {
}
}
.box.box2 {
position: absolute;
width: 120px;
height: 120px;
top: 200px;
left: 0;
}
.box.box23 {
position: absolute;
top: 150px;
width: 120px;
height: 120px;
top: 200px;
left: 200px;
}
.box.box24 {
position: absolute;
width: 120px;
height: 120px;
top: 200px;
left: 450px;
}
.box2 span {
Expand Down
22 changes: 17 additions & 5 deletions packages/react-moveable/src/App.tsx
Expand Up @@ -16,7 +16,7 @@ setAlias("sx", ["transform", "scaleX"]);
setAlias("sy", ["transform", "scaleY"]);
setAlias("matrix3d", ["transform", "matrix3d"]);

class App extends React.Component {
class App extends React.Component<any, any> {
public moveable!: Moveable;
public state: {
container: any,
Expand All @@ -25,19 +25,24 @@ class App extends React.Component {
isShift: boolean,
targets: Array<HTMLElement | SVGElement>,
isResizable: boolean,
isUnmount: boolean,
} = {
target: null,
container: null,
targets: [],
isResizable: true,
isShift: false,
isUnmount: false,
emo: null,
};
private itemMap: Map<HTMLElement |SVGElement, Frame> = new Map();
private items: IObject<Frame> = {};
private guides1!: Guides;
private guides2!: Guides;
public render() {
if (this.state.isUnmount) {
return (<div></div>);
}
const selectedTarget = this.state.target;
const isResizable = this.state.isResizable;
const item = this.itemMap.get(selectedTarget)!;
Expand Down Expand Up @@ -186,16 +191,18 @@ class App extends React.Component {
snapDigit={0}
bounds={{ left: 30, top: 20 }}
// innerBounds={{ left: 400, top: 400, width: 200, height: 200 }}
verticalGuidelines={[200, 400, 600]}
horizontalGuidelines={[200, 400, 600]}
// verticalGuidelines={[150]}
// horizontalGuidelines={[150]}
// zoom={2}
// renderDirections={["n", "ne", "nw"]}
elementGuidelines={[
document.querySelector(".box1 span")!,
// document.querySelector(".box1 span")!,
// document.querySelector(".emo img")!,
document.querySelector<HTMLElement>(".box2")!,
document.querySelector<HTMLElement>(".box23")!,
document.querySelector<HTMLElement>(".box24")!,
]}
snapCenter={true}
// snapCenter={true}
// snapThreshold={10}
// scalable={!isResizable}
// scalable={true}
Expand Down Expand Up @@ -321,6 +328,7 @@ class App extends React.Component {
<div className="box box2" data-target="box2"><span>A</span></div>

<div className="box box23" data-target="box23"><span>AA</span></div>
<div className="box box24" data-target="box24"><span>BB</span></div>

<img src={logo} className="App-logo" alt="logo" data-target="logo" />
<p data-target="p">
Expand Down Expand Up @@ -462,6 +470,10 @@ class App extends React.Component {
requester.requestEnd();
requester = null;
}
}).keydown("e", () => {
this.setState({
isUnmount: true,
});
});

const targets: any[] = [].slice.call(document.querySelectorAll(`[data-target="box"] span`));
Expand Down
167 changes: 164 additions & 3 deletions packages/react-moveable/src/react-moveable/ables/Snappable.tsx
Expand Up @@ -5,11 +5,11 @@ import {
SnappableState, Guideline,
SnapInfo, BoundInfo,
ScalableProps, SnapPosInfo, RotatableProps,
RectInfo, DraggableProps, SnapOffsetInfo,
RectInfo, DraggableProps, SnapOffsetInfo, GapGuideline,
} from "../types";
import {
prefix, caculatePoses, getRect,
getAbsolutePosesByState, getAbsolutePoses, throttle, roundSign, getDistSize, groupBy, flat, maxOffset,
getAbsolutePosesByState, getAbsolutePoses, throttle, roundSign, getDistSize, groupBy, flat, maxOffset, minOffset, triggerEvent,
} from "../utils";
import { IObject, find } from "@daybrush/utils";
import {
Expand All @@ -33,6 +33,7 @@ import {
getNearOffsetInfo,
checkSnapKeepRatio,
} from "./snappable/snap";
import { triggerAble } from "../getAbleDragger";

export function snapStart(moveable: MoveableManager<SnappableProps, SnappableState>) {
const state = moveable.state;
Expand Down Expand Up @@ -1035,7 +1036,7 @@ function groupByElementGuidelines(
) {
const groupInfos: Array<[Element, number, any]> = [];

const group = groupBy(guidelines.filter(({ element }) => element), ({ element, pos }) => {
const group = groupBy(guidelines.filter(({ element, gap }) => element && !gap), ({ element, pos }) => {
const elementPos = pos[index];
const sign = Math.min(0, elementPos - clientPos) < 0 ? -1 : 1;
const groupKey = `${sign}_${pos[index ? 0 : 1]}`;
Expand Down Expand Up @@ -1149,6 +1150,139 @@ function renderGuidelines(
});
}

function getGapGuidelinesToStart(
guidelines: Guideline[],
index: number,
targetPos: number[],
targetSizes: number[],
guidelinePos: number[],
gap: number,
otherPos: number,
): GapGuideline[] {
const absGap = Math.abs(gap);
let start = guidelinePos[index] + (gap > 0 ? targetSizes[0] : 0);

return guidelines.filter(({ pos: gapPos }) => gapPos[index] <= targetPos[index])
.sort(({ pos: aPos }, { pos: bPos }) => bPos[index] - aPos[index])
.filter(({ pos: gapPos, sizes: gapSizes }) => {
const nextPos = gapPos[index];

if (throttle(nextPos + gapSizes![index], 0.0001) === throttle(start - absGap, 0.0001)) {
start = nextPos;
return true;
}
return false;
}).map(gapGuideline => {
const renderPos = -targetPos[index] + gapGuideline.pos[index] + gapGuideline.sizes![index];

return {
...gapGuideline,
gap,
renderPos: index ? [otherPos, renderPos] : [renderPos, otherPos],
};
});
}
function getGapGuidelinesToEnd(
guidelines: Guideline[],
index: number,
targetPos: number[],
targetSizes: number[],
guidelinePos: number[],
gap: number,
otherPos: number,
): GapGuideline[] {
const absGap = Math.abs(gap);
let start = guidelinePos[index] + (gap < 0 ? targetSizes[index] : 0);

return guidelines.filter(({ pos: gapPos }) => gapPos[index] > targetPos[index])
.sort(({ pos: aPos }, { pos: bPos }) => aPos[index] - bPos[index])
.filter(({ pos: gapPos, sizes: gapSizes }) => {
const nextPos = gapPos[index];

if (throttle(nextPos, 0.0001) === throttle(start + absGap, 0.0001)) {
start = nextPos + gapSizes![index];
return true;
}
return false;
}).map(gapGuideline => {
const renderPos = -targetPos[index] + gapGuideline.pos[index] - absGap;

return {
...gapGuideline,
gap,
renderPos: index ? [otherPos, renderPos] : [renderPos, otherPos],
};
});
}
function getGapGuidelines(
guidelines: Guideline[],
type: "vertical" | "horizontal",
targetPos: number[],
targetSizes: number[],
): GapGuideline[] {
const elementGuidelines = guidelines.filter(
({ element, gap, type: guidelineType }) => element && gap && guidelineType === type);
const [index, otherIndex] = type === "vertical" ? [0, 1] : [1, 0];

return flat(elementGuidelines.map((guideline, i) => {
const pos = guideline.pos;
const gap = guideline.gap!;
const gapGuidelines = guideline.gapGuidelines!;
const sizes = guideline.sizes!;

let offset = minOffset(
pos[otherIndex] + sizes[otherIndex] - targetPos[otherIndex],
pos[otherIndex] - targetPos[otherIndex] - targetSizes[otherIndex],
);
const minSize = Math.min(sizes[otherIndex], targetSizes[otherIndex]);

if (offset > 0 && offset > minSize) {
offset = (offset - minSize / 2) * 2;
} else if (offset < 0 && offset < -minSize) {
offset = (offset + minSize / 2) * 2;
}

const otherPos = (offset > 0 ? 0 : targetSizes[otherIndex]) + offset / 2;
return [
...getGapGuidelinesToStart(gapGuidelines, index, targetPos, targetSizes, pos, gap, otherPos),
...getGapGuidelinesToEnd(gapGuidelines, index, targetPos, targetSizes, pos, gap, otherPos),
];
}));
}
function renderGapGuidelines(
moveable: MoveableManager<SnappableProps, SnappableState>,
gapGuidelines: GapGuideline[],
type: "vertical" | "horizontal",
[directionName, posName1, posName2, sizeName]: readonly [string, string, string, string],
React: any,
) {
const {
snapDigit = 0,
isDisplaySnapDigit = true,
} = moveable.props;

const otherType = type === "vertical" ? "horizontal" : "vertical";
const [index, otherIndex] = type === "vertical" ? [0, 1] : [1, 0];

return gapGuidelines.map(({ renderPos, gap }, i) => {
const absGap = Math.abs(gap!);
const snapSize = isDisplaySnapDigit ? parseFloat(absGap.toFixed(snapDigit)) : 0;

return <div className={prefix(
"line",
directionName,
"guideline",
"gap",
)}
data-size={snapSize}
key={`${otherType}GapGuideline${i}`} style={{
[posName1]: `${renderPos[index]}px`,
[posName2]: `${renderPos[otherIndex]}px`,
[sizeName]: `${absGap}px`,
}} />;
});
}

function addBoundGuidelines(
moveable: MoveableManager<SnappableProps, SnappableState>,
verticalPoses: number[],
Expand Down Expand Up @@ -1193,6 +1327,7 @@ export default {
snapHorizontal: Boolean,
snapVertical: Boolean,
snapElement: Boolean,
snapGap: Boolean,
isDisplaySnapDigit: Boolean,
snapDigit: Number,
snapThreshold: Number,
Expand Down Expand Up @@ -1281,7 +1416,33 @@ export default {
);
const horizontalNames = ["horizontal", "left", "top", "width"] as const;
const verticalNames = ["vertical", "top", "left", "height"] as const;

const gapVerticalGuidelines = getGapGuidelines(
verticalGuildelines, "vertical",
[targetLeft, targetTop],
[width, height],
);
const gapHorizontalGuidelines = getGapGuidelines(
horizontalGuidelines, "horizontal",
[targetLeft, targetTop],
[width, height],
);

return [
...renderGapGuidelines(
moveable,
gapVerticalGuidelines,
"vertical",
horizontalNames,
React,
),
...renderGapGuidelines(
moveable,
gapHorizontalGuidelines,
"horizontal",
verticalNames,
React,
),
...renderElementGroup(
elementHorizontalGroup,
horizontalNames,
Expand Down

0 comments on commit 4cecb27

Please sign in to comment.