mirror of
https://github.com/oxynote/oxynote.git
synced 2026-10-17 02:49:18 +02:00
add fade animations to the drag handle
This commit is contained in:
parent
99cd7cf646
commit
766bef1cab
|
|
@ -188,7 +188,7 @@ function dragEnd() {
|
|||
:provider="props.dataSyncProvider"
|
||||
:locked="isLocked"
|
||||
:on-drag-cancel="dragEnd"
|
||||
class="z-drag-handle pr-1.5"
|
||||
class="z-drag-handle pr-1.5 transition-[opacity,visibility] duration-100"
|
||||
@node-change="handleNodeHover"
|
||||
@mouseenter="handleDragHoverEnter"
|
||||
@mouseleave="handleDragHoverLeave"
|
||||
|
|
|
|||
|
|
@ -834,6 +834,7 @@ describe("DragHandlePlugin", { concurrent: false }, () => {
|
|||
const { editor, element, handle, onNodeChange } = setup({
|
||||
content: [paragraph("one"), paragraph("two")],
|
||||
})
|
||||
element.style.transition = "opacity 100ms"
|
||||
hover(editor, handle.plugin, 0)
|
||||
onNodeChange.mockClear()
|
||||
vi.mocked(computePosition).mockClear()
|
||||
|
|
@ -859,6 +860,23 @@ describe("DragHandlePlugin", { concurrent: false }, () => {
|
|||
expect(computePosition).toHaveBeenCalledTimes(1)
|
||||
expect(element.style.visibility).toBe("")
|
||||
expect(element.style.pointerEvents).toBe("auto")
|
||||
// the handle left the first paragraph, so a copy fades out there
|
||||
expect(element.parentElement?.children).toHaveLength(2)
|
||||
})
|
||||
|
||||
it("leaves no copy when the drop lands back on the same node", async ({
|
||||
expect,
|
||||
}) => {
|
||||
const { editor, element, handle } = setup()
|
||||
element.style.transition = "opacity 100ms"
|
||||
hover(editor, handle.plugin, 0)
|
||||
|
||||
endDrag(element, 11, 13)
|
||||
vi.advanceTimersByTime(20)
|
||||
await flushMicrotasks()
|
||||
|
||||
expect(element.style.visibility).toBe("")
|
||||
expect(element.parentElement?.children).toHaveLength(1)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -1447,6 +1465,54 @@ describe("DragHandlePlugin", { concurrent: false }, () => {
|
|||
expect(onNodeChange).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("leaves a fading copy on the old node when the pointer moves to another", ({
|
||||
expect,
|
||||
}) => {
|
||||
const { editor, element, handle } = setup({
|
||||
content: [paragraph("one"), paragraph("two")],
|
||||
})
|
||||
element.style.transition = "opacity 100ms"
|
||||
hover(editor, handle.plugin, 0)
|
||||
|
||||
hover(editor, handle.plugin, 5)
|
||||
|
||||
const copy = must(element.nextElementSibling, "a copy") as HTMLElement
|
||||
expect(element.parentElement?.children).toHaveLength(2)
|
||||
expect(element.style.opacity).toBe("")
|
||||
expect(element.style.transition).toBe("opacity 100ms")
|
||||
expect(copy.inert).toBe(true)
|
||||
expect(copy.getAttribute("aria-hidden")).toBe("true")
|
||||
expect(copy.style.pointerEvents).toBe("none")
|
||||
expect(copy.style.opacity).toBe("0")
|
||||
})
|
||||
|
||||
it("removes the copy once its fade has run", async ({ expect }) => {
|
||||
const { editor, element, handle } = setup({
|
||||
content: [paragraph("one"), paragraph("two")],
|
||||
})
|
||||
element.style.transition = "opacity 100ms"
|
||||
hover(editor, handle.plugin, 0)
|
||||
hover(editor, handle.plugin, 5)
|
||||
const copy = must(element.nextElementSibling, "a copy") as HTMLElement
|
||||
|
||||
copy.getAnimations().forEach((animation) => {
|
||||
animation.finish()
|
||||
})
|
||||
await flushMicrotasks()
|
||||
|
||||
expect(copy.isConnected).toBe(false)
|
||||
expect(element.parentElement?.children).toHaveLength(1)
|
||||
})
|
||||
|
||||
it("leaves no copy when the handle was hidden", ({ expect }) => {
|
||||
const { editor, element, handle } = setup()
|
||||
element.style.transition = "opacity 100ms"
|
||||
|
||||
hover(editor, handle.plugin, 0)
|
||||
|
||||
expect(element.parentElement?.children).toHaveLength(1)
|
||||
})
|
||||
|
||||
it("does nothing while the handle is locked", ({ expect }) => {
|
||||
const { editor, handle, onNodeChange } = setup({ locked: true })
|
||||
|
||||
|
|
|
|||
|
|
@ -383,16 +383,56 @@ export const DragHandlePlugin = ({
|
|||
const resolvedPluginKey: PluginKey<DragHandlePluginState> =
|
||||
typeof pluginKey === "string" ? new PluginKey(pluginKey) : pluginKey
|
||||
|
||||
// the opacity is what fades. Visibility only flips once the fade has
|
||||
// run, if the handle's class transitions it too
|
||||
function hideHandle() {
|
||||
element.style.visibility = "hidden"
|
||||
element.style.opacity = "0"
|
||||
element.style.pointerEvents = "none"
|
||||
}
|
||||
|
||||
function showHandle() {
|
||||
element.style.visibility = ""
|
||||
element.style.opacity = ""
|
||||
element.style.pointerEvents = "auto"
|
||||
}
|
||||
|
||||
// the handle is a single element, so moving it to another node would
|
||||
// jump. A copy stays behind on the old node and fades out, and the
|
||||
// handle starts from nothing so that showHandle fades it in
|
||||
function leaveFadingCopy() {
|
||||
if (element.style.visibility === "hidden") {
|
||||
return
|
||||
}
|
||||
|
||||
const copy = element.cloneNode(true) as HTMLElement
|
||||
copy.inert = true
|
||||
copy.setAttribute("aria-hidden", "true")
|
||||
copy.style.pointerEvents = "none"
|
||||
// a quick move can catch the handle still fading in
|
||||
copy.style.opacity = getComputedStyle(element).opacity
|
||||
wrapper.appendChild(copy)
|
||||
|
||||
// the handle drops to 0 at once, with its transition off, and
|
||||
// showHandle fades it back in from there
|
||||
const transition = element.style.transition
|
||||
element.style.transition = "none"
|
||||
element.style.opacity = "0"
|
||||
// the browser only fades from a style it has already applied.
|
||||
// Measuring makes it apply the styles set so far: the handle at 0
|
||||
// and the copy at its starting opacity
|
||||
element.getBoundingClientRect()
|
||||
element.style.transition = transition
|
||||
|
||||
copy.style.opacity = "0"
|
||||
// the copy goes once its fade has run, or at once without one
|
||||
void Promise.allSettled(
|
||||
copy.getAnimations().map((animation) => animation.finished),
|
||||
).then(() => {
|
||||
copy.remove()
|
||||
})
|
||||
}
|
||||
|
||||
function repositionDragHandle(dom: Element, currentNodePos: number) {
|
||||
const virtualElement = getReferencedVirtualElement?.() ?? {
|
||||
getBoundingClientRect: () => dom.getBoundingClientRect(),
|
||||
|
|
@ -515,6 +555,10 @@ export const DragHandlePlugin = ({
|
|||
return
|
||||
}
|
||||
|
||||
if (result.pos !== currentNodePos) {
|
||||
leaveFadingCopy()
|
||||
}
|
||||
|
||||
currentNode = result.node
|
||||
currentNodePos = result.pos
|
||||
currentNodeRelPos = findRelativePos(editor.view.state, currentNodePos)
|
||||
|
|
@ -804,6 +848,8 @@ export const DragHandlePlugin = ({
|
|||
// false positives when the same logical node gets a new object
|
||||
// reference after document updates
|
||||
if (nodePos !== currentNodePos) {
|
||||
leaveFadingCopy()
|
||||
|
||||
currentNode = node
|
||||
currentNodePos = nodePos
|
||||
currentNodeRelPos = findRelativePos(view.state, currentNodePos)
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user