Row Dragging
Learn about row dragging in LyteNyte Grid, with support for external drop zones and multiple row dragging.
LyteNyte Grid provides flexible row dragging that lets users move data through intuitive drag-and-drop interactions. You can drag single or multiple rows within a grid, across multiple grids, or into external drop zones. The grid supplies the mechanics without enforcing default behavior, so you control how drag interactions are handled.
Single Row Dragging
Use the grid.api.useRowDrag hook to add row dragging. This hook returns drag props that you attach
to a drag handle component. It expects a getDragData callback, which must return the drag data in
the following shape:
export interface DragData {readonly siteLocalData?: Record<string, any>;readonly dataTransfer?: Record<string, string>;}
Drop zones must declare which drag data keys they accept. For example, the Row component is a
dropzone. Set its accepted property to specify what it will accept.
The example below shows a basic setup. Pay attention to the marker column's cellRenderer and the
accepted prop on Grid.Row.
Row Dragging
"use client";import { Grid, useClientRowDataSource } from "@1771technologies/lytenyte-pro";import "@1771technologies/lytenyte-pro/grid.css";import { DragDotsSmallIcon } from "@1771technologies/lytenyte-pro/icons";import type { Column } from "@1771technologies/lytenyte-pro/types";import { bankDataSmall } from "@1771technologies/grid-sample-data/bank-data-smaller";import { useId } from "react";type BankData = (typeof bankDataSmall)[number];const columns: Column<BankData>[] = [{ id: "education" },{ id: "marital" },{ id: "age", type: "number" },{ id: "job" },{ id: "balance", type: "number" },{ id: "default" },{ id: "housing" },{ id: "loan" },{ id: "contact" },{ id: "day", type: "number" },{ id: "month" },{ id: "duration" },{ id: "campaign" },{ id: "pdays" },{ id: "previous" },{ id: "poutcome", name: "P Outcome" },{ id: "y" },];export default function RowDragging() {const ds = useClientRowDataSource({data: bankDataSmall,});const grid = Grid.useLyteNyte({gridId: useId(),rowDataSource: ds,columns,columnMarkerEnabled: true,columnMarker: {cellRenderer: (p) => {const drag = p.grid.api.useRowDrag({getDragData: () => {return {siteLocalData: {row: p.rowIndex,},};},onDrop: (p) => {alert(`Dropped row at ${p.state.siteLocalData?.row ?? ""} ${p.moveState.topHalf ? "before" : "after"} row ${p.dropElement.getAttribute("data-ln-rowindex")}`,);},});return (<span {...drag.dragProps}><DragDotsSmallIcon /></span>);},},});const view = grid.view.useValue();return (<div className="lng-grid" style={{ height: 500 }}><Grid.Root grid={grid}><Grid.Viewport><Grid.Header>{view.header.layout.map((row, i) => {return (<Grid.HeaderRow key={i} headerRowIndex={i}>{row.map((c) => {if (c.kind === "group") return null;return (<Grid.HeaderCellkey={c.id}cell={c}className="flex h-full w-full items-center px-2 capitalize"/>);})}</Grid.HeaderRow>);})}</Grid.Header><Grid.RowsContainer><Grid.RowsCenter>{view.rows.center.map((row) => {if (row.kind === "full-width") return null;return (<Grid.Row row={row} key={row.id} accepted={["row"]}>{row.cells.map((c) => {return (<Grid.Cellkey={c.id}cell={c}className="flex h-full w-full items-center px-2 text-sm"/>);})}</Grid.Row>);})}</Grid.RowsCenter></Grid.RowsContainer></Grid.Viewport></Grid.Root></div>);}
Dragging Multiple Rows
Extend row dragging to multiple rows by combining it with Row Selection. Instead of dragging a single
row, you query the selection state in getDragData to include all selected rows.
Row Dragging Multiple
"use client";import { Grid, useClientRowDataSource } from "@1771technologies/lytenyte-pro";import "@1771technologies/lytenyte-pro/grid.css";import { DragDotsSmallIcon } from "@1771technologies/lytenyte-pro/icons";import type { Column } from "@1771technologies/lytenyte-pro/types";import { bankDataSmall } from "@1771technologies/grid-sample-data/bank-data-smaller";import { useId } from "react";type BankData = (typeof bankDataSmall)[number];const columns: Column<BankData>[] = [{ id: "education" },{ id: "marital" },{ id: "age", type: "number" },{ id: "job" },{ id: "balance", type: "number" },{ id: "default" },{ id: "housing" },{ id: "loan" },{ id: "contact" },{ id: "day", type: "number" },{ id: "month" },{ id: "duration" },{ id: "campaign" },{ id: "pdays" },{ id: "previous" },{ id: "poutcome", name: "P Outcome" },{ id: "y" },];export default function RowDraggingMultiple() {const ds = useClientRowDataSource({data: bankDataSmall,});const grid = Grid.useLyteNyte({gridId: useId(),rowDataSource: ds,columns,rowSelectionMode: "multiple",rowSelectionActivator: "single-click",columnMarkerEnabled: true,columnMarker: {cellRenderer: (p) => {const drag = p.grid.api.useRowDrag({getDragData: () => {const allIndices = [...grid.state.rowSelectedIds.get()].map((c) => {return grid.api.rowById(c)?.id;}).filter((c) => c != null);return {siteLocalData: {row: [...new Set([...allIndices, p.row.id])],},};},onDrop: (p) => {alert(`Dropped rows at indices ${p.state.siteLocalData?.row?.join(", ")} ${p.moveState.topHalf ? "before" : "after"} row ${p.dropElement.getAttribute("data-ln-rowindex")}`,);},});return (<span {...drag.dragProps}><DragDotsSmallIcon /></span>);},},});const view = grid.view.useValue();return (<div className="lng-grid" style={{ height: 500 }}><Grid.Root grid={grid}><Grid.Viewport><Grid.Header>{view.header.layout.map((row, i) => {return (<Grid.HeaderRow key={i} headerRowIndex={i}>{row.map((c) => {if (c.kind === "group") return null;return (<Grid.HeaderCellkey={c.id}cell={c}className="flex h-full w-full items-center px-2 capitalize"/>);})}</Grid.HeaderRow>);})}</Grid.Header><Grid.RowsContainer><Grid.RowsCenter>{view.rows.center.map((row) => {if (row.kind === "full-width") return null;return (<Grid.Row row={row} key={row.id} accepted={["row"]}>{row.cells.map((c) => {return (<Grid.Cellkey={c.id}cell={c}className="flex h-full w-full items-center px-2 text-sm"/>);})}</Grid.Row>);})}</Grid.RowsCenter></Grid.RowsContainer></Grid.Viewport></Grid.Root></div>);}
Dragging Between Grids
To drag between grids, use a shared accepted value. For example, setting accepted=["row"] on both
grids allows any drag data with a row property to be dropped onto rows in either grid.
Row Dragging Between Grids
"use client";import { Grid, useClientRowDataSource } from "@1771technologies/lytenyte-pro";import "@1771technologies/lytenyte-pro/grid.css";import { DragDotsSmallIcon } from "@1771technologies/lytenyte-pro/icons";import type { Column } from "@1771technologies/lytenyte-pro/types";import { bankDataSmall } from "@1771technologies/grid-sample-data/bank-data-smaller";import { useId } from "react";type BankData = (typeof bankDataSmall)[number];const columns: Column<BankData>[] = [{ id: "education" },{ id: "marital" },{ id: "age", type: "number" },{ id: "job" },{ id: "balance", type: "number" },{ id: "default" },{ id: "housing" },{ id: "loan" },{ id: "contact" },{ id: "day", type: "number" },{ id: "month" },{ id: "duration" },{ id: "campaign" },{ id: "pdays" },{ id: "previous" },{ id: "poutcome", name: "P Outcome" },{ id: "y" },];export default function RowDraggingBetweenGrids() {const ds = useClientRowDataSource({data: bankDataSmall,});const upper = Grid.useLyteNyte({gridId: useId(),rowDataSource: ds,columns,columnMarkerEnabled: true,columnMarker: {cellRenderer: (p) => {const drag = p.grid.api.useRowDrag({getDragData: () => {return {siteLocalData: {row: p.rowIndex,},};},onDrop: (p) => {alert(`Dropped row at ${p.state.siteLocalData?.row ?? ""} ${p.moveState.topHalf ? "before" : "after"} row ${p.dropElement.getAttribute("data-ln-rowindex")}`,);},});return (<span {...drag.dragProps}><DragDotsSmallIcon /></span>);},},});const lower = Grid.useLyteNyte({gridId: useId(),rowDataSource: ds,columns,columnMarkerEnabled: true,columnMarker: {cellRenderer: (p) => {const drag = p.grid.api.useRowDrag({getDragData: () => {return {siteLocalData: {row: p.rowIndex,},};},onDrop: (p) => {alert(`Dropped row at ${p.state.siteLocalData?.row ?? ""} ${p.moveState.topHalf ? "before" : "after"} row ${p.dropElement.getAttribute("data-ln-rowindex")}`,);},});return (<span {...drag.dragProps}><DragDotsSmallIcon /></span>);},},});const viewUpper = upper.view.useValue();const viewLower = lower.view.useValue();return (<div className="flex flex-col gap-8"><div className="lng-grid" style={{ height: 200 }}><Grid.Root grid={upper}><Grid.Viewport><Grid.Header>{viewUpper.header.layout.map((row, i) => {return (<Grid.HeaderRow key={i} headerRowIndex={i}>{row.map((c) => {if (c.kind === "group") return null;return (<Grid.HeaderCellkey={c.id}cell={c}className="flex h-full w-full items-center px-2 capitalize"/>);})}</Grid.HeaderRow>);})}</Grid.Header><Grid.RowsContainer><Grid.RowsCenter>{viewUpper.rows.center.map((row) => {if (row.kind === "full-width") return null;return (<Grid.Row row={row} key={row.id} accepted={["row"]}>{row.cells.map((c) => {return (<Grid.Cellkey={c.id}cell={c}className="flex h-full w-full items-center px-2 text-sm"/>);})}</Grid.Row>);})}</Grid.RowsCenter></Grid.RowsContainer></Grid.Viewport></Grid.Root></div><div className="lng-grid" style={{ height: 200 }}><Grid.Root grid={lower}><Grid.Viewport><Grid.Header>{viewLower.header.layout.map((row, i) => {return (<Grid.HeaderRow key={i} headerRowIndex={i}>{row.map((c) => {if (c.kind === "group") return null;return (<Grid.HeaderCellkey={c.id}cell={c}className="flex h-full w-full items-center px-2 capitalize"/>);})}</Grid.HeaderRow>);})}</Grid.Header><Grid.RowsContainer><Grid.RowsCenter>{viewLower.rows.center.map((row) => {if (row.kind === "full-width") return null;return (<Grid.Row row={row} key={row.id} accepted={["row"]}>{row.cells.map((c) => {return (<Grid.Cellkey={c.id}cell={c}className="flex h-full w-full items-center px-2 text-sm"/>);})}</Grid.Row>);})}</Grid.RowsCenter></Grid.RowsContainer></Grid.Viewport></Grid.Root></div></div>);}
External Drop Zones
LyteNyte Grid provides a DropWrap component for external drop zones. DropWrap is a simple div
that handles all the necessary drag events and can accept any drag data produced by the grid.
Row Dragging External Drop Zone
"use client";import { DropWrap, Grid, useClientRowDataSource } from "@1771technologies/lytenyte-pro";import "@1771technologies/lytenyte-pro/grid.css";import { DragDotsSmallIcon, DragIcon } from "@1771technologies/lytenyte-pro/icons";import type { Column } from "@1771technologies/lytenyte-pro/types";import { bankDataSmall } from "@1771technologies/grid-sample-data/bank-data-smaller";import { useId, useState } from "react";type BankData = (typeof bankDataSmall)[number];const columns: Column<BankData>[] = [{ id: "education" },{ id: "marital" },{ id: "age", type: "number" },{ id: "job" },{ id: "balance", type: "number" },{ id: "default" },{ id: "housing" },{ id: "loan" },{ id: "contact" },{ id: "day", type: "number" },{ id: "month" },{ id: "duration" },{ id: "campaign" },{ id: "pdays" },{ id: "previous" },{ id: "poutcome", name: "P Outcome" },{ id: "y" },];export default function RowDraggingExternalDropZone() {const ds = useClientRowDataSource({data: bankDataSmall,});const [dropped, setDropped] = useState<number[]>([]);const upper = Grid.useLyteNyte({gridId: useId(),rowDataSource: ds,columns,columnMarkerEnabled: true,columnMarker: {cellRenderer: (p) => {const drag = p.grid.api.useRowDrag({getDragData: () => {return {siteLocalData: {row: p.rowIndex,},};},onDrop: (p) => {setDropped((prev) => [...prev, p.state.siteLocalData?.row as number]);},});return (<span {...drag.dragProps}><DragDotsSmallIcon /></span>);},},});const grid = upper.view.useValue();return (<div className="flex flex-col gap-8"><div className="lng-grid" style={{ height: 200 }}><Grid.Root grid={upper}><Grid.Viewport><Grid.Header>{grid.header.layout.map((row, i) => {return (<Grid.HeaderRow key={i} headerRowIndex={i}>{row.map((c) => {if (c.kind === "group") return null;return (<Grid.HeaderCellkey={c.id}cell={c}className="flex h-full w-full items-center px-2 capitalize"/>);})}</Grid.HeaderRow>);})}</Grid.Header><Grid.RowsContainer><Grid.RowsCenter>{grid.rows.center.map((row) => {if (row.kind === "full-width") return null;return (<Grid.Row row={row} key={row.id}>{row.cells.map((c) => {return (<Grid.Cellkey={c.id}cell={c}className="flex h-full w-full items-center px-2 text-sm"/>);})}</Grid.Row>);})}</Grid.RowsCenter></Grid.RowsContainer></Grid.Viewport></Grid.Root></div><DropWrapaccepted={["row"]}className="data-[ln-can-drop=true]:border-ln-primary-50 border border-dashed"style={{ height: 200 }}>{dropped.length === 0 && (<div className="flex h-full w-full items-center justify-center"><DragIcon width={100} height={100} /></div>)}{dropped.map((c, i) => {return (<div className="flex items-center px-2 py-1" key={i}>Value: {c}</div>);})}</DropWrap></div>);}
Row Full Width
Create rows that span the entire grid viewport width in LyteNyte Grid, ideal for section headers, summary rows, or special content areas.
Cell Renderers
Customize cell display in LyteNyte Grid using React components directly or through registered renderers to control appearance and behavior.