[English](./README.md) | 简体中文

# @ray-js/drag-list

[![latest](https://img.shields.io/npm/v/@ray-js/drag-list/latest.svg)](https://www.npmjs.com/package/@ray-js/drag-list) [![download](https://img.shields.io/npm/dt/@ray-js/drag-list.svg)](https://www.npmjs.com/package/@ray-js/drag-list)

> 拖拽排序列表

## 实现说明

- 列表由 Ray/小程序自定义组件实现：父级 `Drag` 负责测量子项并协调排序，每一行对应 `DragItem`。
- 拖拽触摸逻辑在 `sjs` 中处理；页面需配置 `disableScroll: true`，避免页面滚动与拖拽手势冲突。
- `keyId` 须指向列表项上的稳定唯一字段，且每个 `DragItem` 的 `id` 须与该字段一致。

## 安装

```sh
$ npm install @ray-js/drag-list
// 或者
$ yarn add @ray-js/drag-list
```

## 使用

使用此组件需要禁用小程序默认的页面滚动，在 `index.config.ts` 内增加 `disableScroll: true`

```ts
export default {
  backgroundColor: '@bgColor',
  navigationBarBackgroundColor: '@navBgColor',
  navigationBarTextStyle: 'white',
  navigationBarTitleText: 'Home',
  navigationStyle: 'custom',
  disableScroll: true,
};
```

`package.json` 中的 `@ray-js/ray` 和 `@ray-js/cli` 也需要升级到 1.7.0 以上版本：

```json
{
  "@ray-js/cli": "^1.7.11",
  "@ray-js/ray": "^1.7.11"
}
```

### 基础用法 `v2.0.0`

插槽插入 `dragIconNode` 为拖拽节点，只有手指在此节点上交互才能触发拖拽

```tsx
import React, { useState } from 'react';
import { Drag, DragItem } from '@ray-js/drag-list';
import { ScrollView, Text, View } from '@ray-js/ray';
import styles from './index.module.less';

const dataList = [
  { name: 'item1', idx: 'key1' },
  { name: 'item2', idx: 'key2' },
  { name: 'item3', idx: 'key3' },
  { name: 'item4', idx: 'key4' },
  { name: 'item5', idx: 'key5' },
  { name: 'item6', idx: 'key6' },
];
const DragList = () => {
  const [list, setList] = useState(dataList);
  const [scrollEnabled, setScrollEnabled] = useState(true);
  return (
    <ScrollView
      scrollY={scrollEnabled}
      style={{
        display: 'flex',
        width: '100%',
        height: '300px',
        flexDirection: 'column',
        background: 'rgba(244, 244, 246)',
      }}
    >
      <Drag
        list={list}
        touchStart={() => {
          setScrollEnabled(false);
        }}
        activeClassName={styles.active}
        keyId="idx"
        dragStartDelay={300}
        style={{
          height: (108 + 20) * list.length,
          padding: '0 16px',
        }}
        handleSortEnd={data => {
          setScrollEnabled(true);
          data?.detail && setList([...data.detail] as typeof dataList);
        }}
      >
        {list.map(item => (
          <DragItem
            item={item}
            key={item.idx}
            id={item.idx}
            dragIconNode={
              <View
                style={{
                  backgroundColor: 'red',
                  width: '20px',
                  height: '20px',
                }}
              />
            }
          >
            <View
              style={{
                height: 108,
                width: '100%',
                background: 'white',
                borderRadius: 32,
                marginBottom: 20,
                display: 'flex',
                paddingLeft: 24,
                alignItems: 'center',
                color: 'black',
              }}
            >
              <Text>{item.name}</Text>
            </View>
          </DragItem>
        ))}
      </Drag>
    </ScrollView>
  );
};
export default DragList;
```

### 多列用法 `v2.1.0`

`multipleCol` 属性开启多列用法，内部会采用 flex 布局进行多列排布;也可以配合 `bodyDrag` 属性实现整个节点都可以触发拖拽。

```tsx
import React, { useState } from 'react';
import { Drag, DragItem } from '@ray-js/drag-list';
import { ScrollView, Text, View } from '@ray-js/ray';
import styles from './index.module.less';

const dataList = [
  { name: 'item1', idx: 'key1' },
  { name: 'item2', idx: 'key2' },
  { name: 'item3', idx: 'key3' },
  { name: 'item4', idx: 'key4' },
  { name: 'item5', idx: 'key5' },
  { name: 'item6', idx: 'key6' },
];
const DragList = () => {
  const [list, setList] = useState(dataList);
  const [scrollEnabled, setScrollEnabled] = useState(true);
  return (
    <ScrollView
      scrollY={scrollEnabled}
      style={{
        display: 'flex',
        width: '100%',
        height: 500,
        flexDirection: 'column',
        background: 'rgba(244, 244, 246)',
      }}
    >
      <Drag
        list={list}
        touchStart={() => {
          setScrollEnabled(false);
        }}
        bodyDrag
        dragStartDelay={100}
        multipleCol
        activeClassName={styles.active}
        keyId="idx"
        style={{
          height: (150 + 20) * 2,
          padding: '0 16px',
        }}
        handleSortEnd={data => {
          setScrollEnabled(true);
          data?.detail && setList([...data.detail] as typeof dataList);
        }}
      >
        {list.map(item => (
          <DragItem item={item} key={item.idx} id={item.idx}>
            <View
              style={{
                height: 150,
                width: 200,
                background: 'white',
                borderRadius: 32,
                marginBottom: 20,
                display: 'flex',
                justifyContent: 'center',
                alignItems: 'center',
                color: 'black',
              }}
            >
              {item.name}
            </View>
          </DragItem>
        ))}
      </Drag>
    </ScrollView>
  );
};
export default DragList;
```

### 不等高用法 `v2.2.0`

当列表项高度不一致时，需要让每个 `DragItem` 的高度与数据保持一致，并基于当前列表动态计算拖拽容器高度。

```tsx
import React, { useState } from 'react';
import { Drag, DragItem } from '@ray-js/drag-list';
import { ScrollView, Text, View } from '@ray-js/ray';
import styles from './index.module.less';

const ITEM_GAP = 20;
const variableHeightList = [
  { name: 'item1 (h60)', idx: 'v1', height: 60 },
  { name: 'item2 (h140)', idx: 'v2', height: 140 },
  { name: 'item3 (h80)', idx: 'v3', height: 80 },
  { name: 'item4 (h200)', idx: 'v4', height: 200 },
  { name: 'item5 (h60)', idx: 'v5', height: 60 },
  { name: 'item6 (h120)', idx: 'v6', height: 120 },
  { name: 'item7 (h90)', idx: 'v7', height: 90 },
];

const DragList = () => {
  const [variableList, setVariableList] = useState(variableHeightList);
  const [scrollEnabled, setScrollEnabled] = useState(true);

  return (
    <ScrollView
      scrollY={scrollEnabled}
      style={{
        display: 'flex',
        width: '100%',
        height: '400px',
        flexDirection: 'column',
        background: 'rgba(244, 244, 246)',
      }}
    >
      <Drag
        list={variableList}
        touchStart={() => {
          setScrollEnabled(false);
        }}
        activeClassName={styles.active}
        keyId="idx"
        dragStartDelay={300}
        style={{
          height:
            variableList.reduce((sum, item) => sum + item.height, 0) +
            ITEM_GAP * variableList.length,
          padding: '0 16px',
        }}
        handleSortEnd={data => {
          setScrollEnabled(true);
          data?.detail && setVariableList([...data.detail] as typeof variableHeightList);
        }}
      >
        {variableList.map(item => (
          <DragItem
            item={item}
            key={item.idx}
            id={item.idx}
            dragIconNode={
              <View
                style={{
                  backgroundColor: 'red',
                  width: '20px',
                  height: '20px',
                }}
              />
            }
          >
            <View
              style={{
                height: item.height,
                width: '100%',
                background: 'white',
                borderRadius: 16,
                marginBottom: ITEM_GAP,
                display: 'flex',
                paddingLeft: 24,
                alignItems: 'center',
                color: 'black',
              }}
            >
              <Text>{item.name}</Text>
            </View>
          </DragItem>
        ))}
      </Drag>
    </ScrollView>
  );
};

export default DragList;
```