タグ D&D 時の表示位置修正 (#268) #400
@@ -58,7 +58,8 @@ const DraggableDroppableTagRow: FC<Props> = ({ tag, nestLevel, pathKey, parentTa
|
|||||||
data: { kind: 'tag',
|
data: { kind: 'tag',
|
||||||
tagId: tag.id,
|
tagId: tag.id,
|
||||||
parentTagId,
|
parentTagId,
|
||||||
nestLevel } })
|
nestLevel,
|
||||||
|
dndId } })
|
||||||
|
|
||||||
const { setNodeRef: setDropRef, isOver: over } = useDroppable ({
|
const { setNodeRef: setDropRef, isOver: over } = useDroppable ({
|
||||||
id: dndId,
|
id: dndId,
|
||||||
@@ -99,6 +100,7 @@ const DraggableDroppableTagRow: FC<Props> = ({ tag, nestLevel, pathKey, parentTa
|
|||||||
>
|
>
|
||||||
<motion.div
|
<motion.div
|
||||||
ref={setDragRef}
|
ref={setDragRef}
|
||||||
|
data-tag-dnd-id={dndId}
|
||||||
style={style}
|
style={style}
|
||||||
className={cn (
|
className={cn (
|
||||||
'inline-flex min-w-0 max-w-full items-baseline overflow-hidden',
|
'inline-flex min-w-0 max-w-full items-baseline overflow-hidden',
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { DndContext,
|
|||||||
DragOverlay,
|
DragOverlay,
|
||||||
MouseSensor,
|
MouseSensor,
|
||||||
TouchSensor,
|
TouchSensor,
|
||||||
|
type Modifier,
|
||||||
pointerWithin,
|
pointerWithin,
|
||||||
rectIntersection,
|
rectIntersection,
|
||||||
useDroppable,
|
useDroppable,
|
||||||
@@ -10,7 +11,7 @@ import { DndContext,
|
|||||||
import { restrictToWindowEdges } from '@dnd-kit/modifiers'
|
import { restrictToWindowEdges } from '@dnd-kit/modifiers'
|
||||||
import { useQueryClient } from '@tanstack/react-query'
|
import { useQueryClient } from '@tanstack/react-query'
|
||||||
import { motion } from 'framer-motion'
|
import { motion } from 'framer-motion'
|
||||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
|
||||||
|
|
||||||
import DraggableDroppableTagRow from '@/components/DraggableDroppableTagRow'
|
import DraggableDroppableTagRow from '@/components/DraggableDroppableTagRow'
|
||||||
import PrefetchLink from '@/components/PrefetchLink'
|
import PrefetchLink from '@/components/PrefetchLink'
|
||||||
@@ -187,7 +188,12 @@ type Props = {
|
|||||||
|
|
||||||
type ActiveTagDrag = {
|
type ActiveTagDrag = {
|
||||||
tagId: number
|
tagId: number
|
||||||
nestLevel: number }
|
nestLevel: number
|
||||||
|
dndId: string }
|
||||||
|
|
||||||
|
type DragOverlayOffset = {
|
||||||
|
x: number
|
||||||
|
y: number }
|
||||||
|
|
||||||
|
|
||||||
const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
|
const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
|
||||||
@@ -222,16 +228,59 @@ const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
|
|||||||
const [dragging, setDragging] = useState (false)
|
const [dragging, setDragging] = useState (false)
|
||||||
const [saving, setSaving] = useState (false)
|
const [saving, setSaving] = useState (false)
|
||||||
const [tags, setTags] = useState (baseTags)
|
const [tags, setTags] = useState (baseTags)
|
||||||
|
const [dragOverlayOffset, setDragOverlayOffset] =
|
||||||
|
useState<DragOverlayOffset> ({ x: 0, y: 0 })
|
||||||
const flatTagsByCategory = useMemo<TagByCategory> (
|
const flatTagsByCategory = useMemo<TagByCategory> (
|
||||||
() => buildFlatTagByCategory (tags),
|
() => buildFlatTagByCategory (tags),
|
||||||
[tags],
|
[tags],
|
||||||
)
|
)
|
||||||
|
|
||||||
const suppressClickRef = useRef (false)
|
const suppressClickRef = useRef (false)
|
||||||
|
const dragStartRectRef = useRef<DOMRect | null> (null)
|
||||||
|
const offsetFrameRef = useRef<number | null> (null)
|
||||||
|
|
||||||
const sensors = useSensors (
|
const sensors = useSensors (
|
||||||
useSensor (MouseSensor, { activationConstraint: { distance: 6 } }),
|
useSensor (MouseSensor, { activationConstraint: { distance: 6 } }),
|
||||||
useSensor (TouchSensor, { activationConstraint: { delay: 250, tolerance: 8 } }))
|
useSensor (TouchSensor, { activationConstraint: { delay: 250, tolerance: 8 } }))
|
||||||
|
const dragOverlayModifiers = useMemo<Modifier[]> (
|
||||||
|
() => (
|
||||||
|
sp
|
||||||
|
? [({ transform }) => ({
|
||||||
|
...transform,
|
||||||
|
x: transform.x + dragOverlayOffset.x,
|
||||||
|
y: transform.y + dragOverlayOffset.y })]
|
||||||
|
: []),
|
||||||
|
[dragOverlayOffset.x, dragOverlayOffset.y, sp],
|
||||||
|
)
|
||||||
|
|
||||||
|
useLayoutEffect (() => {
|
||||||
|
if (!(sp && dragging && activeTagDrag && dragStartRectRef.current))
|
||||||
|
{
|
||||||
|
setDragOverlayOffset ({ x: 0, y: 0 })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
offsetFrameRef.current = requestAnimationFrame (() => {
|
||||||
|
const node = document.querySelector<HTMLElement> (
|
||||||
|
`[data-tag-dnd-id="${ activeTagDrag.dndId }"]`,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!(node && dragStartRectRef.current))
|
||||||
|
return
|
||||||
|
|
||||||
|
const currentRect = node.getBoundingClientRect ()
|
||||||
|
const initialRect = dragStartRectRef.current
|
||||||
|
|
||||||
|
setDragOverlayOffset ({
|
||||||
|
x: currentRect.left - initialRect.left,
|
||||||
|
y: currentRect.top - initialRect.top })
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (offsetFrameRef.current != null)
|
||||||
|
cancelAnimationFrame (offsetFrameRef.current)
|
||||||
|
}
|
||||||
|
}, [activeTagDrag, dragging, sp])
|
||||||
|
|
||||||
const reloadTags = async (): Promise<void> => {
|
const reloadTags = async (): Promise<void> => {
|
||||||
setTags (buildTagByCategory (await apiGet<Post> (`/posts/${ post.id }`)))
|
setTags (buildTagByCategory (await apiGet<Post> (`/posts/${ post.id }`)))
|
||||||
@@ -343,10 +392,19 @@ const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
|
|||||||
{
|
{
|
||||||
const tagId = e.active.data.current?.tagId
|
const tagId = e.active.data.current?.tagId
|
||||||
const nestLevel = e.active.data.current?.nestLevel
|
const nestLevel = e.active.data.current?.nestLevel
|
||||||
|
const dndId = e.active.data.current?.dndId
|
||||||
|
dragStartRectRef.current =
|
||||||
|
e.active.rect.current.initial
|
||||||
|
? new DOMRect (
|
||||||
|
e.active.rect.current.initial.left,
|
||||||
|
e.active.rect.current.initial.top,
|
||||||
|
e.active.rect.current.initial.width,
|
||||||
|
e.active.rect.current.initial.height)
|
||||||
|
: null
|
||||||
setActiveTagDrag (
|
setActiveTagDrag (
|
||||||
tagId == null || nestLevel == null
|
tagId == null || nestLevel == null || !(dndId)
|
||||||
? null
|
? null
|
||||||
: { tagId, nestLevel })
|
: { tagId, nestLevel, dndId })
|
||||||
}
|
}
|
||||||
setDragging (true)
|
setDragging (true)
|
||||||
suppressClickRef.current = true
|
suppressClickRef.current = true
|
||||||
@@ -360,12 +418,16 @@ const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
|
|||||||
onDragCancel={() => {
|
onDragCancel={() => {
|
||||||
setActiveTagDrag (null)
|
setActiveTagDrag (null)
|
||||||
setDragging (false)
|
setDragging (false)
|
||||||
|
setDragOverlayOffset ({ x: 0, y: 0 })
|
||||||
|
dragStartRectRef.current = null
|
||||||
document.body.style.userSelect = ''
|
document.body.style.userSelect = ''
|
||||||
suppressClickRef.current = false
|
suppressClickRef.current = false
|
||||||
}}
|
}}
|
||||||
onDragEnd={async e => {
|
onDragEnd={async e => {
|
||||||
setActiveTagDrag (null)
|
setActiveTagDrag (null)
|
||||||
setDragging (false)
|
setDragging (false)
|
||||||
|
setDragOverlayOffset ({ x: 0, y: 0 })
|
||||||
|
dragStartRectRef.current = null
|
||||||
await onDragEnd (e)
|
await onDragEnd (e)
|
||||||
document.body.style.userSelect = ''
|
document.body.style.userSelect = ''
|
||||||
}}
|
}}
|
||||||
@@ -458,7 +520,7 @@ const TagDetailSidebar: FC<Props> = ({ className, post, sp }) => {
|
|||||||
</ul>
|
</ul>
|
||||||
</motion.div>)}
|
</motion.div>)}
|
||||||
|
|
||||||
<DragOverlay adjustScale={false}>
|
<DragOverlay adjustScale={false} modifiers={dragOverlayModifiers}>
|
||||||
<div className="pointer-events-none">
|
<div className="pointer-events-none">
|
||||||
{activeTagDrag != null && (() => {
|
{activeTagDrag != null && (() => {
|
||||||
const tag = findTag (tags, activeTagDrag.tagId)
|
const tag = findTag (tags, activeTagDrag.tagId)
|
||||||
|
|||||||
新しい課題から参照
ユーザをブロックする