Skip to content

Commit 9dc4897

Browse files
committed
refactor: listen dragOver only on root
1 parent ba5b311 commit 9dc4897

File tree

1 file changed

+148
-138
lines changed

1 file changed

+148
-138
lines changed

lib/HeTree.tsx

Lines changed: 148 additions & 138 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export interface Stat<T> {
2525
draggable: boolean,
2626
}
2727

28-
export type NodeAttrs = { 'data-key': string, 'data-level': string, 'data-node-box': boolean, 'data-drag-placeholder'?: boolean } & React.HTMLProps<HTMLDivElement>
28+
export type NodeAttrs = { 'data-key': string, 'data-level': string, 'data-node-box': boolean, 'data-drag-placeholder'?: boolean, 'data-dragging'?: boolean } & React.HTMLProps<HTMLDivElement>
2929

3030
// single instance ==================================
3131
const dragOverInfo = {
@@ -217,6 +217,7 @@ export function useHeTree<T extends Record<string, any>>(
217217
zIndex: '-999999999',
218218
visibility: 'hidden',
219219
})
220+
attrs['data-dragging'] = true
220221
}
221222
attrsList.push(attrs)
222223
visibleIds.push(stat.id)
@@ -308,159 +309,168 @@ export function useHeTree<T extends Record<string, any>>(
308309
}, 0)
309310
props.onDragStart?.(e, stat)
310311
},
311-
onDragOver(e) {
312-
if (isExternal && !props.onExternalDragOver?.(e)) {
313-
return
314-
}
315-
// dragOpen ========================
316-
const shouldDragOpen = () => {
317-
if (!props.dragOpen) {
318-
return false
319-
}
320-
if (isPlaceholder) {
321-
return false
322-
}
323-
if (stat.open) {
324-
return false
325-
}
326-
327-
const refresh = () => Object.assign(dragOverInfo, { id: stat.id, x: e.pageX, y: e.pageY, time: Date.now() })
328-
if (dragOverInfo.id !== stat.id) {
329-
refresh()
330-
return false
331-
}
332-
if (calculateDistance(e.pageX, e.pageY, dragOverInfo.x, dragOverInfo.y) > 10) {
333-
refresh()
334-
return false
335-
}
336-
const now = Date.now()
337-
if (now - dragOverInfo.time >= props.dragOpenDelay!) {
338-
return true
339-
}
340-
}
341-
if (shouldDragOpen()) {
342-
props.onDragOpen!(stat)
343-
}
344-
// dragOpen end ========================
345-
let t = findClosestAndNext(stat, isPlaceholder)
346-
const { closest, next } = t
347-
348-
let { atTop } = t
349-
const rootEl = rootRef.current!
350-
// @ts-ignore
351-
const nodeBox = hp.findParent(e.target, (el) => el.hasAttribute('data-node-box'), { withSelf: true })
352-
// node start position
353-
const getPlaceholderLevel = () => {
354-
let rect = nodeBox.getBoundingClientRect()
355-
let pl
356-
if (!rtl) {
357-
// ltr
358-
pl = Math.ceil((e.pageX - rect.x) / indent)
359-
} else {
360-
pl = Math.ceil((rect.right - e.pageX) / indent)
361-
}
362-
return hp.between(pl, 0, (closest?.level || 0) + 1)
363-
}
364-
let placeholderLevel = getPlaceholderLevel() // use this number to detect placeholder position. >= 0: prepend. < 0: after.}
365-
if (!atTop && !isPlaceholder && closest.id === rootIds[0]) {
366-
// check if at top
367-
const topNodeElement = rootEl.querySelector(`[data-key="${closest.id}"]`)
368-
if (topNodeElement) {
369-
const rect = topNodeElement.getBoundingClientRect()
370-
atTop = rect.y + rect.height / 2 > e.pageY
371-
}
312+
onDragLeave(e) {
313+
// dragLeave behavior is not expected. https://stackoverflow.com/questions/7110353/html5-dragleave-fired-when-hovering-a-child-element
314+
},
315+
}
316+
}
317+
const onDragOverRoot: React.DragEventHandler<HTMLElement> = (e) => {
318+
if (isExternal && !props.onExternalDragOver?.(e)) {
319+
return
320+
}
321+
const el = getClosestEl()
322+
if (el) {
323+
// @ts-ignore
324+
const stat = getStat(el.getAttribute('data-key'))
325+
const isPlaceholder = !!el.getAttribute('data-drag-placeholder')
326+
if (shouldDragOpen(stat, isPlaceholder)) {
327+
props.onDragOpen!(stat)
328+
}
329+
let t = findClosestAndNext(stat, isPlaceholder)
330+
const { closest, next } = t
331+
let { atTop } = t
332+
const rootEl = rootRef.current!
333+
// @ts-ignore
334+
const nodeBox = el
335+
// node start position
336+
const getPlaceholderLevel = () => {
337+
let rect = nodeBox.getBoundingClientRect()
338+
let pl
339+
if (!rtl) {
340+
// ltr
341+
pl = Math.ceil((e.pageX - rect.x) / indent)
342+
} else {
343+
pl = Math.ceil((rect.right - e.pageX) / indent)
372344
}
373-
if (atTop) {
374-
placeholderLevel = 0
345+
return hp.between(pl, 0, (closest?.level || 0) + 1)
346+
}
347+
let placeholderLevel = getPlaceholderLevel() // use this number to detect placeholder position. >= 0: prepend. < 0: after.}
348+
if (!atTop && !isPlaceholder && closest.id === rootIds[0]) {
349+
// check if at top
350+
const topNodeElement = rootEl.querySelector(`[data-key="${closest.id}"]`)
351+
if (topNodeElement) {
352+
const rect = topNodeElement.getBoundingClientRect()
353+
atTop = rect.y + rect.height / 2 > e.pageY
375354
}
376-
//
377-
let newPlaceholder: typeof placeholder
378-
if (atTop) {
355+
}
356+
if (atTop) {
357+
placeholderLevel = 0
358+
}
359+
//
360+
let newPlaceholder: typeof placeholder
361+
if (atTop) {
362+
if (getDroppable(null, 0)) {
379363
newPlaceholder = {
380364
...placeholder!,
381365
parentStat: null,
382366
level: 1,
383367
index: 0,
384368
}
385-
} else {
386-
const parentMinLevel = next ? next.level - 1 : 0
387-
// find all droppable positions
388-
const availablePositionsLeft: { parentStat: Stat<T>, index: number }[] = [];
389-
const availablePositionsRight: typeof availablePositionsLeft = [];
390-
let cur = closest
391-
const curLevel = () => cur ? cur.level : 0
392-
while (curLevel() >= parentMinLevel) {
393-
const index = getTargetIndex(cur, next)
394-
if (getDroppable(cur, index)) {
395-
(placeholderLevel > curLevel() ? availablePositionsLeft : availablePositionsRight).unshift({
396-
parentStat: cur,
397-
index,
398-
})
399-
}
400-
if (!cur) {
401-
break
402-
}
403-
cur = cur.parentStat!
404-
}
405-
let placeholderPosition = hp.arrayLast(availablePositionsLeft)
406-
if (!placeholderPosition) {
407-
placeholderPosition = hp.arrayFirst(availablePositionsRight)
369+
}
370+
} else {
371+
const parentMinLevel = next ? next.level - 1 : 0
372+
// find all droppable positions
373+
const availablePositionsLeft: { parentStat: Stat<T>, index: number }[] = [];
374+
const availablePositionsRight: typeof availablePositionsLeft = [];
375+
let cur = closest
376+
const curLevel = () => cur ? cur.level : 0
377+
while (curLevel() >= parentMinLevel) {
378+
const index = getTargetIndex(cur, next)
379+
if (getDroppable(cur, index)) {
380+
(placeholderLevel > curLevel() ? availablePositionsLeft : availablePositionsRight).unshift({
381+
parentStat: cur,
382+
index,
383+
})
408384
}
409-
if (placeholderPosition) {
410-
newPlaceholder = {
411-
...placeholder!,
412-
parentStat: placeholderPosition.parentStat,
413-
level: (placeholderPosition.parentStat?.level ?? 0) + 1,
414-
index: placeholderPosition.index,
415-
}
416-
} else {
417-
//
385+
if (!cur) {
386+
break
418387
}
388+
cur = cur.parentStat!
419389
}
420-
setPlaceholder(newPlaceholder)
421-
if (newPlaceholder) {
422-
e.preventDefault(); // call mean droppable
390+
let placeholderPosition = hp.arrayLast(availablePositionsLeft)
391+
if (!placeholderPosition) {
392+
placeholderPosition = hp.arrayFirst(availablePositionsRight)
423393
}
424-
setDragOverStat(isPlaceholder ? undefined : stat)
425-
props.onDragOver?.(e, stat, isExternal)
426-
},
427-
onDragLeave(e) {
428-
// dragLeave behavior is not expected. https://stackoverflow.com/questions/7110353/html5-dragleave-fired-when-hovering-a-child-element
429-
},
430-
}
431-
}
432-
const onDragOverRoot: React.DragEventHandler<HTMLElement> = (e) => {
433-
if (isExternal && !props.onExternalDragOver?.(e)) {
434-
return
435-
}
436-
if (getDroppable(null, 0)) {
437-
setPlaceholder({
438-
...placeholder!,
439-
parentStat: null,
440-
level: 1,
441-
index: 0,
442-
})
443-
e.preventDefault(); // droppable
444-
}
445-
function isAnyNodeOver() {
446-
let r = false
447-
const el = e.target as HTMLElement
448-
if (el) {
449-
for (const parent of walkParentsGenerator(el, 'parentElement', { withSelf: true })) {
450-
if (parent.hasAttribute('data-node-box')) {
451-
r = true
452-
break
453-
}
454-
if (parent === rootRef.current) {
455-
break
394+
if (placeholderPosition) {
395+
newPlaceholder = {
396+
...placeholder!,
397+
parentStat: placeholderPosition.parentStat,
398+
level: (placeholderPosition.parentStat?.level ?? 0) + 1,
399+
index: placeholderPosition.index,
456400
}
401+
} else {
402+
//
457403
}
458404
}
459-
return r
405+
setPlaceholder(newPlaceholder)
406+
if (newPlaceholder) {
407+
e.preventDefault(); // call mean droppable
408+
}
409+
setDragOverStat(isPlaceholder ? undefined : stat)
410+
props.onDragOver?.(e, stat, isExternal)
411+
} else {
412+
if (getDroppable(null, 0)) {
413+
setPlaceholder({
414+
...placeholder!,
415+
parentStat: null,
416+
level: 1,
417+
index: 0,
418+
})
419+
e.preventDefault(); // droppable
420+
}
460421
}
461-
function getCloest() {
422+
423+
function getClosestEl() {
462424
const rootEl = rootRef.current as HTMLElement
463-
// rootEl.querySelectorAll([])
425+
const nodeEls = rootEl.querySelectorAll(`[data-node-box]:not([data-dragging])`);
426+
const t = hp.binarySearch(
427+
// @ts-ignore
428+
nodeEls,
429+
(nodeEl: HTMLElement) =>
430+
nodeEl.getBoundingClientRect().top -
431+
e.pageY,
432+
{ returnNearestIfNoHit: true }
433+
)!;
434+
let index: number | undefined
435+
if (t.hit) {
436+
} else {
437+
if (t.greater) {
438+
index = t.index - 1;
439+
if (index < 0) {
440+
index = 0
441+
}
442+
} else {
443+
}
444+
}
445+
if (index == null) {
446+
index = t.index
447+
}
448+
return nodeEls[index]
449+
}
450+
function shouldDragOpen(stat: any, isPlaceholder: boolean) {
451+
if (!props.dragOpen) {
452+
return false
453+
}
454+
if (isPlaceholder) {
455+
return false
456+
}
457+
if (stat.open) {
458+
return false
459+
}
460+
461+
const refresh = () => Object.assign(dragOverInfo, { id: stat.id, x: e.pageX, y: e.pageY, time: Date.now() })
462+
if (dragOverInfo.id !== stat.id) {
463+
refresh()
464+
return false
465+
}
466+
if (calculateDistance(e.pageX, e.pageY, dragOverInfo.x, dragOverInfo.y) > 10) {
467+
refresh()
468+
return false
469+
}
470+
const now = Date.now()
471+
if (now - dragOverInfo.time >= props.dragOpenDelay!) {
472+
return true
473+
}
464474
}
465475
}
466476
const onDropToRoot: React.DragEventHandler<HTMLElement> = (e) => {
@@ -547,7 +557,7 @@ export function useHeTree<T extends Record<string, any>>(
547557
*/
548558
function findClosestAndNext(stat: Stat<T>, isPlaceholder: boolean) {
549559
let closest = stat
550-
let index = visibleIds.indexOf(stat.id) // index of closest node
560+
let index = visibleIds.indexOf(!isPlaceholder ? stat.id : props.placeholderId) // index of closest node
551561
let atTop = false
552562
const isPlaceholderOrDraggedNode = (id: Id) => id === placeholderId || getStat(id) === draggingStat
553563
const find = (startIndex: number, step: number) => {

0 commit comments

Comments
 (0)