import { Meta, Canvas } from '@storybook/addon-docs/blocks'
import * as RowDragStories from './rowDrag.stories'

<Meta title="pv-grid/Components/Grid/Row Drag" />

# Row Drag

The `rowDrag` property on the grid takes an object with at least an `onDrop` callback, and includes several other optional properties to control the behavior.

- `onDrop` - **Required**  
  A callback that will be called when a drop operation is successful. You must use this information to update the data you are passing to the grid or there will be no visual change in the grid itself. More details on this callback are [included later in this page](#ondrop---processing-the-dropped-rows).
- `mode` - `default` or `parent`. Defaults to `default`
    - `default` mode allows the dragged row (or rows) to be dropped in between other rows and to be dropped in a new parent or the root
    - `parent` mode allows the dragged row (or rows) to be dropped in a new parent or the root, but prevents dropping between rows.
- `multiple` - `true` or `false`. Defaults to `false`  
  Set to `true` to allow multiple rows to be dragged at once. If both a parent and a child are included in a selection, when the user begins the drag, the children will be excluded from the operation as if they were not selected. This requires the Grid to have `selectionMode` set to `multi`.
- `enableLeafConversion` - `true` or `false`. Defaults to `false`  
  By default, in tree grids, rows can only be dropped on top of existing parent rows. If you want to be able to drop on any row (parent or leaf), you need to set this to `true`. This enables your code to convert a leaf row to a parent row as when the drop occurs.
- `canDrop` - This predicate function can be used to prevent a drop in certain conditions and provide details to the user. More details on this callback are [included later in this page](#candrop---preventing-dropping-when-needed).
- `previewColumnId` - When the row is dragged, a single cell is shown as the overlay preview. By default this cell is rendered for the first visible, non-selection column. You can override this by providing the `id` of a different column.

## Usage

The bare minimum to enable dragging in a grid is the following. If you used this exactly, you'd be able to drag and drop but the drop wouldn't produce a change since there is no actual code in the `onDrop` handler:

```tsx
<Grid
    rows={rows}
    columns={columns}
    rowDrag={{
        onDrop() {
            // Handle drop
        },
    }}
/>
```

## `onDrop` - Processing the dropped rows

This callback provides a single parameter with a number of details about the operation:

- `draggedRowIds` - An array of ids. If `multiple` is false, there will only be one item.
- `draggedRows` - An array of objects with `{ row, rowMeta }`. `row` contains the row data object, and `rowMeta` provides the related meta data for the row if any is present. If `multiple` is false, there will only be one item.
- `targetParentId` - An id or `null`. In a flat grid, or if a row is dropped in the root level, this will be `null`. In the case where `enableLeafConversion` is `true` this may be the id of a row that is not yet a parent, but should become a parent so the `draggedRows` can be placed as children.
- `targetParent` - An object with `{ row, rowMeta }` or `null` (Follows the same rules as `targetParentId` defined above)
- `targetIndex` **\*** - `number` or `null`. This is the new starting index of the `draggedRows` within the `targetParent` or root if the `targetParent` is `null`.
- `resultIds` **\*** - An array of ids or `null`. This is the full list of child ids of the `targetParent` (or root ids if there is no parent) _after_ the drop operation is completed. If there were four rows in a grid with ids `[ 1, 2, 3, 4]`, and the first row was dragged below the second row, this would report `[ 2, 1, 3, 4 ]`
- `droppedAfterId` **\*** - An id or `null`. This is a convenience property for situations where it is easier to drop after an item vs at a specific instance (This approach is more resilient when multiple users can be dragging and dropping rows at the same time.)

**\***: These properties will be non-null when the dragged rows are dropped between an existing row and they won't be populated when mode is `parent` or the rows are dropped onto a parent, but not in a specific location.

### Example of handling `onDrop`

Applying index and hierarchical changes to data can be complicated depending on the original structure of your data and APIs used in your application. The example below is for a simple, flat grid with no external APIs in use.

```tsx
type Fruit = {
    id: number
    name: string
}

const columns: Column<Fruit>[] = [
    {
        id: 'name',
        label: 'Name',
    },
]

const data = new Map<number, Fruit>([
    [1, { id: 1, name: 'Apples' }],
    [2, { id: 2, name: 'Oranges' }],
    [3, { id: 3, name: 'Pears' }],
    [4, { id: 4, name: 'Grapes' }],
])

export function FruitGrid() {
    const [ids, setIds] = React.useState([1, 2, 3, 4])

    return (
        <Grid
            selectionMode="none"
            columns={columns}
            rows={{ ids, data }}
            rowDrag={{
                onDrop({ resultIds }) {
                    if (resultIds !== null) {
                        setIds(resultIds)
                    }
                },
            }}
        />
    )
}
```

<Canvas of={RowDragStories.RowDragSimple} />

## `canDrop` - Preventing dropping when needed

In some cases, there may be certain parents or areas where rows cannot be dropped. This callback enables you to evaluate the potential drop, and respond by informing the grid if the operation is allowed or not.

This predicate function, receives the same exact payload as `onDrop` - but it requires a return value of either `{ allowed: true }` or `{ allowed: false, message: "..." }`. The message is shown directly to the user and should be localized.

If you return an empty message `{ allowed: false, message: "" }`, the drop will still not be allowed but no error state will be shown to the user. Use this only when it should be reasonably obvious to the user why they cannot drop rows in a certain location.

In this contrived example, if a user tries to drag a row into a different parent it will show them an error.

```tsx
canDrop( { draggedRows, targetParentId }) {
    if ( targetParentId !== draggedRows[0].row.parentId ) {
        return {
            allowed: false,
            message: "You cannot change the category of fruits"
        }
    }
    return { allowed: true }
}
```

### Example of restricting drop targets with `canDrop`

In this example, you can change the order of fruits within a section, but not which section they are in. This uses shows a visual message when attempting to drop in a new parent as well as silently preventing dropping on the root. Since the categories are groups, they do not have dragging enabled.

Here is the `canDrop` handler that produces this result:

```tsx
canDrop({ draggedRows, targetParentId }) {
if (targetParentId === null) {
    return {
        allowed: false,
        message: '',
    }
}
if (targetParentId !== draggedRows[0].row.parentId) {
    return {
        allowed: false,
        message: 'You cannot change the category of fruits',
    }
}
return { allowed: true }
}
```

<Canvas of={RowDragStories.RowDragCanDrop} />

## Preventing dragging when needed

In some cases, you'll want to prevent some rows from being dragged at all. That is not configured on the `rowDrag` object. Instead, you'll need to pass `preventDrag: true` to [the metadata for the row](/docs/pv-grid-components-grid-row-metadata--docs). Rows marked as `type: group` will have dragging disabled by default.
