Skip to content

Commit

Permalink
fix hover highlight and arrow render when canDrop is false
Browse files Browse the repository at this point in the history
  • Loading branch information
Zubair286 committed Oct 25, 2024
1 parent 25c6ba9 commit 0ae9219
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 32 deletions.
28 changes: 16 additions & 12 deletions src/components/LayoutTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -372,20 +372,23 @@ const LayoutTree = () => {
)
const scrollTop = scrollTopStore.useState()

const [bulletWidth, setBulletWidth] = useState<number | undefined>(0)
const toolbarHeightRef = useRef(0)
const distanceFromTop = useRef<number | undefined>(0)
// Width of thought bullet
const [bulletWidth, setBulletWidth] = useState(0)
// Height of toolbar element
const [toolbarHeight, setToolbarHeight] = useState(0)
// Distance from toolbar to the first visible thought
const [distanceFromTop, setDistanceFromTop] = useState(0)

// set the bullet width only during drag or when simulateDrop is true
useLayoutEffect(() => {
if (dragInProgress || testFlags.simulateDrop) {
const bullet = ref.current?.querySelector('[aria-label=bullet]')
setBulletWidth(bullet?.getBoundingClientRect().width)
if (bullet) setBulletWidth(bullet?.getBoundingClientRect().width)

const toolbar = document.querySelector('#toolbar')
if (toolbar) setToolbarHeight(toolbar.getBoundingClientRect().height)

if (toolbar) toolbarHeightRef.current = toolbar?.getBoundingClientRect().height
distanceFromTop.current = (ref.current?.getBoundingClientRect().top ?? 0) + scrollTop
setDistanceFromTop((ref.current?.getBoundingClientRect().top ?? 0) + scrollTop)
}
}, [dragInProgress, scrollTop])

