2
0

🚸 (editor) Improve block dragging behavior

This commit is contained in:
Baptiste Arnaud
2023-03-24 15:53:06 +01:00
parent e1de63a405
commit 92b92ed268
10 changed files with 56 additions and 46 deletions

View File

@@ -87,6 +87,7 @@ export const AutocompleteInput = ({
useOutsideClick({ useOutsideClick({
ref: dropdownRef, ref: dropdownRef,
handler: onClose, handler: onClose,
isEnabled: isOpen,
}) })
useEffect( useEffect(
@@ -110,7 +111,9 @@ export const AutocompleteInput = ({
inputRef.current?.focus() inputRef.current?.focus()
} }
const handleKeyUp = (e: React.KeyboardEvent<HTMLInputElement>) => { const updateFocusedDropdownItem = (
e: React.KeyboardEvent<HTMLInputElement>
) => {
if (e.key === 'Enter' && isDefined(keyboardFocusIndex)) { if (e.key === 'Enter' && isDefined(keyboardFocusIndex)) {
handleItemClick(filteredItems[keyboardFocusIndex])() handleItemClick(filteredItems[keyboardFocusIndex])()
return setKeyboardFocusIndex(undefined) return setKeyboardFocusIndex(undefined)
@@ -179,7 +182,7 @@ export const AutocompleteInput = ({
onChange={(e) => changeValue(e.target.value)} onChange={(e) => changeValue(e.target.value)}
onFocus={onOpen} onFocus={onOpen}
onBlur={updateCarretPosition} onBlur={updateCarretPosition}
onKeyDown={handleKeyUp} onKeyDown={updateFocusedDropdownItem}
placeholder={placeholder} placeholder={placeholder}
/> />
</PopoverAnchor> </PopoverAnchor>

View File

@@ -61,10 +61,6 @@ export const Select = <T extends Item>({
) )
) )
const closeDropwdown = () => {
onClose()
}
const [keyboardFocusIndex, setKeyboardFocusIndex] = useState< const [keyboardFocusIndex, setKeyboardFocusIndex] = useState<
number | undefined number | undefined
>() >()
@@ -85,12 +81,20 @@ export const Select = <T extends Item>({
: items : items
).slice(0, 50) ).slice(0, 50)
const closeDropdown = () => {
onClose()
setTimeout(() => {
setIsTouched(false)
}, dropdownCloseAnimationDuration)
}
useOutsideClick({ useOutsideClick({
ref: dropdownRef, ref: dropdownRef,
handler: closeDropwdown, handler: closeDropdown,
isEnabled: isOpen,
}) })
const handleInputChange = (e: ChangeEvent<HTMLInputElement>) => { const updateInputValue = (e: ChangeEvent<HTMLInputElement>) => {
if (!isOpen) onOpen() if (!isOpen) onOpen()
if (!isTouched) setIsTouched(true) if (!isTouched) setIsTouched(true)
setInputValue(e.target.value) setInputValue(e.target.value)
@@ -101,10 +105,12 @@ export const Select = <T extends Item>({
setInputValue(getItemLabel(item)) setInputValue(getItemLabel(item))
onSelect?.(getItemValue(item), item) onSelect?.(getItemValue(item), item)
setKeyboardFocusIndex(undefined) setKeyboardFocusIndex(undefined)
closeDropwdown() closeDropdown()
} }
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { const updateFocusedDropdownItem = (
e: React.KeyboardEvent<HTMLInputElement>
) => {
if (e.key === 'Enter' && isDefined(keyboardFocusIndex)) { if (e.key === 'Enter' && isDefined(keyboardFocusIndex)) {
e.preventDefault() e.preventDefault()
handleItemClick(filteredItems[keyboardFocusIndex])() handleItemClick(filteredItems[keyboardFocusIndex])()
@@ -138,13 +144,7 @@ export const Select = <T extends Item>({
setInputValue('') setInputValue('')
onSelect?.(undefined) onSelect?.(undefined)
setKeyboardFocusIndex(undefined) setKeyboardFocusIndex(undefined)
closeDropwdown() closeDropdown()
}
const resetIsTouched = () => {
setTimeout(() => {
setIsTouched(false)
}, dropdownCloseAnimationDuration)
} }
return ( return (
@@ -183,10 +183,9 @@ export const Select = <T extends Item>({
placeholder={ placeholder={
!isTouched && inputValue !== '' ? undefined : placeholder !isTouched && inputValue !== '' ? undefined : placeholder
} }
onBlur={resetIsTouched} onChange={updateInputValue}
onChange={handleInputChange}
onFocus={onOpen} onFocus={onOpen}
onKeyDown={handleKeyDown} onKeyDown={updateFocusedDropdownItem}
pr={selectedItem ? 16 : undefined} pr={selectedItem ? 16 : undefined}
/> />

View File

@@ -60,6 +60,7 @@ export const VariableSearchInput = ({
useOutsideClick({ useOutsideClick({
ref: dropdownRef, ref: dropdownRef,
handler: onClose, handler: onClose,
isEnabled: isOpen,
}) })
useEffect(() => { useEffect(() => {

View File

@@ -9,6 +9,7 @@ export const ImageBubbleContent = ({ block }: { block: ImageBubbleBlock }) => {
) : ( ) : (
<Box w="full"> <Box w="full">
<Image <Image
pointerEvents="none"
src={ src={
containsVariables ? '/images/dynamic-image.png' : block.content?.url containsVariables ? '/images/dynamic-image.png' : block.content?.url
} }

View File

@@ -1,9 +1,4 @@
import { import { Flex, Stack, useColorModeValue } from '@chakra-ui/react'
Flex,
Stack,
useColorModeValue,
useEventListener,
} from '@chakra-ui/react'
import React, { useEffect, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import { Plate, PlateProvider, usePlateEditorRef } from '@udecode/plate-core' import { Plate, PlateProvider, usePlateEditorRef } from '@udecode/plate-core'
import { editorStyle, platePlugins } from '@/lib/plate' import { editorStyle, platePlugins } from '@/lib/plate'
@@ -84,14 +79,6 @@ const TextBubbleEditorContent = ({
} }
} }
useEventListener(
'pointerdown',
(e) => {
e.stopPropagation()
},
textEditorRef.current
)
const handleVariableSelected = (variable?: Variable) => { const handleVariableSelected = (variable?: Variable) => {
setIsVariableDropdownOpen(false) setIsVariableDropdownOpen(false)
if (!rememberedSelection.current || !variable) return if (!rememberedSelection.current || !variable) return
@@ -115,6 +102,7 @@ const TextBubbleEditorContent = ({
pos="relative" pos="relative"
spacing={0} spacing={0}
cursor="text" cursor="text"
className="prevent-group-drag"
sx={{ sx={{
'.slate-ToolbarButton-active': { '.slate-ToolbarButton-active': {
color: useColorModeValue('blue.500', 'blue.300') + ' !important', color: useColorModeValue('blue.500', 'blue.300') + ' !important',

View File

@@ -204,6 +204,7 @@ export const BlockNode = ({
onClick={handleClick} onClick={handleClick}
data-testid={`block`} data-testid={`block`}
w="full" w="full"
className="prevent-group-drag"
> >
<HStack <HStack
flex="1" flex="1"

View File

@@ -96,6 +96,7 @@ const NonMemoizedDraggableGroupNode = ({
handler: () => setIsFocused(false), handler: () => setIsFocused(false),
ref: groupRef, ref: groupRef,
capture: true, capture: true,
isEnabled: isFocused,
}) })
// When the group is moved from external action (e.g. undo/redo), update the current coordinates // When the group is moved from external action (e.g. undo/redo), update the current coordinates
@@ -154,8 +155,13 @@ const NonMemoizedDraggableGroupNode = ({
useDrag( useDrag(
({ first, last, offset: [offsetX, offsetY], event, target }) => { ({ first, last, offset: [offsetX, offsetY], event, target }) => {
event.stopPropagation() event.stopPropagation()
if ((target as HTMLElement).classList.contains('prevent-group-drag')) if (
(target as HTMLElement)
.closest('.prevent-group-drag')
?.classList.contains('prevent-group-drag')
)
return return
if (first) { if (first) {
setIsFocused(true) setIsFocused(true)
setIsMouseDown(true) setIsMouseDown(true)

View File

@@ -44,6 +44,7 @@ export const AvatarForm = ({
useOutsideClick({ useOutsideClick({
ref: popoverContainerRef, ref: popoverContainerRef,
handler: onClose, handler: onClose,
isEnabled: isOpen,
}) })
const isDefaultAvatar = !avatarProps?.url || avatarProps.url.includes('{{') const isDefaultAvatar = !avatarProps?.url || avatarProps.url.includes('{{')

View File

@@ -28,6 +28,7 @@ export const VariablesButton = ({ onSelectVariable, ...props }: Props) => {
useOutsideClick({ useOutsideClick({
ref: popoverRef, ref: popoverRef,
handler: onClose, handler: onClose,
isEnabled: isOpen,
}) })
return ( return (

View File

@@ -1,5 +1,4 @@
import { useEventListener } from '@chakra-ui/react' import { RefObject, useEffect } from 'react'
import { RefObject } from 'react'
type Handler = (event: MouseEvent) => void type Handler = (event: MouseEvent) => void
@@ -7,22 +6,32 @@ type Props<T> = {
ref: RefObject<T> ref: RefObject<T>
handler: Handler handler: Handler
capture?: boolean capture?: boolean
isEnabled?: boolean
} }
export const useOutsideClick = <T extends HTMLElement = HTMLElement>({ export const useOutsideClick = <T extends HTMLElement = HTMLElement>({
ref, ref,
handler, handler,
capture, capture,
isEnabled,
}: Props<T>): void => { }: Props<T>): void => {
const triggerHandlerIfOutside = (event: MouseEvent) => { useEffect(() => {
const el = ref?.current if (isEnabled === false) return
if (!el || el.contains(event.target as Node)) { const triggerHandlerIfOutside = (event: MouseEvent) => {
return const el = ref?.current
if (!el || el.contains(event.target as Node)) {
return
}
handler(event)
} }
handler(event)
}
useEventListener('pointerdown', triggerHandlerIfOutside, null, { document.addEventListener('pointerdown', triggerHandlerIfOutside, {
capture, capture,
}) })
return () => {
document.removeEventListener('pointerdown', triggerHandlerIfOutside, {
capture,
})
}
}, [capture, handler, isEnabled, ref])
} }