🐛 (editor) Fix dragging text bubble after editting
This commit is contained in:
@ -101,9 +101,6 @@ export const Graph = ({
|
|||||||
const [groupRects, setGroupRects] = useState<
|
const [groupRects, setGroupRects] = useState<
|
||||||
{ groupId: string; rect: DOMRect }[] | undefined
|
{ groupId: string; rect: DOMRect }[] | undefined
|
||||||
>()
|
>()
|
||||||
const [lastMouseClickPosition, setLastMouseClickPosition] = useState<
|
|
||||||
Coordinates | undefined
|
|
||||||
>()
|
|
||||||
const [isDragging, setIsDragging] = useState(false)
|
const [isDragging, setIsDragging] = useState(false)
|
||||||
|
|
||||||
const graphContainerRef = useRef<HTMLDivElement | null>(null)
|
const graphContainerRef = useRef<HTMLDivElement | null>(null)
|
||||||
@ -167,7 +164,7 @@ export const Graph = ({
|
|||||||
if (isRightClick) e.stopPropagation()
|
if (isRightClick) e.stopPropagation()
|
||||||
}
|
}
|
||||||
|
|
||||||
const handlePointerUp = (e: PointerEvent) => {
|
const handlePointerUp = () => {
|
||||||
if (isDraggingGraph) return
|
if (isDraggingGraph) return
|
||||||
if (
|
if (
|
||||||
!selectBoxCoordinates ||
|
!selectBoxCoordinates ||
|
||||||
@ -176,9 +173,6 @@ export const Graph = ({
|
|||||||
5
|
5
|
||||||
) {
|
) {
|
||||||
blurGroups()
|
blurGroups()
|
||||||
setLastMouseClickPosition(
|
|
||||||
projectMouse({ x: e.clientX, y: e.clientY }, graphPosition)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
setSelectBoxCoordinates(undefined)
|
setSelectBoxCoordinates(undefined)
|
||||||
setOpenedBlockId(undefined)
|
setOpenedBlockId(undefined)
|
||||||
@ -355,10 +349,9 @@ export const Graph = ({
|
|||||||
{selectBoxCoordinates && <SelectBox {...selectBoxCoordinates} />}
|
{selectBoxCoordinates && <SelectBox {...selectBoxCoordinates} />}
|
||||||
<Fade in={!isReadOnly && focusedGroups.length > 1}>
|
<Fade in={!isReadOnly && focusedGroups.length > 1}>
|
||||||
<GroupSelectionMenu
|
<GroupSelectionMenu
|
||||||
lastMouseClickPosition={lastMouseClickPosition}
|
graphPosition={graphPosition}
|
||||||
focusedGroups={focusedGroups}
|
focusedGroups={focusedGroups}
|
||||||
blurGroups={blurGroups}
|
blurGroups={blurGroups}
|
||||||
graphPosition={graphPosition}
|
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
/>
|
/>
|
||||||
</Fade>
|
</Fade>
|
||||||
|
@ -9,30 +9,29 @@ import {
|
|||||||
useColorModeValue,
|
useColorModeValue,
|
||||||
useEventListener,
|
useEventListener,
|
||||||
} from '@chakra-ui/react'
|
} from '@chakra-ui/react'
|
||||||
import { useRef } from 'react'
|
import { useRef, useState } from 'react'
|
||||||
import { useGroupsStore } from '../hooks/useGroupsStore'
|
import { useGroupsStore } from '../hooks/useGroupsStore'
|
||||||
import { toast } from 'sonner'
|
import { toast } from 'sonner'
|
||||||
import { createId } from '@paralleldrive/cuid2'
|
import { createId } from '@paralleldrive/cuid2'
|
||||||
import { Edge, GroupV6 } from '@typebot.io/schemas'
|
import { Edge, GroupV6 } from '@typebot.io/schemas'
|
||||||
import { projectMouse } from '../helpers/projectMouse'
|
|
||||||
import { Coordinates } from '../types'
|
import { Coordinates } from '../types'
|
||||||
import { useShallow } from 'zustand/react/shallow'
|
import { useShallow } from 'zustand/react/shallow'
|
||||||
|
import { projectMouse } from '../helpers/projectMouse'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
graphPosition: Coordinates & { scale: number }
|
graphPosition: Coordinates & { scale: number }
|
||||||
isReadOnly: boolean
|
isReadOnly: boolean
|
||||||
lastMouseClickPosition: Coordinates | undefined
|
|
||||||
focusedGroups: string[]
|
focusedGroups: string[]
|
||||||
blurGroups: () => void
|
blurGroups: () => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GroupSelectionMenu = ({
|
export const GroupSelectionMenu = ({
|
||||||
graphPosition,
|
graphPosition,
|
||||||
lastMouseClickPosition,
|
|
||||||
isReadOnly,
|
isReadOnly,
|
||||||
focusedGroups,
|
focusedGroups,
|
||||||
blurGroups,
|
blurGroups,
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
|
const [mousePosition, setMousePosition] = useState<Coordinates>()
|
||||||
const { typebot, deleteGroups, pasteGroups } = useTypebot()
|
const { typebot, deleteGroups, pasteGroups } = useTypebot()
|
||||||
const ref = useRef<HTMLDivElement>(null)
|
const ref = useRef<HTMLDivElement>(null)
|
||||||
|
|
||||||
@ -50,6 +49,13 @@ export const GroupSelectionMenu = ({
|
|||||||
|
|
||||||
useEventListener('pointerup', (e) => e.stopPropagation(), ref.current)
|
useEventListener('pointerup', (e) => e.stopPropagation(), ref.current)
|
||||||
|
|
||||||
|
useEventListener('mousemove', (e) => {
|
||||||
|
setMousePosition({
|
||||||
|
x: e.clientX,
|
||||||
|
y: e.clientY,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const handleCopy = () => {
|
const handleCopy = () => {
|
||||||
if (!typebot) return
|
if (!typebot) return
|
||||||
const groups = typebot.groups.filter((g) => focusedGroups.includes(g.id))
|
const groups = typebot.groups.filter((g) => focusedGroups.includes(g.id))
|
||||||
@ -72,18 +78,11 @@ export const GroupSelectionMenu = ({
|
|||||||
groups: GroupV6[]
|
groups: GroupV6[]
|
||||||
edges: Edge[]
|
edges: Edge[]
|
||||||
}) => {
|
}) => {
|
||||||
if (!groupsInClipboard || isReadOnly) return
|
if (!groupsInClipboard || isReadOnly || !mousePosition) return
|
||||||
const clipboard = overrideClipBoard ?? groupsInClipboard
|
const clipboard = overrideClipBoard ?? groupsInClipboard
|
||||||
const { groups, oldToNewIdsMapping } = parseGroupsToPaste(
|
const { groups, oldToNewIdsMapping } = parseGroupsToPaste(
|
||||||
clipboard.groups,
|
clipboard.groups,
|
||||||
lastMouseClickPosition ??
|
projectMouse(mousePosition, graphPosition)
|
||||||
projectMouse(
|
|
||||||
{
|
|
||||||
x: window.innerWidth / 2,
|
|
||||||
y: window.innerHeight / 2,
|
|
||||||
},
|
|
||||||
graphPosition
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
groups.forEach((group) => {
|
groups.forEach((group) => {
|
||||||
updateGroupCoordinates(group.id, group.graphCoordinates)
|
updateGroupCoordinates(group.id, group.graphCoordinates)
|
||||||
|
@ -101,6 +101,7 @@ export const BlockNode = ({
|
|||||||
ref: blockRef,
|
ref: blockRef,
|
||||||
onDrag,
|
onDrag,
|
||||||
isDisabled: !onMouseDown,
|
isDisabled: !onMouseDown,
|
||||||
|
deps: [isEditing],
|
||||||
})
|
})
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
@ -105,11 +105,13 @@ export const useDragDistance = ({
|
|||||||
onDrag,
|
onDrag,
|
||||||
distanceTolerance = 20,
|
distanceTolerance = 20,
|
||||||
isDisabled = false,
|
isDisabled = false,
|
||||||
|
deps = [],
|
||||||
}: {
|
}: {
|
||||||
ref: React.MutableRefObject<HTMLDivElement | null>
|
ref: React.MutableRefObject<HTMLDivElement | null>
|
||||||
onDrag: (position: { absolute: Coordinates; relative: Coordinates }) => void
|
onDrag: (position: { absolute: Coordinates; relative: Coordinates }) => void
|
||||||
distanceTolerance?: number
|
distanceTolerance?: number
|
||||||
isDisabled: boolean
|
isDisabled: boolean
|
||||||
|
deps?: unknown[]
|
||||||
}) => {
|
}) => {
|
||||||
const mouseDownPosition = useRef<{
|
const mouseDownPosition = useRef<{
|
||||||
absolute: Coordinates
|
absolute: Coordinates
|
||||||
@ -119,7 +121,7 @@ export const useDragDistance = ({
|
|||||||
const handleMouseUp = () => {
|
const handleMouseUp = () => {
|
||||||
if (mouseDownPosition) mouseDownPosition.current = undefined
|
if (mouseDownPosition) mouseDownPosition.current = undefined
|
||||||
}
|
}
|
||||||
useEventListener('mouseup', handleMouseUp)
|
useEventListener('mouseup', handleMouseUp, undefined, undefined, deps)
|
||||||
|
|
||||||
const handleMouseDown = (e: MouseEvent) => {
|
const handleMouseDown = (e: MouseEvent) => {
|
||||||
if (isDisabled || !ref.current) return
|
if (isDisabled || !ref.current) return
|
||||||
@ -133,7 +135,7 @@ export const useDragDistance = ({
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
useEventListener('mousedown', handleMouseDown, ref)
|
useEventListener('mousedown', handleMouseDown, ref, undefined, deps)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let triggered = false
|
let triggered = false
|
||||||
|
@ -9,7 +9,8 @@ function useEventListener<K extends keyof MediaQueryListEventMap>(
|
|||||||
eventName: K,
|
eventName: K,
|
||||||
handler: (event: MediaQueryListEventMap[K]) => void,
|
handler: (event: MediaQueryListEventMap[K]) => void,
|
||||||
element: RefObject<MediaQueryList>,
|
element: RefObject<MediaQueryList>,
|
||||||
options?: Options
|
options?: Options,
|
||||||
|
deps?: unknown[]
|
||||||
): void
|
): void
|
||||||
|
|
||||||
// Window Event based useEventListener interface
|
// Window Event based useEventListener interface
|
||||||
@ -17,7 +18,8 @@ function useEventListener<K extends keyof WindowEventMap>(
|
|||||||
eventName: K,
|
eventName: K,
|
||||||
handler: (event: WindowEventMap[K]) => void,
|
handler: (event: WindowEventMap[K]) => void,
|
||||||
element?: undefined,
|
element?: undefined,
|
||||||
options?: Options
|
options?: Options,
|
||||||
|
deps?: unknown[]
|
||||||
): void
|
): void
|
||||||
|
|
||||||
// Element Event based useEventListener interface
|
// Element Event based useEventListener interface
|
||||||
@ -28,7 +30,8 @@ function useEventListener<
|
|||||||
eventName: K,
|
eventName: K,
|
||||||
handler: (event: HTMLElementEventMap[K]) => void,
|
handler: (event: HTMLElementEventMap[K]) => void,
|
||||||
element: RefObject<T>,
|
element: RefObject<T>,
|
||||||
options?: Options
|
options?: Options,
|
||||||
|
deps?: unknown[]
|
||||||
): void
|
): void
|
||||||
|
|
||||||
// Document Event based useEventListener interface
|
// Document Event based useEventListener interface
|
||||||
@ -36,7 +39,8 @@ function useEventListener<K extends keyof DocumentEventMap>(
|
|||||||
eventName: K,
|
eventName: K,
|
||||||
handler: (event: DocumentEventMap[K]) => void,
|
handler: (event: DocumentEventMap[K]) => void,
|
||||||
element: RefObject<Document>,
|
element: RefObject<Document>,
|
||||||
options?: Options
|
options?: Options,
|
||||||
|
deps?: unknown[]
|
||||||
): void
|
): void
|
||||||
|
|
||||||
function useEventListener<
|
function useEventListener<
|
||||||
@ -54,7 +58,8 @@ function useEventListener<
|
|||||||
| Event
|
| Event
|
||||||
) => void,
|
) => void,
|
||||||
element?: RefObject<T>,
|
element?: RefObject<T>,
|
||||||
options?: Options
|
options?: Options,
|
||||||
|
deps: unknown[] = []
|
||||||
) {
|
) {
|
||||||
// Create a ref that stores handler
|
// Create a ref that stores handler
|
||||||
const savedHandler = useRef(handler)
|
const savedHandler = useRef(handler)
|
||||||
@ -72,7 +77,12 @@ function useEventListener<
|
|||||||
if (!(targetElement && targetElement.addEventListener)) return
|
if (!(targetElement && targetElement.addEventListener)) return
|
||||||
|
|
||||||
if (options && typeof options !== 'boolean')
|
if (options && typeof options !== 'boolean')
|
||||||
console.log('Add event listener', { eventName, element, options })
|
console.log('Add event listener', {
|
||||||
|
elementText: (targetElement as HTMLElement).textContent,
|
||||||
|
eventName,
|
||||||
|
element: targetElement,
|
||||||
|
options,
|
||||||
|
})
|
||||||
// Create event listener that calls handler function stored in ref
|
// Create event listener that calls handler function stored in ref
|
||||||
const listener: typeof handler = (event) => savedHandler.current(event)
|
const listener: typeof handler = (event) => savedHandler.current(event)
|
||||||
|
|
||||||
@ -81,10 +91,16 @@ function useEventListener<
|
|||||||
// Remove event listener on cleanup
|
// Remove event listener on cleanup
|
||||||
return () => {
|
return () => {
|
||||||
if (options && typeof options !== 'boolean')
|
if (options && typeof options !== 'boolean')
|
||||||
console.log('Remove event listener')
|
console.log('Remove event listener', {
|
||||||
|
elementText: (targetElement as HTMLElement).textContent,
|
||||||
|
eventName,
|
||||||
|
element: targetElement,
|
||||||
|
options,
|
||||||
|
})
|
||||||
targetElement.removeEventListener(eventName, listener, options)
|
targetElement.removeEventListener(eventName, listener, options)
|
||||||
}
|
}
|
||||||
}, [eventName, element, options])
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [eventName, ...deps])
|
||||||
}
|
}
|
||||||
|
|
||||||
export { useEventListener }
|
export { useEventListener }
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { isNotEmpty } from '@typebot.io/lib'
|
||||||
import { useEventListener } from './useEventListener'
|
import { useEventListener } from './useEventListener'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@ -40,6 +41,10 @@ export const useKeyboardShortcuts = ({
|
|||||||
event.key === 'Backspace'
|
event.key === 'Backspace'
|
||||||
|
|
||||||
useEventListener('keydown', (event: KeyboardEvent) => {
|
useEventListener('keydown', (event: KeyboardEvent) => {
|
||||||
|
if (!event.metaKey && !event.ctrlKey && event.key !== 'Backspace') return
|
||||||
|
// get text selection
|
||||||
|
const textSelection = window.getSelection()?.toString()
|
||||||
|
if (isNotEmpty(textSelection)) return
|
||||||
const target = event.target as HTMLElement | null
|
const target = event.target as HTMLElement | null
|
||||||
const isTyping =
|
const isTyping =
|
||||||
target?.role === 'textbox' ||
|
target?.role === 'textbox' ||
|
||||||
|
Reference in New Issue
Block a user