Expand Down Expand Up @@ -471,7 +474,7 @@ const LayoutTree = () => {
const { footerHeight, navbarHeight } = useNavAndFooterHeight()
const navAndFooterHeight = navbarHeight + footerHeight

const maxVisibleY = viewportHeight + scrollTop - ((distanceFromTop.current ?? 0) + navbarHeight)
const maxVisibleY = viewportHeight + scrollTop - (distanceFromTop + navbarHeight)

const { isSortedContext, newRank, hoveringOnDropEnd } = useSortedContext()

Expand Down Expand Up @@ -504,7 +507,7 @@ const LayoutTree = () => {
let yaccum = 0
let indentCursorAncestorTables = 0

// Arrow visibility based on insertionY
// Arrow visibility based on the rank of drop target in sorted context.
let hoverArrowVisibility: 'above' | 'below' | null = null

// The rank of the first and last thoughts in sorted context.
Expand Down Expand Up @@ -626,15 +629,15 @@ const LayoutTree = () => {

if (isInSortedContext) {
const currentThought = getThoughtById(state, head(node.path))
// Update first and last thought ranks in sorted context
// Get first and last thought ranks in sorted context
if (!firstThoughtRank) {
firstThoughtRank = currentThought.rank
}
lastThoughtRank = currentThought.rank

// Check if the current thought is visible
if (y < maxVisibleY && y > scrollTop - (toolbarHeightRef.current ?? 0)) {
// Update first and last visible thought ranks in sorted context
if (y < maxVisibleY && y > scrollTop - toolbarHeight) {
// Get first and last visible thought ranks in sorted context
if (!firstVisibleThoughtRank) {
firstVisibleThoughtRank = currentThought.rank
}
Expand All @@ -655,7 +658,7 @@ const LayoutTree = () => {
})

// Determine hoverArrowVisibility based on newRank and the visible thoughts
if (isSortedContext || hoveringOnDropEnd) {
if (newRank > 0 && (isSortedContext || hoveringOnDropEnd)) {
if (newRank > lastVisibleThoughtRank && lastVisibleThoughtRank !== lastThoughtRank) {
hoverArrowVisibility = 'below'
} else if (newRank < firstVisibleThoughtRank && firstVisibleThoughtRank !== firstThoughtRank) {
Expand All @@ -675,6 +678,7 @@ const LayoutTree = () => {
sizes,
maxVisibleY,
scrollTop,
toolbarHeight,
newRank,
])

Expand Down
15 changes: 8 additions & 7 deletions src/components/Thought.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,15 +162,16 @@ const ThoughtContainer = ({
equalPath(cursorParent, path)
})

const { isDragging, dragSource, isHovering, isBeingHoveredOver, dropTarget, isDeepHovering } = useDragAndDropThought({
path,
simplePath,
isVisible,
isCursorParent,
})
const { isDragging, dragSource, isHovering, isBeingHoveredOver, dropTarget, canDropThought, isDeepHovering } =
useDragAndDropThought({
path,
simplePath,
isVisible,
isCursorParent,
})

useHoveringPath(path, isBeingHoveredOver, DropThoughtZone.ThoughtDrop)
useDragLeave({ isDeepHovering })
useDragLeave({ isDeepHovering, canDropThought })

// check if the cursor is editing a thought directly
const isEditing = useSelector(state => equalPath(state.cursor, path))
Expand Down
1 change: 1 addition & 0 deletions src/durations.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const durationsMillis = {
layoutNodeAnimationDuration: 150,
/** The time it takes the trace TraceGesture to fade in/out. */
traceOpacityDuration: 150,
arrowBobbleAnimation: 1000,
} as const

export default durationsMillis
14 changes: 12 additions & 2 deletions src/hooks/useDragAndDropThought.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,7 @@ const dropCollect = (monitor: DropTargetMonitor) => ({
// is being hovered over current thought irrespective of whether the given item is droppable
isBeingHoveredOver: monitor.isOver({ shallow: true }),
isDeepHovering: monitor.isOver(),
canDropThought: monitor.canDrop(),
})

/** A draggable and droppable Thought hook. */
Expand All @@ -221,14 +222,23 @@ const useDragAndDropThought = (props: Partial<ThoughtContainerProps>) => {
collect: dragCollect,
})

const [{ isHovering, isBeingHoveredOver, isDeepHovering }, dropTarget] = useDrop({
const [{ isHovering, isBeingHoveredOver, isDeepHovering, canDropThought }, dropTarget] = useDrop({
accept: [DragAndDropType.Thought, NativeTypes.FILE],
canDrop: (item, monitor) => canDrop(propsTypes, monitor),
drop: (item, monitor) => drop(propsTypes, monitor),
collect: dropCollect,
})

return { isDragging, dragSource, dragPreview, isHovering, isBeingHoveredOver, isDeepHovering, dropTarget }
return {
isDragging,
dragSource,
dragPreview,
isHovering,
isBeingHoveredOver,
isDeepHovering,
canDropThought,
dropTarget,
}
}

export default useDragAndDropThought
36 changes: 25 additions & 11 deletions src/hooks/useDragLeave.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { debounce } from 'lodash'
import { useEffect, useRef } from 'react'
import { useDispatch } from 'react-redux'
import { useDispatch, useSelector } from 'react-redux'
import { updateHoveringPathActionCreator } from '../actions/updateHoveringPath'
import useSortedContext from './useSortedContext'

const DEBOUNCE_DELAY = 50
let hoverCount = 0
Expand All @@ -11,10 +10,11 @@ let debouncedSetHoveringPath: ReturnType<typeof debounce> | null = null
/**
* Hook to capture the dragleave event and dispatch the dragInProgress action.
*/
const useDragLeave = ({ isDeepHovering }: { isDeepHovering: boolean }) => {
const useDragLeave = ({ isDeepHovering, canDropThought }: { isDeepHovering: boolean; canDropThought: boolean }) => {
const dispatch = useDispatch()
const hoverZone = useSelector(state => state.hoverZone)
const prevIsDeepHoveringRef = useRef(isDeepHovering)
const { hoveringOnDropEnd } = useSortedContext()
const prevHoverZone = useRef(hoverZone)

// Initialize the debounced function if it hasn't been already
if (!debouncedSetHoveringPath) {
Expand All @@ -25,33 +25,47 @@ const useDragLeave = ({ isDeepHovering }: { isDeepHovering: boolean }) => {
}

useEffect(() => {
if (prevHoverZone.current !== hoverZone) {
// Cancel any debounce function if hovering on subthought
prevHoverZone.current = hoverZone
debouncedSetHoveringPath?.cancel()
return
}

// If canDrop is false return
if (!canDropThought) {
dispatch(updateHoveringPathActionCreator({ path: undefined }))
return
}

if (isDeepHovering && !prevIsDeepHoveringRef.current) {
// Cursor has entered a drop target
// Cursor has entered a drop target, increase hover count
hoverCount += 1

// Cancel any pending debounce since we're over a drop target
debouncedSetHoveringPath?.cancel()
} else if (!isDeepHovering && prevIsDeepHoveringRef.current) {
// Cursor has left a drop target
if (hoverCount === 0 && !hoveringOnDropEnd) {
} else {
// Cursor has left a drop target, decrease hover count
hoverCount = Math.max(hoverCount - 1, 0)
if (hoverCount === 0) {
// No drop targets are being hovered over; start debounce
debouncedSetHoveringPath?.()
}
}

prevIsDeepHoveringRef.current = isDeepHovering
prevHoverZone.current = hoverZone

return () => {
// Cleanup on unmount
if (isDeepHovering) {
hoverCount = Math.max(hoverCount - 1, 0)

if (hoverCount === 0) {
// Start debounce when unmounting and no more drop targets are hovered
debouncedSetHoveringPath?.()
}
}
}
}, [isDeepHovering, dispatch, hoveringOnDropEnd])
}, [isDeepHovering, dispatch, hoverZone, canDropThought])
}

export default useDragLeave

0 comments on commit 0ae9219

Please sign in to comment.