🚸 (editor) Improve block dragging behavior
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ export const VariableSearchInput = ({
|
|||||||
useOutsideClick({
|
useOutsideClick({
|
||||||
ref: dropdownRef,
|
ref: dropdownRef,
|
||||||
handler: onClose,
|
handler: onClose,
|
||||||
|
isEnabled: isOpen,
|
||||||
})
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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('{{')
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export const VariablesButton = ({ onSelectVariable, ...props }: Props) => {
|
|||||||
useOutsideClick({
|
useOutsideClick({
|
||||||
ref: popoverRef,
|
ref: popoverRef,
|
||||||
handler: onClose,
|
handler: onClose,
|
||||||
|
isEnabled: isOpen,
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -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])
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user