1
1
import React , {
2
- createRef ,
3
2
useState ,
4
3
useRef ,
5
4
useCallback ,
6
5
FunctionComponent ,
7
6
useMemo ,
8
7
useEffect ,
9
- Component ,
10
8
} from 'react' ;
11
9
import { UploadMajor , CircleAlertMajor } from '@shopify/polaris-icons' ;
12
10
@@ -22,10 +20,12 @@ import {useUniqueId} from '../../utilities/unique-id';
22
20
import { useComponentDidMount } from '../../utilities/use-component-did-mount' ;
23
21
import { useToggle } from '../../utilities/use-toggle' ;
24
22
import { AlphaStack } from '../AlphaStack' ;
23
+ import { useEventListener } from '../../utilities/use-event-listener' ;
25
24
26
25
import { FileUpload } from './components' ;
27
26
import { DropZoneContext } from './context' ;
28
27
import {
28
+ DropZoneEvent ,
29
29
fileAccepted ,
30
30
getDataTransferFiles ,
31
31
defaultAllowMultiple ,
@@ -141,6 +141,7 @@ export const DropZone: React.FunctionComponent<DropZoneProps> & {
141
141
onDragLeave,
142
142
} : DropZoneProps ) {
143
143
const node = useRef < HTMLDivElement > ( null ) ;
144
+ const inputRef = useRef < HTMLInputElement > ( null ) ;
144
145
const dragTargets = useRef < EventTarget [ ] > ( [ ] ) ;
145
146
146
147
// eslint-disable-next-line react-hooks/exhaustive-deps
@@ -208,7 +209,7 @@ export const DropZone: React.FunctionComponent<DropZoneProps> & {
208
209
) ;
209
210
210
211
const handleDrop = useCallback (
211
- ( event : DragEvent ) => {
212
+ ( event : DropZoneEvent ) => {
212
213
stopEvent ( event ) ;
213
214
if ( disabled ) return ;
214
215
@@ -225,13 +226,15 @@ export const DropZone: React.FunctionComponent<DropZoneProps> & {
225
226
onDropAccepted && acceptedFiles . length && onDropAccepted ( acceptedFiles ) ;
226
227
onDropRejected && rejectedFiles . length && onDropRejected ( rejectedFiles ) ;
227
228
228
- ( event . target as HTMLInputElement ) . value = '' ;
229
+ if ( ! ( event . target && 'value' in event . target ) ) return ;
230
+
231
+ event . target . value = '' ;
229
232
} ,
230
233
[ disabled , getValidatedFiles , onDrop , onDropAccepted , onDropRejected ] ,
231
234
) ;
232
235
233
236
const handleDragEnter = useCallback (
234
- ( event : DragEvent ) => {
237
+ ( event : DropZoneEvent ) => {
235
238
stopEvent ( event ) ;
236
239
if ( disabled ) return ;
237
240
@@ -254,7 +257,7 @@ export const DropZone: React.FunctionComponent<DropZoneProps> & {
254
257
) ;
255
258
256
259
const handleDragOver = useCallback (
257
- ( event : DragEvent ) => {
260
+ ( event : DropZoneEvent ) => {
258
261
stopEvent ( event ) ;
259
262
if ( disabled ) return ;
260
263
onDragOver && onDragOver ( ) ;
@@ -263,7 +266,7 @@ export const DropZone: React.FunctionComponent<DropZoneProps> & {
263
266
) ;
264
267
265
268
const handleDragLeave = useCallback (
266
- ( event : DragEvent ) => {
269
+ ( event : DropZoneEvent ) => {
267
270
event . preventDefault ( ) ;
268
271
269
272
if ( disabled ) return ;
@@ -284,38 +287,20 @@ export const DropZone: React.FunctionComponent<DropZoneProps> & {
284
287
[ dropOnPage , disabled , onDragLeave ] ,
285
288
) ;
286
289
287
- useEffect ( ( ) => {
288
- const dropNode = dropOnPage ? document : node . current ;
289
-
290
- if ( ! dropNode ) return ;
291
-
292
- dropNode . addEventListener ( 'drop' , handleDrop ) ;
293
- dropNode . addEventListener ( 'dragover' , handleDragOver ) ;
294
- dropNode . addEventListener ( 'dragenter' , handleDragEnter ) ;
295
- dropNode . addEventListener ( 'dragleave' , handleDragLeave ) ;
296
- window . addEventListener ( 'resize' , adjustSize ) ;
297
-
298
- return ( ) => {
299
- dropNode . removeEventListener ( 'drop' , handleDrop ) ;
300
- dropNode . removeEventListener ( 'dragover' , handleDragOver ) ;
301
- dropNode . removeEventListener ( 'dragenter' , handleDragEnter ) ;
302
- dropNode . removeEventListener ( 'dragleave' , handleDragLeave ) ;
303
- window . removeEventListener ( 'resize' , adjustSize ) ;
304
- } ;
305
- } , [
306
- dropOnPage ,
307
- handleDrop ,
308
- handleDragOver ,
309
- handleDragEnter ,
310
- handleDragLeave ,
311
- adjustSize ,
312
- ] ) ;
290
+ const dropNode = dropOnPage && ! isServer ? document : node . current ;
291
+
292
+ useEventListener ( 'drop' , handleDrop , dropNode ) ;
293
+ useEventListener ( 'dragover' , handleDragOver , dropNode ) ;
294
+ useEventListener ( 'dragenter' , handleDragEnter , dropNode ) ;
295
+ useEventListener ( 'dragleave' , handleDragLeave , dropNode ) ;
296
+ useEventListener ( 'resize' , adjustSize , isServer ? null : window ) ;
313
297
314
298
useComponentDidMount ( ( ) => {
315
299
adjustSize ( ) ;
316
300
} ) ;
317
301
318
302
const id = useUniqueId ( 'DropZone' , idProp ) ;
303
+
319
304
const typeSuffix = capitalize ( type ) ;
320
305
const allowMultipleKey = createAllowMultipleKey ( allowMultiple ) ;
321
306
@@ -336,17 +321,6 @@ export const DropZone: React.FunctionComponent<DropZoneProps> & {
336
321
i18n . translate ( `Polaris.DropZone.${ allowMultipleKey } .label${ typeSuffix } ` ) ;
337
322
const labelHiddenValue = label ? labelHidden : true ;
338
323
339
- const inputAttributes = {
340
- id,
341
- accept,
342
- disabled,
343
- type : 'file' as const ,
344
- multiple : allowMultiple ,
345
- onChange : handleDrop ,
346
- onFocus : handleFocus ,
347
- onBlur : handleBlur ,
348
- } ;
349
-
350
324
const classes = classNames (
351
325
styles . DropZone ,
352
326
outline && styles . hasOutline ,
@@ -382,35 +356,15 @@ export const DropZone: React.FunctionComponent<DropZoneProps> & {
382
356
[ disabled , focused , measuring , size , type , allowMultiple ] ,
383
357
) ;
384
358
385
- return (
386
- < DropZoneContext . Provider value = { context } >
387
- < Labelled
388
- id = { id }
389
- label = { labelValue }
390
- action = { labelAction }
391
- labelHidden = { labelHiddenValue }
392
- >
393
- < div
394
- ref = { node }
395
- className = { classes }
396
- aria-disabled = { disabled }
397
- onClick = { handleClick }
398
- onDragStart = { stopEvent }
399
- >
400
- { dragOverlay }
401
- { dragErrorOverlay }
402
- < Text variant = "bodySm" as = "span" visuallyHidden >
403
- < DropZoneInput
404
- { ...inputAttributes }
405
- openFileDialog = { openFileDialog }
406
- onFileDialogClose = { onFileDialogClose }
407
- />
408
- </ Text >
409
- < div className = { styles . Container } > { children } </ div >
410
- </ div >
411
- </ Labelled >
412
- </ DropZoneContext . Provider >
413
- ) ;
359
+ const open = useCallback ( ( ) => {
360
+ if ( ! inputRef . current ) return ;
361
+ inputRef . current . click ( ) ;
362
+ } , [ inputRef ] ) ;
363
+
364
+ const triggerFileDialog = useCallback ( ( ) => {
365
+ open ( ) ;
366
+ onFileDialogClose ?.( ) ;
367
+ } , [ open , onFileDialogClose ] ) ;
414
368
415
369
function overlayMarkup (
416
370
icon : FunctionComponent ,
@@ -431,68 +385,57 @@ export const DropZone: React.FunctionComponent<DropZoneProps> & {
431
385
) ;
432
386
}
433
387
434
- function open ( ) {
435
- const fileInputNode = node . current && node . current . querySelector ( `#${ id } ` ) ;
436
- fileInputNode &&
437
- fileInputNode instanceof HTMLElement &&
438
- fileInputNode . click ( ) ;
439
- }
440
-
441
388
function handleClick ( event : React . MouseEvent < HTMLElement > ) {
442
389
if ( disabled ) return ;
443
390
444
391
return onClick ? onClick ( event ) : open ( ) ;
445
392
}
393
+
394
+ useEffect ( ( ) => {
395
+ if ( openFileDialog ) triggerFileDialog ( ) ;
396
+ } , [ openFileDialog , triggerFileDialog ] ) ;
397
+
398
+ return (
399
+ < DropZoneContext . Provider value = { context } >
400
+ < Labelled
401
+ id = { id }
402
+ label = { labelValue }
403
+ action = { labelAction }
404
+ labelHidden = { labelHiddenValue }
405
+ >
406
+ < div
407
+ ref = { node }
408
+ className = { classes }
409
+ aria-disabled = { disabled }
410
+ onClick = { handleClick }
411
+ onDragStart = { stopEvent }
412
+ >
413
+ { dragOverlay }
414
+ { dragErrorOverlay }
415
+ < Text variant = "bodySm" as = "span" visuallyHidden >
416
+ < input
417
+ id = { id }
418
+ accept = { accept }
419
+ disabled = { disabled }
420
+ multiple = { allowMultiple }
421
+ onChange = { handleDrop }
422
+ onFocus = { handleFocus }
423
+ onBlur = { handleBlur }
424
+ type = "file"
425
+ ref = { inputRef }
426
+ autoComplete = "off"
427
+ />
428
+ </ Text >
429
+ < div className = { styles . Container } > { children } </ div >
430
+ </ div >
431
+ </ Labelled >
432
+ </ DropZoneContext . Provider >
433
+ ) ;
446
434
} ;
447
435
448
- function stopEvent ( event : DragEvent | React . DragEvent ) {
436
+ function stopEvent ( event : DropZoneEvent | React . DragEvent < HTMLDivElement > ) {
449
437
event . preventDefault ( ) ;
450
438
event . stopPropagation ( ) ;
451
439
}
452
440
453
441
DropZone . FileUpload = FileUpload ;
454
-
455
- interface DropZoneInputProps {
456
- id : string ;
457
- accept ?: string ;
458
- disabled : boolean ;
459
- type : DropZoneFileType ;
460
- multiple : boolean ;
461
- openFileDialog ?: boolean ;
462
- onChange ( event : DragEvent | React . ChangeEvent < HTMLInputElement > ) : void ;
463
- onFocus ( ) : void ;
464
- onBlur ( ) : void ;
465
- onFileDialogClose ?( ) : void ;
466
- }
467
-
468
- // Due to security reasons, browsers do not allow file inputs to be opened artificially.
469
- // For example `useEffect(() => { ref.click() })`. Oddly enough react class-based components bi-pass this.
470
- class DropZoneInput extends Component < DropZoneInputProps , never > {
471
- private fileInputNode = createRef < HTMLInputElement > ( ) ;
472
-
473
- componentDidMount ( ) {
474
- this . props . openFileDialog && this . triggerFileDialog ( ) ;
475
- }
476
-
477
- componentDidUpdate ( ) {
478
- this . props . openFileDialog && this . triggerFileDialog ( ) ;
479
- }
480
-
481
- render ( ) {
482
- const { openFileDialog, onFileDialogClose, ...inputProps } = this . props ;
483
-
484
- return (
485
- < input { ...inputProps } ref = { this . fileInputNode } autoComplete = "off" />
486
- ) ;
487
- }
488
-
489
- private triggerFileDialog = ( ) => {
490
- this . open ( ) ;
491
- this . props . onFileDialogClose && this . props . onFileDialogClose ( ) ;
492
- } ;
493
-
494
- private open = ( ) => {
495
- if ( ! this . fileInputNode . current ) return ;
496
- this . fileInputNode . current . click ( ) ;
497
- } ;
498
- }
0 commit comments