@@ -125,6 +125,82 @@ function removeEvent(el, event, handler) {
125
125
}
126
126
}
127
127
128
+ function outerHeight ( node ) {
129
+ // This is deliberately excluding margin for our calculations, since we are using
130
+ // offsetTop which is including margin. See getBoundPosition
131
+ var height = node . clientHeight ;
132
+ var computedStyle = window . getComputedStyle ( node ) ;
133
+ height += int ( computedStyle . borderTopWidth ) ;
134
+ height += int ( computedStyle . borderBottomWidth ) ;
135
+ return height ;
136
+ }
137
+
138
+ function outerWidth ( node ) {
139
+ // This is deliberately excluding margin for our calculations, since we are using
140
+ // offsetLeft which is including margin. See getBoundPosition
141
+ var width = node . clientWidth ;
142
+ var computedStyle = window . getComputedStyle ( node ) ;
143
+ width += int ( computedStyle . borderLeftWidth ) ;
144
+ width += int ( computedStyle . borderRightWidth ) ;
145
+ return width ;
146
+ }
147
+ function innerHeight ( node ) {
148
+ var height = node . clientHeight ;
149
+ var computedStyle = window . getComputedStyle ( node ) ;
150
+ height -= int ( computedStyle . paddingTop ) ;
151
+ height -= int ( computedStyle . paddingBottom ) ;
152
+ return height ;
153
+ }
154
+
155
+ function innerWidth ( node ) {
156
+ var width = node . clientWidth ;
157
+ var computedStyle = window . getComputedStyle ( node ) ;
158
+ width -= int ( computedStyle . paddingLeft ) ;
159
+ width -= int ( computedStyle . paddingRight ) ;
160
+ return width ;
161
+ }
162
+
163
+ function isNum ( num ) {
164
+ return typeof num === 'number' && ! isNaN ( num ) ;
165
+ }
166
+
167
+ function int ( a ) {
168
+ return parseInt ( a , 10 ) ;
169
+ }
170
+
171
+ function getBoundPosition ( draggable , clientX , clientY ) {
172
+ var bounds = JSON . parse ( JSON . stringify ( draggable . props . bounds ) ) ;
173
+ var node = draggable . getDOMNode ( ) ;
174
+ var parent = node . parentNode ;
175
+
176
+ if ( bounds === 'parent' ) {
177
+ var nodeStyle = window . getComputedStyle ( node ) ;
178
+ var parentStyle = window . getComputedStyle ( parent ) ;
179
+ // Compute bounds. This is a pain with padding and offsets but this gets it exactly right.
180
+ bounds = {
181
+ left : - node . offsetLeft + int ( parentStyle . paddingLeft ) +
182
+ int ( nodeStyle . borderLeftWidth ) + int ( nodeStyle . marginLeft ) ,
183
+ top : - node . offsetTop + int ( parentStyle . paddingTop ) +
184
+ int ( nodeStyle . borderTopWidth ) + int ( nodeStyle . marginTop ) ,
185
+ right : innerWidth ( parent ) - outerWidth ( node ) - node . offsetLeft ,
186
+ bottom : innerHeight ( parent ) - outerHeight ( node ) - node . offsetTop
187
+ } ;
188
+ } else {
189
+ if ( isNum ( bounds . right ) ) bounds . right -= outerWidth ( node ) ;
190
+ if ( isNum ( bounds . bottom ) ) bounds . bottom -= outerHeight ( node ) ;
191
+ }
192
+
193
+ // Keep x and y below right and bottom limits...
194
+ if ( isNum ( bounds . right ) ) clientX = Math . min ( clientX , bounds . right ) ;
195
+ if ( isNum ( bounds . bottom ) ) clientY = Math . min ( clientY , bounds . bottom ) ;
196
+
197
+ // But above left and top limits.
198
+ if ( isNum ( bounds . left ) ) clientX = Math . max ( clientX , bounds . left ) ;
199
+ if ( isNum ( bounds . top ) ) clientY = Math . max ( clientY , bounds . top ) ;
200
+
201
+ return [ clientX , clientY ] ;
202
+ }
203
+
128
204
function snapToGrid ( grid , pendingX , pendingY ) {
129
205
var x = Math . round ( pendingX / grid [ 0 ] ) * grid [ 0 ] ;
130
206
var y = Math . round ( pendingY / grid [ 1 ] ) * grid [ 1 ] ;
@@ -185,6 +261,42 @@ module.exports = React.createClass({
185
261
*/
186
262
axis : React . PropTypes . oneOf ( [ 'both' , 'x' , 'y' ] ) ,
187
263
264
+ /**
265
+ * `bounds` determines the range of movement available to the element.
266
+ * Available values are:
267
+ *
268
+ * 'parent' restricts movement within the Draggable's parent node.
269
+ *
270
+ * Alternatively, pass an object with the following properties, all of which are optional:
271
+ *
272
+ * {left: LEFT_BOUND, right: RIGHT_BOUND, bottom: BOTTOM_BOUND, top: TOP_BOUND}
273
+ *
274
+ * All values are in px.
275
+ *
276
+ * Example:
277
+ *
278
+ * ```jsx
279
+ * var App = React.createClass({
280
+ * render: function () {
281
+ * return (
282
+ * <Draggable bounds={{right: 300, bottom: 300}}>
283
+ * <div>Content</div>
284
+ * </Draggable>
285
+ * );
286
+ * }
287
+ * });
288
+ * ```
289
+ */
290
+ bounds : React . PropTypes . oneOfType ( [
291
+ React . PropTypes . shape ( {
292
+ left : React . PropTypes . Number ,
293
+ right : React . PropTypes . Number ,
294
+ top : React . PropTypes . Number ,
295
+ bottom : React . PropTypes . Number
296
+ } ) ,
297
+ React . PropTypes . oneOf ( [ 'parent' , false ] )
298
+ ] ) ,
299
+
188
300
/**
189
301
* By default, we add 'user-select:none' attributes to the document body
190
302
* to prevent ugly text selection during drag. If this is causing problems
@@ -353,6 +465,7 @@ module.exports = React.createClass({
353
465
getDefaultProps : function ( ) {
354
466
return {
355
467
axis : 'both' ,
468
+ bounds : false ,
356
469
handle : null ,
357
470
cancel : null ,
358
471
grid : null ,
@@ -454,6 +567,11 @@ module.exports = React.createClass({
454
567
clientX = coords [ 0 ] , clientY = coords [ 1 ] ;
455
568
}
456
569
570
+ if ( this . props . bounds ) {
571
+ var pos = getBoundPosition ( this , clientX , clientY ) ;
572
+ clientX = pos [ 0 ] , clientY = pos [ 1 ] ;
573
+ }
574
+
457
575
// Call event handler. If it returns explicit false, cancel.
458
576
var shouldUpdate = this . props . onDrag ( e , createUIEvent ( this ) ) ;
459
577
if ( shouldUpdate === false ) return this . handleDragEnd ( ) ;
0 commit comments