From d73836cfd2afe28a73d61825957ec71e61ba0d54 Mon Sep 17 00:00:00 2001 From: Alva Greason <243506597@qq.com> Date: Tue, 12 Mar 2024 17:31:47 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0useDrag=E5=92=8CuseDr?= =?UTF-8?q?op=20(#141)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: panxiaoliang --- hooks/useDrag/docs/demo/demo1.tsx | 87 +++++++++++++ hooks/useDrag/docs/demo/demo2.tsx | 36 ++++++ hooks/useDrag/docs/useDrag.md | 93 ++++++++++++++ hooks/useDrag/package.json | 24 ++++ hooks/useDrag/src/index.ts | 85 +++++++++++++ hooks/useDrag/test/index.dom.test.ts | 72 +++++++++++ hooks/useDrag/tsconfig.json | 10 ++ hooks/useDrag/tsup.config.ts | 3 + hooks/useDrop/package.json | 24 ++++ hooks/useDrop/src/index.ts | 126 ++++++++++++++++++ hooks/useDrop/test/index.dom.test.ts | 183 +++++++++++++++++++++++++++ hooks/useDrop/tsconfig.json | 8 ++ hooks/useDrop/tsup.config.ts | 3 + 13 files changed, 754 insertions(+) create mode 100644 hooks/useDrag/docs/demo/demo1.tsx create mode 100644 hooks/useDrag/docs/demo/demo2.tsx create mode 100644 hooks/useDrag/docs/useDrag.md create mode 100644 hooks/useDrag/package.json create mode 100644 hooks/useDrag/src/index.ts create mode 100644 hooks/useDrag/test/index.dom.test.ts create mode 100644 hooks/useDrag/tsconfig.json create mode 100644 hooks/useDrag/tsup.config.ts create mode 100644 hooks/useDrop/package.json create mode 100644 hooks/useDrop/src/index.ts create mode 100644 hooks/useDrop/test/index.dom.test.ts create mode 100644 hooks/useDrop/tsconfig.json create mode 100644 hooks/useDrop/tsup.config.ts diff --git a/hooks/useDrag/docs/demo/demo1.tsx b/hooks/useDrag/docs/demo/demo1.tsx new file mode 100644 index 00000000..05b56a47 --- /dev/null +++ b/hooks/useDrag/docs/demo/demo1.tsx @@ -0,0 +1,87 @@ +/** + * title: Basic usage + * desc: The drop area can accept files, uri, text or one of the boxes below. + * + * title.zh-CN: 基础用法 + * desc.zh-CN: 拖拽区域可以接受文件,链接,文字,和下方的 box 节点。 + */ + +import React, { useRef, useState } from "react"; +import { useDrag } from "../../src"; +import { useDrop } from "../../../useDrop/src"; +const DragItem = ({ data }) => { + const dragRef = useRef(null); + + const [dragging, setDragging] = useState(false); + + useDrag(data, dragRef, { + onDragStart: () => { + setDragging(true); + }, + onDragEnd: () => { + setDragging(false); + }, + }); + + return ( +
+ {dragging ? "dragging" : `box-${data}`} +
+ ); +}; + +export default () => { + const [isHovering, setIsHovering] = useState(false); + + const dropRef = useRef(null); + + useDrop(dropRef, { + onText: (text, e) => { + console.log(e); + alert(`'text: ${text}' dropped`); + }, + onFiles: (files, e) => { + console.log(e, files); + alert(`${files.length} file dropped`); + }, + onUri: (uri, e) => { + console.log(e); + alert(`uri: ${uri} dropped`); + }, + onDom: (content: string, e) => { + alert(`custom: ${content} dropped`); + }, + onDragEnter: () => setIsHovering(true), + onDragLeave: () => setIsHovering(false), + }); + + return ( +
+
+ {isHovering ? "release here" : "drop here"} +
+ +
+ {["1", "2", "3", "4", "5"].map((e) => ( + + ))} +
+
+ ); +}; diff --git a/hooks/useDrag/docs/demo/demo2.tsx b/hooks/useDrag/docs/demo/demo2.tsx new file mode 100644 index 00000000..cc09fd7b --- /dev/null +++ b/hooks/useDrag/docs/demo/demo2.tsx @@ -0,0 +1,36 @@ +/** + * title: Customize Image + * desc: Customize image that follow the mouse pointer during dragging. + * + * title.zh-CN: 自定义拖拽图像 + * desc.zh-CN: 自定义拖拽过程中跟随鼠标指针的图像。 + */ + +import React, { useRef } from "react"; +import { useDrag } from "../../src"; + +const COMMON_STYLE: React.CSSProperties = { + border: "1px solid #e8e8e8", + height: "50px", + lineHeight: "50px", + padding: "16px", + textAlign: "center", + marginRight: "16px", +}; + +export default () => { + const dragRef = useRef(null); + + useDrag("", dragRef, { + dragImage: { + image: "/logo.svg", + }, + }); + + return ( +
+ +
drag me
+
+ ); +}; diff --git a/hooks/useDrag/docs/useDrag.md b/hooks/useDrag/docs/useDrag.md new file mode 100644 index 00000000..c8c04ffd --- /dev/null +++ b/hooks/useDrag/docs/useDrag.md @@ -0,0 +1,93 @@ +--- +title: useDrop & useDrag +group: +title: Dom +order: 70 + +--- + +# useDrop & useDrag + +处理元素拖拽的 Hook。 + +> useDrop 可以单独使用来接收文件、文字和网址的拖拽。 +> +> useDrag 允许一个 DOM 节点被拖拽,需要配合 useDrop 使用。 +> +> 向节点内触发粘贴动作也会被视为拖拽。 + +## 代码演示 + +### 基础用法 + + + +### 自定义拖拽图像 + + + +## API + +### useDrag + +```typescript +useDrag( + data: any, + target: (() => Element) | Element | MutableRefObject, + options?: DragOptions +); +``` + +#### Params + +| 参数 | 说明 | 类型 | 默认值 | +| ------- | --------------------- | ----------------------------------------------------------- | ------ | +| data | 拖拽的内容 | `any` | - | +| target | DOM 节点或者 Ref 对象 | `() => Element` \| `Element` \| `MutableRefObject` | - | +| options | 额外的配置项 | `DragOptions` | - | + +#### DragOptions + +| 参数 | 说明 | 类型 | 默认值 | +| ----------- | ---------------------------------- | ------------------------------ | ------ | +| onDragStart | 开始拖拽的回调 | `(e: React.DragEvent) => void` | - | +| onDragEnd | 结束拖拽的回调 | `(e: React.DragEvent) => void` | - | +| dragImage | 自定义拖拽过程中跟随鼠标指针的图像 | `DragImageOptions` | - | + +#### DragImageOptions + +| 参数 | 说明 | 类型 | 默认值 | +| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------- | ------ | +| image | 拖拽过程中跟随鼠标指针的图像。图像通常是一个 [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img) 元素,但也可以是 [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas) 或任何其他图像元素。 | `string \| Element` | - | +| offsetX | 水平偏移 | `number` | 0 | +| offsetY | 垂直偏移 | `number` | 0 | + +### useDrop + +```typescript +useDrop( + target: (() => Element) | Element | MutableRefObject, + options?: DropOptions +); +``` + +#### Params + +| 参数 | 说明 | 类型 | 默认值 | +| ------- | --------------------- | ----------------------------------------------------------- | ------ | +| target | DOM 节点或者 Ref 对象 | `() => Element` \| `Element` \| `MutableRefObject` | - | +| options | 额外的配置项 | `DragOptions` | - | + +#### DropOptions + +| 参数 | 说明 | 类型 | 默认值 | +| ----------- | ------------------------------ | --------------------------------------------- | ------ | +| onText | 拖拽/粘贴文字的回调 | `(text: string, e: React.DragEvent) => void` | - | +| onFiles | 拖拽/粘贴文件的回调 | `(files: File[], e: React.DragEvent) => void` | - | +| onUri | 拖拽/粘贴链接的回调 | `(text: string, e: React.DragEvent) => void` | - | +| onDom | 拖拽/粘贴自定义 DOM 节点的回调 | `(content: any, e: React.DragEvent) => void` | - | +| onDrop | 拖拽任意内容的回调 | `(e: React.DragEvent) => void` | - | +| onPaste | 粘贴内容的回调 | `(e: React.DragEvent) => void` | - | +| onDragEnter | 拖拽进入 | `(e: React.DragEvent) => void` | - | +| onDragOver | 拖拽中 | `(e: React.DragEvent) => void` | - | +| onDragLeave | 拖拽出去 | `(e: React.DragEvent) => void` | - | diff --git a/hooks/useDrag/package.json b/hooks/useDrag/package.json new file mode 100644 index 00000000..c39c535e --- /dev/null +++ b/hooks/useDrag/package.json @@ -0,0 +1,24 @@ +{ + "name": "@pansy/use-drag", + "description": "useDrag 允许一个 DOM 节点被拖拽,需要配合 useDrop 使用。", + "version": "1.0.0", + "main": "lib/index.js", + "module": "es/index.js", + "types": "es/index.d.ts", + "files": [ + "es", + "lib", + "dist" + ], + "scripts": { + "dev": "redbud dev", + "build": "redbud build" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "publishConfig": { + "registry": "https://registry.npmjs.org", + "access": "public" + } +} diff --git a/hooks/useDrag/src/index.ts b/hooks/useDrag/src/index.ts new file mode 100644 index 00000000..2c01385f --- /dev/null +++ b/hooks/useDrag/src/index.ts @@ -0,0 +1,85 @@ +import { useRef } from "react"; +import React from "react"; +import { useLatest } from "@pansy/use-latest"; +import { useMount } from "@pansy/use-mount"; +import { isString } from "@pansy/shared"; +import type { BasicTarget } from "@pansy/shared/react"; +import { getTargetElement } from "@pansy/shared/react"; +import { useEffectWithTarget } from "@pansy/hook-utils"; + +export interface Options { + onDragStart?: (event: React.DragEvent) => void; + onDragEnd?: (event: React.DragEvent) => void; + dragImage?: { + image: string | Element; + offsetX?: number; + offsetY?: number; + }; +} + +export const useDrag = ( + data: T, + target: BasicTarget, + options: Options = {} +) => { + const optionsRef = useLatest(options); + const dataRef = useLatest(data); + const imageElementRef = useRef(); + + const { dragImage } = optionsRef.current; + + useMount(() => { + if (dragImage?.image) { + const { image } = dragImage; + + if (isString(image)) { + const imageElement = new Image(); + + imageElement.src = image; + imageElementRef.current = imageElement; + } else { + imageElementRef.current = image; + } + } + }); + + useEffectWithTarget( + () => { + const targetElement = getTargetElement(target) as Element; + if (!targetElement?.addEventListener) { + return; + } + + const onDragStart = (event: React.DragEvent) => { + optionsRef.current.onDragStart?.(event); + event.dataTransfer.setData("custom", JSON.stringify(dataRef.current)); + + if (dragImage?.image && imageElementRef.current) { + const { offsetX = 0, offsetY = 0 } = dragImage; + + event.dataTransfer.setDragImage( + imageElementRef.current, + offsetX, + offsetY + ); + } + }; + + const onDragEnd = (event: React.DragEvent) => { + optionsRef.current.onDragEnd?.(event); + }; + + targetElement?.setAttribute?.("draggable", "true"); + + targetElement.addEventListener("dragstart", onDragStart as any); + targetElement.addEventListener("dragend", onDragEnd as any); + + return () => { + targetElement.removeEventListener("dragstart", onDragStart as any); + targetElement.removeEventListener("dragend", onDragEnd as any); + }; + }, + [], + target + ); +}; diff --git a/hooks/useDrag/test/index.dom.test.ts b/hooks/useDrag/test/index.dom.test.ts new file mode 100644 index 00000000..0557d844 --- /dev/null +++ b/hooks/useDrag/test/index.dom.test.ts @@ -0,0 +1,72 @@ +import { renderHook } from "@testing-library/react"; +import type { Options } from "../src/index"; +import { useDrag } from "../src/index"; +import type { BasicTarget } from "@pansy/shared/react"; + +const setup = (data: T, target: BasicTarget, options?: Options) => + renderHook((newData: T) => + useDrag(newData ? newData : data, target, options) + ); + +const events: Record void> = {}; +const mockTarget = { + addEventListener: jest.fn((event, callback) => { + events[event] = callback; + }), + removeEventListener: jest.fn((event) => { + Reflect.deleteProperty(events, event); + }), + setAttribute: jest.fn(), +}; + +describe("useDrag", () => { + it("should add/remove listener on mount/unmount", () => { + const { unmount } = setup(1, mockTarget as any); + expect(mockTarget.addEventListener).toBeCalled(); + expect(mockTarget.addEventListener.mock.calls[0][0]).toBe("dragstart"); + expect(mockTarget.addEventListener.mock.calls[1][0]).toBe("dragend"); + expect(mockTarget.setAttribute).toBeCalledWith("draggable", "true"); + unmount(); + expect(mockTarget.removeEventListener).toBeCalled(); + }); + + it("should triggle drag callback", () => { + const onDragStart = jest.fn(); + const onDragEnd = jest.fn(); + const mockEvent = { + dataTransfer: { + setData: jest.fn(), + }, + }; + const hook = setup(1, mockTarget as any, { + onDragStart, + onDragEnd, + }); + events.dragstart(mockEvent); + expect(onDragStart).toBeCalled(); + expect(mockEvent.dataTransfer.setData).toBeCalledWith("custom", "1"); + events.dragend(mockEvent); + expect(onDragEnd).toBeCalled(); + + hook.rerender(2); + + events.dragstart(mockEvent); + expect(onDragStart).toBeCalled(); + expect(mockEvent.dataTransfer.setData).toHaveBeenLastCalledWith( + "custom", + "2" + ); + events.dragend(mockEvent); + expect(onDragEnd).toBeCalled(); + }); + + it(`should not work when target don't support addEventListener method`, () => { + Object.defineProperty(mockTarget, "addEventListener", { + get() { + return false; + }, + }); + setup(1, mockTarget as any); + expect(mockTarget.setAttribute).not.toBeCalled(); + }); +}); diff --git a/hooks/useDrag/tsconfig.json b/hooks/useDrag/tsconfig.json new file mode 100644 index 00000000..d7a1b476 --- /dev/null +++ b/hooks/useDrag/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": [ + "src" + ] +} \ No newline at end of file diff --git a/hooks/useDrag/tsup.config.ts b/hooks/useDrag/tsup.config.ts new file mode 100644 index 00000000..2ec7174c --- /dev/null +++ b/hooks/useDrag/tsup.config.ts @@ -0,0 +1,3 @@ +import { config } from '../../tsup.config'; + +export default config; diff --git a/hooks/useDrop/package.json b/hooks/useDrop/package.json new file mode 100644 index 00000000..fc9302d2 --- /dev/null +++ b/hooks/useDrop/package.json @@ -0,0 +1,24 @@ +{ + "name": "@pansy/use-drop", + "description": "useDrop 可以单独使用来接收文件、文字和网址的拖拽。", + "version": "1.0.0", + "main": "lib/index.js", + "module": "es/index.js", + "types": "es/index.d.ts", + "files": [ + "es", + "lib", + "dist" + ], + "scripts": { + "dev": "redbud dev", + "build": "redbud build" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "publishConfig": { + "registry": "https://registry.npmjs.org", + "access": "public" + } +} diff --git a/hooks/useDrop/src/index.ts b/hooks/useDrop/src/index.ts new file mode 100644 index 00000000..a5cb6c16 --- /dev/null +++ b/hooks/useDrop/src/index.ts @@ -0,0 +1,126 @@ +import type React from "react"; +import { useLatest } from "@pansy/use-latest"; +import type { BasicTarget } from "@pansy/shared/react"; +import { getTargetElement } from "@pansy/shared/react"; +import { useEffectWithTarget } from "@pansy/hook-utils"; +import { useRef } from "react"; + +export interface Options { + onFiles?: (files: File[], event?: React.DragEvent) => void; + onUri?: (url: string, event?: React.DragEvent) => void; + onDom?: (content: any, event?: React.DragEvent) => void; + onText?: (text: string, event?: React.ClipboardEvent) => void; + onDragEnter?: (event?: React.DragEvent) => void; + onDragOver?: (event?: React.DragEvent) => void; + onDragLeave?: (event?: React.DragEvent) => void; + onDrop?: (event?: React.DragEvent) => void; + onPaste?: (event?: React.ClipboardEvent) => void; +} + +export const useDrop = (target: BasicTarget, options: Options = {}) => { + const optionsRef = useLatest(options); + + // https://stackoverflow.com/a/26459269 + const dragEnterTarget = useRef(); + + useEffectWithTarget( + () => { + const targetElement = getTargetElement(target); + if (!targetElement?.addEventListener) { + return; + } + + const onData = ( + dataTransfer: DataTransfer, + event: React.DragEvent | React.ClipboardEvent + ) => { + const uri = dataTransfer.getData("text/uri-list"); + const dom = dataTransfer.getData("custom"); + + if (dom && optionsRef.current.onDom) { + let data = dom; + try { + data = JSON.parse(dom); + } catch (e) { + data = dom; + } + optionsRef.current.onDom(data, event as React.DragEvent); + return; + } + + if (uri && optionsRef.current.onUri) { + optionsRef.current.onUri(uri, event as React.DragEvent); + return; + } + + if ( + dataTransfer?.files && + dataTransfer.files?.length && + optionsRef.current.onFiles + ) { + optionsRef.current.onFiles( + Array.from(dataTransfer.files), + event as React.DragEvent + ); + return; + } + + if ( + dataTransfer?.items && + dataTransfer.items?.length && + optionsRef.current.onText + ) { + dataTransfer.items[0].getAsString((text) => { + optionsRef.current.onText!(text, event as React.ClipboardEvent); + }); + } + }; + + const onDragEnter = (event: React.DragEvent) => { + event.preventDefault(); + event.stopPropagation(); + + dragEnterTarget.current = event.target; + optionsRef.current.onDragEnter?.(event); + }; + + const onDragOver = (event: React.DragEvent) => { + event.preventDefault(); + optionsRef.current.onDragOver?.(event); + }; + + const onDragLeave = (event: React.DragEvent) => { + if (event.target === dragEnterTarget.current) { + optionsRef.current.onDragLeave?.(event); + } + }; + + const onDrop = (event: React.DragEvent) => { + event.preventDefault(); + onData(event.dataTransfer, event); + optionsRef.current.onDrop?.(event); + }; + + const onPaste = (event: React.ClipboardEvent) => { + onData(event.clipboardData, event); + optionsRef.current.onPaste?.(event); + }; + + targetElement.addEventListener("dragenter", onDragEnter as any); + targetElement.addEventListener("dragover", onDragOver as any); + targetElement.addEventListener("dragleave", onDragLeave as any); + targetElement.addEventListener("drop", onDrop as any); + targetElement.addEventListener("paste", onPaste as any); + + return () => { + targetElement.removeEventListener("dragenter", onDragEnter as any); + targetElement.removeEventListener("dragover", onDragOver as any); + targetElement.removeEventListener("dragleave", onDragLeave as any); + targetElement.removeEventListener("drop", onDrop as any); + targetElement.removeEventListener("paste", onPaste as any); + }; + }, + [], + target + ); +}; diff --git a/hooks/useDrop/test/index.dom.test.ts b/hooks/useDrop/test/index.dom.test.ts new file mode 100644 index 00000000..f7ac976b --- /dev/null +++ b/hooks/useDrop/test/index.dom.test.ts @@ -0,0 +1,183 @@ +import { renderHook } from "@testing-library/react"; +import { useDrop, Options } from "../src/index"; +import type { BasicTarget } from "@pansy/shared/react"; + +const setup = (target: unknown, options?: Options) => + renderHook(() => useDrop(target as BasicTarget, options)); + +const events = {}; +const mockTarget = { + addEventListener: jest.fn((event, callback) => { + events[event] = callback; + }), + removeEventListener: jest.fn((event) => { + Reflect.deleteProperty(events, event); + }), +}; + +const mockEvent = { + dataTransfer: { + getData: (format?: string) => "mock" as unknown, + get items() { + return [] as unknown[]; + }, + get files() { + return [] as unknown[]; + }, + }, + clipboardData: { + getData: (format?: string) => "mock" as unknown, + get items() { + return [] as unknown[]; + }, + get files() { + return [] as unknown[]; + }, + }, + preventDefault: jest.fn(), + stopPropagation: jest.fn(), +}; + +describe("useDrop", () => { + it(`should not work when target don't support addEventListener method`, () => { + const originAddEventListener = mockTarget.addEventListener; + Object.defineProperty(mockTarget, "addEventListener", { + value: false, + }); + setup(mockTarget); + expect(Object.keys(events)).toHaveLength(0); + Object.defineProperty(mockTarget, "addEventListener", { + value: originAddEventListener, + }); + }); + + it("should add/remove listener on mount/unmount", () => { + const { unmount } = setup(mockTarget); + const eventNames = ["dragenter", "dragover", "dragleave", "drop", "paste"]; + expect(mockTarget.addEventListener).toBeCalledTimes(eventNames.length); + eventNames.forEach((eventName, i) => { + expect(mockTarget.addEventListener.mock.calls[i][0]).toBe(eventName); + }); + unmount(); + expect(mockTarget.removeEventListener).toBeCalledTimes(eventNames.length); + eventNames.forEach((eventName, i) => { + expect(mockTarget.addEventListener.mock.calls[i][0]).toBe(eventName); + }); + }); + + it("should call callback", () => { + const onDragEnter = jest.fn(); + const onDragOver = jest.fn(); + const onDragLeave = jest.fn(); + const onDrop = jest.fn(); + const onPaste = jest.fn(); + + setup(mockTarget, { + onDragEnter, + onDragOver, + onDragLeave, + onDrop, + onPaste, + }); + const callbacks = [onDragEnter, onDragOver, onDragLeave, onDrop, onPaste]; + const eventNames = ["dragenter", "dragover", "dragleave", "drop", "paste"]; + eventNames.forEach((event) => { + events[event](mockEvent); + }); + callbacks.forEach((callback) => expect(callback).toBeCalled()); + }); + + it("should call onText on drop", async () => { + jest.spyOn(mockEvent.dataTransfer, "items", "get").mockReturnValue([ + { + getAsString: (callback) => { + callback("drop text"); + }, + }, + ]); + + const onText = jest.fn(); + setup(mockTarget, { + onText, + }); + events["dragenter"](mockEvent); + events["drop"](mockEvent); + expect(onText.mock.calls[0][0]).toBe("drop text"); + }); + + it("should call onFiles on drop", async () => { + const file = new File(["hello"], "hello.png"); + jest.spyOn(mockEvent.dataTransfer, "files", "get").mockReturnValue([file]); + const onFiles = jest.fn(); + setup(mockTarget, { + onFiles, + }); + events["dragenter"](mockEvent); + events["drop"](mockEvent); + expect(onFiles.mock.calls[0][0]).toHaveLength(1); + }); + + it("should call onUri on drop", async () => { + const url = "https://alipay.com"; + jest + .spyOn(mockEvent.dataTransfer, "getData") + .mockImplementation((format: string | undefined) => { + if (format === "text/uri-list") return url; + }); + + const onUri = jest.fn(); + setup(mockTarget, { + onUri, + }); + events["dragenter"](mockEvent); + events["drop"](mockEvent); + expect(onUri.mock.calls[0][0]).toBe(url); + }); + + it("should call onDom on drop", async () => { + const data = { + value: "mock", + }; + jest + .spyOn(mockEvent.dataTransfer, "getData") + .mockImplementation((format: string | undefined) => { + if (format === "custom") return data; + }); + + const onDom = jest.fn(); + setup(mockTarget, { + onDom, + }); + events["dragenter"](mockEvent); + events["drop"](mockEvent); + expect(onDom.mock.calls[0][0]).toMatchObject(data); + + // catch JSON.parse error + jest + .spyOn(mockEvent.dataTransfer, "getData") + .mockImplementation((format: string | undefined) => { + if (format === "custom") return {}; + }); + events["dragenter"](mockEvent); + events["drop"](mockEvent); + expect(onDom.mock.calls[0][0]).toMatchObject({}); + }); + + it("should call onText on paste", async () => { + jest.spyOn(mockEvent.clipboardData, "items", "get").mockReturnValue([ + { + getAsString: (callback) => { + callback("paste text"); + }, + }, + ]); + + const onText = jest.fn(); + setup(mockTarget, { + onText, + }); + events["dragenter"](mockEvent); + events["paste"](mockEvent); + expect(onText.mock.calls[0][0]).toBe("paste text"); + }); +}); diff --git a/hooks/useDrop/tsconfig.json b/hooks/useDrop/tsconfig.json new file mode 100644 index 00000000..792172fb --- /dev/null +++ b/hooks/useDrop/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src" + }, + "include": ["src"] +} diff --git a/hooks/useDrop/tsup.config.ts b/hooks/useDrop/tsup.config.ts new file mode 100644 index 00000000..2ec7174c --- /dev/null +++ b/hooks/useDrop/tsup.config.ts @@ -0,0 +1,3 @@ +import { config } from '../../tsup.config'; + +export default config;