From b40802d7f54397a26572831e38002386543bdfcd Mon Sep 17 00:00:00 2001 From: Eugene Date: Wed, 29 Jan 2025 12:11:13 -0800 Subject: [PATCH] Fix: dropzone doesn't receive drop event (#5415) * Fix: dropzone doesn't get drop events * Improve * Changelog * Add test --- CHANGELOG.md | 2 +- .../html/fluentTheme/dragAndDrop.upload.html | 12 ++++++++ .../src/components/dropZone/DropZone.tsx | 30 +++++++++++++++---- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50f26b434f..ee81497eea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -121,7 +121,7 @@ Notes: web developers are advised to use [`~` (tilde range)](https://github.com/ - Fixed math parsing that could cause Web Chat to hang when processing certain LaTeX expressions, in PR [#5377](https://github.com/microsoft/BotFramework-WebChat/pull/5377), by [@OEvgeny](https://github.com/OEvgeny) - Fixed long math formula should be scrollable, in PR [#5380](https://github.com/microsoft/BotFramework-WebChat/pull/5380), by [@compulim](https://github.com/compulim) - Fixed [#4948](https://github.com/microsoft/BotFramework-WebChat/issues/4948). Microphone should stop after initial silence, in PR [#5385](https://github.com/microsoft/BotFramework-WebChat/pull/5385) -- Fixed [#5390](https://github.com/microsoft/BotFramework-WebChat/issues/5390). Fixed drop zone remaining visible when file is dropped outside of the zone, in PR [#5394](https://github.com/microsoft/BotFramework-WebChat/pull/5394), by [@OEvgeny](https://github.com/OEvgeny) +- Fixed [#5390](https://github.com/microsoft/BotFramework-WebChat/issues/5390). Fixed drop zone remaining visible when file is dropped outside of the zone, in PR [#5394](https://github.com/microsoft/BotFramework-WebChat/pull/5394), in PR [#5415](https://github.com/microsoft/BotFramework-WebChat/pull/5415), by [@OEvgeny](https://github.com/OEvgeny) # Removed diff --git a/__tests__/html/fluentTheme/dragAndDrop.upload.html b/__tests__/html/fluentTheme/dragAndDrop.upload.html index d00310302b..03eff2edcb 100644 --- a/__tests__/html/fluentTheme/dragAndDrop.upload.html +++ b/__tests__/html/fluentTheme/dragAndDrop.upload.html @@ -95,6 +95,18 @@ // THEN: Should render the drop zone. await host.snapshot(); + // WHEN: Dragging a file over the document. + const dragOverDocumentEvent = new DragEvent('dragover', { + bubbles: true, + cancelable: true, + dataTransfer + }); + + document.dispatchEvent(dragOverDocumentEvent); + + // THEN: The default browser behavior should be prevented. + await pageConditions.became('DragOver event preventDefault is called', () => dragOverDocumentEvent.defaultPrevented, 1000); + // WHEN: Dropping out of the drop zone. const dropEvent1 = new DragEvent('drop', { bubbles: true, diff --git a/packages/fluent-theme/src/components/dropZone/DropZone.tsx b/packages/fluent-theme/src/components/dropZone/DropZone.tsx index 31aef8f468..56e2343add 100644 --- a/packages/fluent-theme/src/components/dropZone/DropZone.tsx +++ b/packages/fluent-theme/src/components/dropZone/DropZone.tsx @@ -1,6 +1,14 @@ import { hooks } from 'botframework-webchat-component'; import cx from 'classnames'; -import React, { memo, useCallback, useEffect, useRef, useState, type DragEventHandler } from 'react'; +import React, { + memo, + useCallback, + useEffect, + useRef, + useState, + type DragEvent as ReactDragEvent, + type DragEventHandler +} from 'react'; import { useRefFrom } from 'use-ref-from'; import { AddDocumentIcon } from '../../icons'; @@ -10,8 +18,14 @@ import { useStyles } from '../../styles'; const { useLocalizer } = hooks; -const handleDragOver: DragEventHandler = event => { - // This is for preventing the browser from opening the dropped file in a new tab. +const handleDragOver = (event: ReactDragEvent | DragEvent) => { + // Prevent default dragover behavior to enable drop event triggering. + // Browsers require this to fire subsequent drop events - without it, + // they would handle the drop directly (e.g., open files in new tabs). + // This is needed regardless of whether we prevent default drop behavior, + // as it ensures our dropzone receives the drop event first. If we allow + // default drop handling (by not calling preventDefault there), the browser + // will still process the drop after our event handlers complete. event.preventDefault(); }; @@ -47,6 +61,8 @@ const DropZone = (props: { readonly onFilesAdded: (files: File[]) => void }) => let entranceCounter = 0; const handleDragEnter = (event: DragEvent) => { + document.addEventListener('dragover', handleDragOver); + entranceCounter++; if (isFilesTransferEvent(event)) { @@ -63,7 +79,10 @@ const DropZone = (props: { readonly onFilesAdded: (files: File[]) => void }) => const handleDragLeave = () => --entranceCounter <= 0 && setDropZoneState(false); const handleDragEnd = () => { + document.removeEventListener('dragover', handleDragOver); + entranceCounter = 0; + setDropZoneState(false); }; @@ -73,15 +92,16 @@ const DropZone = (props: { readonly onFilesAdded: (files: File[]) => void }) => } }; + document.addEventListener('dragend', handleDragEnd); document.addEventListener('dragenter', handleDragEnter); document.addEventListener('dragleave', handleDragLeave); - document.addEventListener('dragend', handleDragEnd); document.addEventListener('drop', handleDocumentDrop); return () => { + document.removeEventListener('dragend', handleDragEnd); document.removeEventListener('dragenter', handleDragEnter); document.removeEventListener('dragleave', handleDragLeave); - document.removeEventListener('dragend', handleDragEnd); + document.removeEventListener('dragover', handleDragOver); document.removeEventListener('drop', handleDocumentDrop); }; }, [setDropZoneState]);