import "./reorder_list.css"; import { type PointerEvent, type ReactNode } from "react"; import type * as React from "react"; import { useRender } from "@base-ui/react/use-render"; import { type StyleProps } from "./style_props"; /** Spread onto the grip the row is dragged by — `ReorderItem` does it for you. */ export interface ReorderDragHandleProps { onPointerDown: (event: PointerEvent) => void; onPointerMove: (event: PointerEvent) => void; onPointerUp: (event: PointerEvent) => void; onPointerCancel: (event: PointerEvent) => void; } /** * Everything a row needs to be reordered, handed to `renderItem` as one bundle * and passed straight to `ReorderItem`. * * The keyboard moves are not an alternative affordance bolted on beside the * drag — they are the ONLY way to reorder without a pointer, since a drag has no * keyboard equivalent (WCAG 2.1.1), so every consumer of this list is handed * them by construction. Render them: a row with a grip and no move pair is the * finding, not a simplification. */ export interface ReorderControls { /** Null where the list reorders by BUTTONS — there is no drag to grip, and the * row draws the rail instead. */ dragHandleProps: ReorderDragHandleProps | null; /** Null at the top of the list — the control renders disabled. */ moveUp: (() => void) | null; /** Null at the bottom. */ moveDown: (() => void) | null; } /** What `renderItem` is handed for one row. */ export interface ReorderRow { item: T; index: number; isDragging: boolean; reorder: ReorderControls; } interface ReorderListBase extends StyleProps { testID?: string; /** The rows, in the order they are shown. */ data: T[]; /** Stable identity per row — a row keyed by index loses its place mid-drag. */ keyExtractor: (item: T, index: number) => string; renderItem: (row: ReorderRow) => ReactNode; /** Called with the WHOLE new order, by drag or by keyboard. */ onReorder: (data: T[]) => void; ref?: React.Ref; render?: useRender.RenderProp; } /** * A DRAG list — rows the reader rearranges, each wearing a grip. The row height * is fixed and stated up front because the drag reads it. */ interface ReorderListDragProps extends ReorderListBase { reorder?: "drag"; /** * The gap between rows, in px, and the row's own height. * * Both are MEASUREMENTS rather than rungs because the drag reads them: a drop * target is the pointer's travel divided by one row's total height, so a * number the component can compute with is the only form that works. Take the * gap off the 8-grid (`SPACE`, `@lotics/ui/spacing`) so the list still sits in * the page's rhythm. */ gap?: number; itemHeight: number; /** * How long the grip is held before the drag starts, in ms. The default holds * the row still long enough that a press meant as a click is not a drag; pass * 0 where the grip does nothing else. */ activationDelay?: number; } /** * A BUTTONS list — an ordered run whose order IS the data (a route's stops, an * approval chain), reordered by the pair on each row and drawn as one connected * rail through its positions. * * It takes none of the drag's measurements: rows size to their content, and the * list carries no gap, because the rail has to reach the next row's segment. */ interface ReorderListButtonsProps extends ReorderListBase { reorder: "buttons"; gap?: never; itemHeight?: never; activationDelay?: never; } export type ReorderListProps = ReorderListDragProps | ReorderListButtonsProps; /** * A vertical list whose ORDER the reader sets — by dragging a grip, or from the * pair on each row, through the one `reorder` bundle every row is handed. * * `reorder` picks the affordance, and with it the drawing: a drag list's rows * render in data order and move by transform, so the list never reflows * mid-gesture — the dragged row tracks the pointer 1:1 and the rows it passes * slide one whole slot aside, which makes the opened gap the drop preview. A * `buttons` list draws the connected rail instead and sizes each row to its * content, because a run whose order IS the data has no fixed row height to * measure a drag against. * * It owns no data. `onReorder` is called with the whole new array and the caller * persists it; nothing moves until the caller's own state comes back. Every row * is reorderable by construction — a read-only ordered run is a plain list, and * an activity feed is a `Timeline`. */ export declare function ReorderList(props: ReorderListProps): React.ReactElement>; export {};