@@ -33,19 +33,28 @@ export interface GridMouseEvent {
33
33
}
34
34
35
35
export const Grid3 : React . FC < Grid3Props > = ( props : Grid3Props ) : ReactNode => {
36
- const { dimensions } = props ;
36
+ const gridDimensions = props . dimensions ;
37
37
38
38
const logger = useLogger ( 'page:grid3' ) ;
39
39
40
40
const gridItemRef = useRef < HTMLDivElement > ( null ) ;
41
+
41
42
const [ position , setPosition ] = useState ( { x : 0 , y : 0 } ) ;
43
+ const [ dimension , setDimension ] = useState ( { width : 100 , height : 100 } ) ;
44
+
42
45
const [ isDragging , setIsDragging ] = useState ( false ) ;
43
46
const [ dragOffset , setDragOffset ] = useState ( { x : 0 , y : 0 } ) ;
44
47
45
- const onMouseDown = useCallback (
48
+ const [ isResizing , setIsResizing ] = useState ( false ) ;
49
+ const [ resizeOffset , setResizeOffset ] = useState ( { x : 0 , y : 0 } ) ;
50
+ const [ dimensionBeforeResize , setDimensionBeforeResize ] = useState ( dimension ) ;
51
+
52
+ const onDragStart = useCallback (
46
53
( event : GridMouseEvent ) => {
47
- logger . debug ( 'onMouseDown' ) ;
54
+ logger . debug ( 'onDragStart' ) ;
55
+
48
56
setIsDragging ( true ) ;
57
+
49
58
setDragOffset ( {
50
59
x : event . clientX - position . x ,
51
60
y : event . clientY - position . y ,
@@ -54,13 +63,35 @@ export const Grid3: React.FC<Grid3Props> = (props: Grid3Props): ReactNode => {
54
63
[ logger , position ]
55
64
) ;
56
65
66
+ const onResizeStart = useCallback (
67
+ ( event : GridMouseEvent ) => {
68
+ logger . debug ( 'onResizeStart' ) ;
69
+
70
+ setIsResizing ( true ) ;
71
+
72
+ setResizeOffset ( {
73
+ x : event . clientX ,
74
+ y : event . clientY ,
75
+ } ) ;
76
+
77
+ setDimensionBeforeResize ( {
78
+ width : dimension . width ,
79
+ height : dimension . height ,
80
+ } ) ;
81
+ } ,
82
+ [ logger , dimension ]
83
+ ) ;
84
+
57
85
const onMouseMove = useCallback (
58
86
( event : GridMouseEvent ) => {
59
- if ( ! gridItemRef . current ) {
60
- return ;
61
- }
87
+ const handleDrag = ( ) => {
88
+ if ( ! gridItemRef . current ) {
89
+ return ;
90
+ }
91
+
92
+ const currentWidth = gridItemRef . current . clientWidth ;
93
+ const currentHeight = gridItemRef . current . clientHeight ;
62
94
63
- if ( isDragging ) {
64
95
let newX = event . clientX - dragOffset . x ;
65
96
let newY = event . clientY - dragOffset . y ;
66
97
@@ -81,20 +112,18 @@ export const Grid3: React.FC<Grid3Props> = (props: Grid3Props): ReactNode => {
81
112
// If the new position would reach farther right than the parent
82
113
// then adjust the position of the right edge of the element
83
114
// to be at the right edge of the parent.
84
- if ( newX + gridItemRef . current . clientWidth > dimensions . width ) {
85
- newX = dimensions . width - gridItemRef . current . clientWidth ;
115
+ if ( newX + currentWidth > gridDimensions . width ) {
116
+ newX = gridDimensions . width - currentWidth ;
86
117
}
87
118
88
119
// If the new position would reach farther down than the parent
89
120
// then adjust the position of the bottom edge of the element
90
121
// to be at the bottom edge of the parent.
91
- if ( newY + gridItemRef . current . clientHeight > dimensions . height ) {
92
- newY = dimensions . height - gridItemRef . current . clientHeight ;
122
+ if ( newY + currentHeight > gridDimensions . height ) {
123
+ newY = gridDimensions . height - currentHeight ;
93
124
}
94
125
95
126
logger . debug ( 'onMouseMove - dragging' , {
96
- calcX : event . clientX - dragOffset . x ,
97
- calcY : event . clientY - dragOffset . y ,
98
127
newX,
99
128
newY,
100
129
} ) ;
@@ -103,67 +132,166 @@ export const Grid3: React.FC<Grid3Props> = (props: Grid3Props): ReactNode => {
103
132
x : newX ,
104
133
y : newY ,
105
134
} ) ;
135
+ } ;
136
+
137
+ const handleResize = ( ) => {
138
+ if ( ! gridItemRef . current ) {
139
+ return ;
140
+ }
141
+
142
+ const currentLeft = gridItemRef . current . offsetLeft ;
143
+ const currentTop = gridItemRef . current . offsetTop ;
144
+
145
+ const currentWidth = dimensionBeforeResize . width ;
146
+ const currentHeight = dimensionBeforeResize . height ;
147
+
148
+ const deltaWidth = event . clientX - resizeOffset . x ;
149
+ const deltaHeight = event . clientY - resizeOffset . y ;
150
+
151
+ let newWidth = currentWidth + deltaWidth ;
152
+ let newHeight = currentHeight + deltaHeight ;
153
+
154
+ // If the new size would shrink the element to be smaller than
155
+ // the minimum size then set the size to the minimum.
156
+ const minWidth = 50 ;
157
+ const minHeight = 50 ;
158
+
159
+ if ( newWidth < minWidth ) {
160
+ newWidth = minWidth ;
161
+ }
162
+
163
+ if ( newHeight < minHeight ) {
164
+ newHeight = minHeight ;
165
+ }
166
+
167
+ // If the new size would reach farther right than the parent then
168
+ // adjust the size of the element to be at the right edge of the parent.
169
+ if ( currentLeft + newWidth > gridDimensions . width ) {
170
+ newWidth = gridDimensions . width - currentLeft ;
171
+ }
172
+
173
+ // If the new size would reach farther down than the parent then
174
+ // adjust the size of the element to be at the bottom edge of the parent.
175
+ if ( currentTop + newHeight > gridDimensions . height ) {
176
+ newHeight = gridDimensions . height - currentTop ;
177
+ }
178
+
179
+ logger . debug ( 'onMouseMove - resizing' , {
180
+ deltaWidth,
181
+ deltaHeight,
182
+ oldWidth : currentWidth ,
183
+ oldHeight : currentHeight ,
184
+ newWidth,
185
+ newHeight,
186
+ currentTop,
187
+ currentLeft,
188
+ } ) ;
189
+
190
+ setDimension ( {
191
+ width : newWidth ,
192
+ height : newHeight ,
193
+ } ) ;
194
+ } ;
195
+
196
+ if ( isDragging ) {
197
+ handleDrag ( ) ;
106
198
}
107
- } ,
108
- [ logger , dimensions , isDragging , dragOffset ]
109
- ) ;
110
199
111
- const onMouseUp = useCallback (
112
- ( _event : GridMouseEvent ) => {
113
- logger . debug ( 'onMouseUp' ) ;
114
- setIsDragging ( false ) ;
200
+ if ( isResizing ) {
201
+ handleResize ( ) ;
202
+ }
115
203
} ,
116
- [ logger ]
204
+ [
205
+ logger ,
206
+ gridDimensions ,
207
+ isDragging ,
208
+ dragOffset ,
209
+ isResizing ,
210
+ resizeOffset ,
211
+ dimensionBeforeResize ,
212
+ ]
117
213
) ;
118
214
215
+ const onMouseUp = useCallback ( ( ) => {
216
+ logger . debug ( 'onMouseUp' ) ;
217
+ setIsDragging ( false ) ;
218
+ setIsResizing ( false ) ;
219
+ } , [ logger ] ) ;
220
+
119
221
// When we attach the `mousemove` event listeners to the draggable element
120
222
// then if the mouse moves too quickly then it can leave the element behind.
121
223
// Once that happens, then the mouse events stop being emitted and so it
122
224
// doesn't reposition to stay under the mouse cursor.
123
225
// A workaround is to attach the `mousemove` event listeners to the `window`
124
226
// so that the event always fires.
125
227
useEffect ( ( ) => {
126
- const windowOnMouseMove = ( event : WindowEventMap [ 'mousemove' ] ) => {
127
- onMouseMove ( event ) ;
128
- } ;
228
+ // Events that signal the user has stopped dragging or resizing.
229
+ const onMouseUpEventAliases : Array < keyof WindowEventMap > = [
230
+ 'mouseup' ,
231
+ 'mouseleave' ,
232
+ 'blur' ,
233
+ ] ;
129
234
130
- const windowOnMouseUp = ( event : WindowEventMap [ 'mouseup' ] ) => {
131
- onMouseUp ( event ) ;
132
- } ;
235
+ onMouseUpEventAliases . forEach ( ( eventName ) => {
236
+ window . addEventListener ( eventName , onMouseUp ) ;
237
+ } ) ;
133
238
134
- window . addEventListener ( 'mousemove' , windowOnMouseMove ) ;
135
- window . addEventListener ( 'mouseup' , windowOnMouseUp ) ;
239
+ window . addEventListener ( 'mousemove' , onMouseMove ) ;
136
240
137
241
return ( ) => {
138
- window . removeEventListener ( 'mousemove' , windowOnMouseMove ) ;
139
- window . removeEventListener ( 'mouseup' , windowOnMouseUp ) ;
242
+ onMouseUpEventAliases . forEach ( ( eventName ) => {
243
+ window . removeEventListener ( eventName , onMouseUp ) ;
244
+ } ) ;
245
+
246
+ window . removeEventListener ( 'mousemove' , onMouseMove ) ;
140
247
} ;
141
248
} , [ onMouseMove , onMouseUp ] ) ;
142
249
143
250
return (
144
251
< div
145
252
style = { {
146
- height : dimensions . height ,
147
- width : dimensions . width ,
253
+ position : 'relative' ,
254
+ height : gridDimensions . height ,
255
+ width : gridDimensions . width ,
148
256
} }
149
257
>
150
258
< div
151
259
ref = { gridItemRef }
152
260
style = { {
153
- width : '100px' ,
154
- height : '100px' ,
155
- backgroundColor : 'lightblue' ,
156
261
position : 'relative' ,
157
262
top : `${ position . y } px` ,
158
263
left : `${ position . x } px` ,
159
- cursor : 'move' ,
264
+ width : `${ dimension . width } px` ,
265
+ height : `${ dimension . height } px` ,
266
+ backgroundColor : 'lightblue' ,
267
+ overflow : 'auto' ,
160
268
} }
161
- onMouseDown = { onMouseDown }
162
- // onMouseMove={onMouseMove}
163
- // onMouseUp={onMouseUp}
164
- // onMouseLeave={onMouseLeave}
165
269
>
166
- Drag me!
270
+ < div
271
+ style = { {
272
+ position : 'relative' ,
273
+ width : '100%' ,
274
+ height : '20px' ,
275
+ backgroundColor : 'red' ,
276
+ cursor : isDragging ? 'grabbing' : 'grab' ,
277
+ textAlign : 'center' ,
278
+ } }
279
+ onMouseDown = { onDragStart }
280
+ >
281
+ Drag Handle
282
+ </ div >
283
+
284
+ < div
285
+ style = { {
286
+ position : 'absolute' ,
287
+ bottom : 0 ,
288
+ right : 0 ,
289
+ borderLeft : '10px solid transparent' ,
290
+ borderBottom : '10px solid green' ,
291
+ cursor : 'se-resize' ,
292
+ } }
293
+ onMouseDown = { onResizeStart }
294
+ > </ div >
167
295
</ div >
168
296
</ div >
169
297
) ;
0 commit comments