diff --git a/services/web/frontend/js/features/share-project-modal/components/add-collaborators.jsx b/services/web/frontend/js/features/share-project-modal/components/add-collaborators.jsx
index 822d2cf6f3..e53f7b33eb 100644
--- a/services/web/frontend/js/features/share-project-modal/components/add-collaborators.jsx
+++ b/services/web/frontend/js/features/share-project-modal/components/add-collaborators.jsx
@@ -1,4 +1,4 @@
-import { useState, useMemo } from 'react'
+import { useState, useMemo, useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { Form, FormGroup, FormControl, Button } from 'react-bootstrap'
import { useMultipleSelection } from 'downshift'
@@ -9,6 +9,7 @@ import { useUserContacts } from '../hooks/use-user-contacts'
import useIsMounted from '../../../shared/hooks/use-is-mounted'
import { useProjectContext } from '../../../shared/context/project-context'
import { sendMB } from '../../../infrastructure/event-tracking'
+import ClickableElementEnhancer from '@/shared/components/clickable-element-enhancer'
export default function AddCollaborators() {
const [privileges, setPrivileges] = useState('readAndWrite')
@@ -45,9 +46,7 @@ export default function AddCollaborators() {
const { reset, selectedItems } = multipleSelectionProps
- async function handleSubmit(event) {
- event.preventDefault()
-
+ const handleSubmit = useCallback(async () => {
if (!selectedItems.length) {
return
}
@@ -125,10 +124,22 @@ export default function AddCollaborators() {
}
setInFlight(false)
- }
+ }, [
+ currentMemberEmails,
+ invites,
+ isMounted,
+ members,
+ privileges,
+ projectId,
+ reset,
+ selectedItems,
+ setError,
+ setInFlight,
+ updateProject,
+ ])
return (
-
diff --git a/services/web/frontend/js/features/share-project-modal/components/share-project-modal-content.tsx b/services/web/frontend/js/features/share-project-modal/components/share-project-modal-content.tsx
index 77d173b317..ec89b2dd1f 100644
--- a/services/web/frontend/js/features/share-project-modal/components/share-project-modal-content.tsx
+++ b/services/web/frontend/js/features/share-project-modal/components/share-project-modal-content.tsx
@@ -5,6 +5,7 @@ import AccessibleModal from '../../../shared/components/accessible-modal'
import { useEditorContext } from '../../../shared/context/editor-context'
import { lazy, Suspense } from 'react'
import { FullSizeLoadingSpinner } from '@/shared/components/loading-spinner'
+import ClickableElementEnhancer from '@/shared/components/clickable-element-enhancer'
const ReadOnlyTokenLink = lazy(() =>
import('./link-sharing').then(({ ReadOnlyTokenLink }) => ({
@@ -63,15 +64,16 @@ export default function ShareProjectModalContent({
-
+
diff --git a/services/web/frontend/js/shared/components/clickable-element-enhancer.tsx b/services/web/frontend/js/shared/components/clickable-element-enhancer.tsx
new file mode 100644
index 0000000000..b028db07fd
--- /dev/null
+++ b/services/web/frontend/js/shared/components/clickable-element-enhancer.tsx
@@ -0,0 +1,75 @@
+import { useRef, useEffect } from 'react'
+import PolymorphicComponent, {
+ PolymorphicComponentProps,
+} from '@/shared/components/polymorphic-component'
+import { MergeAndOverride } from '../../../../types/utils'
+
+// Performs a click event on elements that has been clicked,
+// but when releasing the mouse button are no longer hovered
+// by the cursor (which by default cancels the event).
+
+type ClickableElementEnhancerOwnProps = {
+ onClick: () => void
+ onMouseDown?: (e: React.MouseEvent) => void
+ offset?: number
+}
+
+type ClickableElementEnhancerProps =
+ MergeAndOverride<
+ PolymorphicComponentProps,
+ ClickableElementEnhancerOwnProps
+ >
+
+function ClickableElementEnhancer({
+ onClick,
+ onMouseDown,
+ offset = 50, // the offset around the clicked element which should still trigger the click
+ ...rest
+}: ClickableElementEnhancerProps) {
+ const isClickedRef = useRef(false)
+ const elRectRef = useRef()
+ const restProps = rest as PolymorphicComponentProps
+
+ const handleMouseDown = (e: React.MouseEvent) => {
+ isClickedRef.current = true
+ elRectRef.current = (e.target as HTMLElement).getBoundingClientRect()
+ onMouseDown?.(e)
+ }
+
+ useEffect(() => {
+ const handleMouseUp = (e: MouseEvent) => {
+ if (isClickedRef.current) {
+ isClickedRef.current = false
+
+ if (!elRectRef.current) {
+ return
+ }
+
+ const halfWidth = elRectRef.current.width / 2
+ const halfHeight = elRectRef.current.height / 2
+
+ const centerX = elRectRef.current.x + halfWidth
+ const centerY = elRectRef.current.y + halfHeight
+
+ const deltaX = Math.abs(e.clientX - centerX)
+ const deltaY = Math.abs(e.clientY - centerY)
+
+ // Check if the mouse has moved significantly from the element position
+ if (deltaX < halfWidth + offset && deltaY < halfHeight + offset) {
+ // If the mouse hasn't moved much, consider it a click
+ onClick()
+ }
+ }
+ }
+
+ document.addEventListener('mouseup', handleMouseUp)
+
+ return () => {
+ document.removeEventListener('mouseup', handleMouseUp)
+ }
+ }, [onClick, offset])
+
+ return
+}
+
+export default ClickableElementEnhancer
diff --git a/services/web/frontend/js/shared/components/polymorphic-component.tsx b/services/web/frontend/js/shared/components/polymorphic-component.tsx
new file mode 100644
index 0000000000..44cbb0ea31
--- /dev/null
+++ b/services/web/frontend/js/shared/components/polymorphic-component.tsx
@@ -0,0 +1,19 @@
+import { MergeAndOverride } from '../../../../types/utils'
+
+type PolymorphicComponentOwnProps = {
+ as?: E
+}
+
+export type PolymorphicComponentProps =
+ MergeAndOverride, PolymorphicComponentOwnProps>
+
+function PolymorphicComponent({
+ as,
+ ...props
+}: PolymorphicComponentProps) {
+ const Component = as || 'div'
+
+ return
+}
+
+export default PolymorphicComponent
diff --git a/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx b/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx
index 761bcfe905..c41efdcc52 100644
--- a/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx
+++ b/services/web/test/frontend/features/share-project-modal/components/share-project-modal.test.jsx
@@ -124,8 +124,8 @@ describe('', function () {
{ name: 'Close' }
)
- fireEvent.click(headerCloseButton)
- fireEvent.click(footerCloseButton)
+ await userEvent.click(headerCloseButton)
+ await userEvent.click(footerCloseButton)
expect(handleHide.callCount).to.equal(2)
})
@@ -620,7 +620,7 @@ describe('', function () {
fireEvent.change(privilegesElement, { target: { value: 'readOnly' } })
const submitButton = screen.getByRole('button', { name: 'Share' })
- submitButton.click()
+ await userEvent.click(submitButton)
let calls
await waitFor(
@@ -713,7 +713,7 @@ describe('', function () {
)
expect(submitButton.disabled).to.be.false
- submitButton.click()
+ await userEvent.click(submitButton)
await fetchMock.flush(true)
expect(fetchMock.done()).to.be.true
}