-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Expand file tree
/
Copy pathpositionFns.js
More file actions
156 lines (137 loc) · 6.01 KB
/
positionFns.js
File metadata and controls
156 lines (137 loc) · 6.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// @flow
import {isNum, int} from './shims';
import {getTouch, innerWidth, innerHeight, offsetXYFromParent, outerWidth, outerHeight} from './domFns';
import type Draggable from '../Draggable';
import type {Bounds, ControlPosition, DraggableData, MouseTouchEvent} from './types';
import type DraggableCore from '../DraggableCore';
export function getBoundPosition(draggable: Draggable, x: number, y: number): [number, number] {
// If no bounds, short-circuit and move on
if (!draggable.props.bounds) return [x, y];
// Clone new bounds
let {bounds} = draggable.props;
bounds = typeof bounds === 'string' ? bounds : cloneBounds(bounds);
const node = findDOMNode(draggable);
if (typeof bounds === 'string') {
const {ownerDocument} = node;
const ownerWindow = ownerDocument.defaultView;
let boundNode;
if (bounds === 'parent') {
boundNode = node.parentNode;
} else {
// Flow assigns the wrong return type (Node) for getRootNode(),
// so we cast it to one of the correct types (Element).
// The others are Document and ShadowRoot.
// All three implement querySelector() so it's safe to call.
const rootNode = (((node.getRootNode()): any): Element);
boundNode = rootNode.querySelector(bounds);
}
// Accept Element (not just HTMLElement) to support SVG elements
if (!(boundNode instanceof ownerWindow.Element)) {
throw new Error('Bounds selector "' + bounds + '" could not find an element.');
}
const boundNodeEl: Element = boundNode; // for Flow, can't seem to refine correctly
const nodeStyle = ownerWindow.getComputedStyle(node);
const boundNodeStyle = ownerWindow.getComputedStyle(boundNodeEl);
// Calculate node offset. HTMLElements have offsetLeft/offsetTop, but SVG elements don't.
// For SVG elements, compute offset from bounding rectangles.
let nodeOffsetLeft: number, nodeOffsetTop: number;
if (node instanceof ownerWindow.HTMLElement) {
nodeOffsetLeft = node.offsetLeft;
nodeOffsetTop = node.offsetTop;
} else {
// For SVG elements, calculate offset relative to parent using getBoundingClientRect
const nodeRect = node.getBoundingClientRect();
const boundNodeRect = boundNodeEl.getBoundingClientRect();
nodeOffsetLeft = nodeRect.left - boundNodeRect.left - int(boundNodeStyle.borderLeftWidth);
nodeOffsetTop = nodeRect.top - boundNodeRect.top - int(boundNodeStyle.borderTopWidth);
}
// Compute bounds. This is a pain with padding and offsets but this gets it exactly right.
bounds = {
left: -nodeOffsetLeft + int(boundNodeStyle.paddingLeft) + int(nodeStyle.marginLeft),
top: -nodeOffsetTop + int(boundNodeStyle.paddingTop) + int(nodeStyle.marginTop),
right: innerWidth(boundNodeEl) - outerWidth(node) - nodeOffsetLeft +
int(boundNodeStyle.paddingRight) - int(nodeStyle.marginRight),
bottom: innerHeight(boundNodeEl) - outerHeight(node) - nodeOffsetTop +
int(boundNodeStyle.paddingBottom) - int(nodeStyle.marginBottom)
};
}
// Keep x and y below right and bottom limits...
if (isNum(bounds.right)) x = Math.min(x, bounds.right);
if (isNum(bounds.bottom)) y = Math.min(y, bounds.bottom);
// But above left and top limits.
if (isNum(bounds.left)) x = Math.max(x, bounds.left);
if (isNum(bounds.top)) y = Math.max(y, bounds.top);
return [x, y];
}
export function snapToGrid(grid: [number, number], pendingX: number, pendingY: number): [number, number] {
const x = Math.round(pendingX / grid[0]) * grid[0];
const y = Math.round(pendingY / grid[1]) * grid[1];
return [x, y];
}
export function canDragX(draggable: Draggable): boolean {
return draggable.props.axis === 'both' || draggable.props.axis === 'x';
}
export function canDragY(draggable: Draggable): boolean {
return draggable.props.axis === 'both' || draggable.props.axis === 'y';
}
// Get {x, y} positions from event.
export function getControlPosition(e: MouseTouchEvent, touchIdentifier: ?number, draggableCore: DraggableCore): ?ControlPosition {
const touchObj = typeof touchIdentifier === 'number' ? getTouch(e, touchIdentifier) : null;
if (typeof touchIdentifier === 'number' && !touchObj) return null; // not the right touch
const node = findDOMNode(draggableCore);
// User can provide an offsetParent if desired.
const offsetParent = draggableCore.props.offsetParent || node.offsetParent || node.ownerDocument.body;
return offsetXYFromParent(touchObj || e, offsetParent, draggableCore.props.scale);
}
// Create an data object exposed by <DraggableCore>'s events
export function createCoreData(draggable: DraggableCore, x: number, y: number): DraggableData {
const isStart = !isNum(draggable.lastX);
const node = findDOMNode(draggable);
if (isStart) {
// If this is our first move, use the x and y as last coords.
return {
node,
deltaX: 0, deltaY: 0,
lastX: x, lastY: y,
x, y,
};
} else {
// Otherwise calculate proper values.
return {
node,
deltaX: x - draggable.lastX, deltaY: y - draggable.lastY,
lastX: draggable.lastX, lastY: draggable.lastY,
x, y,
};
}
}
// Create an data exposed by <Draggable>'s events
export function createDraggableData(draggable: Draggable, coreData: DraggableData): DraggableData {
const scale = draggable.props.scale;
return {
node: coreData.node,
x: draggable.state.x + (coreData.deltaX / scale),
y: draggable.state.y + (coreData.deltaY / scale),
deltaX: (coreData.deltaX / scale),
deltaY: (coreData.deltaY / scale),
lastX: draggable.state.x,
lastY: draggable.state.y
};
}
// A lot faster than stringify/parse
function cloneBounds(bounds: Bounds): Bounds {
return {
left: bounds.left,
top: bounds.top,
right: bounds.right,
bottom: bounds.bottom
};
}
function findDOMNode(draggable: Draggable | DraggableCore): HTMLElement {
const node = draggable.findDOMNode();
if (!node) {
throw new Error('<DraggableCore>: Unmounted during event!');
}
// $FlowIgnore we can't assert on HTMLElement due to tests... FIXME
return node;
}