Skip to content

Commit

Permalink
feat: 添加useDrag和useDrop (#141)
Browse files Browse the repository at this point in the history
Co-authored-by: panxiaoliang <panxiaoliang>
  • Loading branch information
xiaoliangpan authored Mar 12, 2024
1 parent 932eb71 commit d73836c
Show file tree
Hide file tree
Showing 13 changed files with 754 additions and 0 deletions.
87 changes: 87 additions & 0 deletions hooks/useDrag/docs/demo/demo1.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div
ref={dragRef}
style={{
border: "1px solid #e8e8e8",
padding: 16,
width: 80,
textAlign: "center",
marginRight: 16,
}}
>
{dragging ? "dragging" : `box-${data}`}
</div>
);
};

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 (
<div>
<div
ref={dropRef}
style={{
border: "1px dashed #e8e8e8",
padding: 16,
textAlign: "center",
}}
>
{isHovering ? "release here" : "drop here"}
</div>

<div style={{ display: "flex", marginTop: 8, overflow: "auto" }}>
{["1", "2", "3", "4", "5"].map((e) => (
<DragItem key={e} data={e} />
))}
</div>
</div>
);
};
36 changes: 36 additions & 0 deletions hooks/useDrag/docs/demo/demo2.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div ref={dragRef} style={{ display: "flex" }}>
<img style={COMMON_STYLE} src="/logo.svg" />
<div style={COMMON_STYLE}>drag me</div>
</div>
);
};
93 changes: 93 additions & 0 deletions hooks/useDrag/docs/useDrag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
---
title: useDrop & useDrag
group:
title: Dom
order: 70

---

# useDrop & useDrag

处理元素拖拽的 Hook。

> useDrop 可以单独使用来接收文件、文字和网址的拖拽。
>
> useDrag 允许一个 DOM 节点被拖拽,需要配合 useDrop 使用。
>
> 向节点内触发粘贴动作也会被视为拖拽。
## 代码演示

### 基础用法

<code src="./demo/demo1.tsx" ></code>

### 自定义拖拽图像

<code src="./demo/demo2.tsx" ></code>

## API

### useDrag

```typescript
useDrag<T>(
data: any,
target: (() => Element) | Element | MutableRefObject<Element>,
options?: DragOptions
);
```

#### Params

| 参数 | 说明 | 类型 | 默认值 |
| ------- | --------------------- | ----------------------------------------------------------- | ------ |
| data | 拖拽的内容 | `any` | - |
| target | DOM 节点或者 Ref 对象 | `() => Element` \| `Element` \| `MutableRefObject<Element>` | - |
| options | 额外的配置项 | `DragOptions` | - |

#### DragOptions

| 参数 | 说明 | 类型 | 默认值 |
| ----------- | ---------------------------------- | ------------------------------ | ------ |
| onDragStart | 开始拖拽的回调 | `(e: React.DragEvent) => void` | - |
| onDragEnd | 结束拖拽的回调 | `(e: React.DragEvent) => void` | - |
| dragImage | 自定义拖拽过程中跟随鼠标指针的图像 | `DragImageOptions` | - |

#### DragImageOptions

| 参数 | 说明 | 类型 | 默认值 |
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------- | ------ |
| image | 拖拽过程中跟随鼠标指针的图像。图像通常是一个 [`<img>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/img) 元素,但也可以是 [`<canvas>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas) 或任何其他图像元素。 | `string \| Element` | - |
| offsetX | 水平偏移 | `number` | 0 |
| offsetY | 垂直偏移 | `number` | 0 |

### useDrop

```typescript
useDrop<T>(
target: (() => Element) | Element | MutableRefObject<Element>,
options?: DropOptions
);
```

#### Params

| 参数 | 说明 | 类型 | 默认值 |
| ------- | --------------------- | ----------------------------------------------------------- | ------ |
| target | DOM 节点或者 Ref 对象 | `() => Element` \| `Element` \| `MutableRefObject<Element>` | - |
| 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` | - |
24 changes: 24 additions & 0 deletions hooks/useDrag/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
85 changes: 85 additions & 0 deletions hooks/useDrag/src/index.ts
Original file line number Diff line number Diff line change
@@ -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 = <T>(
data: T,
target: BasicTarget,
options: Options = {}
) => {
const optionsRef = useLatest(options);
const dataRef = useLatest(data);
const imageElementRef = useRef<Element>();

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
);
};
Loading

0 comments on commit d73836c

Please sign in to comment.