import { useMemo, useRef } from 'react'; import type { MoveOptions } from './useMove'; import { useEffectEvent } from './useEffectEvent'; export type ExtendMoveDataEventHandler unknown> = ( evt: Parameters[0], data: Readonly, ) => ReturnType; export type ExtendMoveOptions< D, O extends { onMove?: (...args: never[]) => void; onMoveEnd?: (...args: never[]) => void; onMoveStart?: (...args: never[]) => void; } = MoveOptions, > = Omit & { onMove?: ExtendMoveDataEventHandler>; onMoveEnd?: ExtendMoveDataEventHandler>; onMoveStart?: ExtendMoveDataEventHandler>; }; export type ExtendMoveDataConverter unknown> = ( data: Readonly[1] & { lastData: Readonly; startData: Readonly }>, evt: Parameters[0], ) => D; export interface MoveDataOptions extends ExtendMoveOptions> { // Any data. data: Readonly; // Function to convert from drag operation to data. toData: ExtendMoveDataConverter['onMove']>>; } export type MoveDataEventHandler = ExtendMoveDataEventHandler< D, NonNullable['onMove']> >; export type MoveDataConverter = ExtendMoveDataConverter< D, NonNullable['onMove']> >; export interface MoveDataResult { // Options given to useMove. moveOptions: MoveOptions; } export function useMoveData({ data, onMove: rawOnMove, onMoveEnd: rawOnMoveEnd, onMoveStart: rawOnMoveStart, toData, ...options }: MoveDataOptions): MoveDataResult { const state = useRef<{ data: Readonly; lastData: Readonly | null; startData: Readonly | null; }>({ data, lastData: null, startData: null, }); const onMoveStart = useEffectEvent(rawOnMoveStart); const onMove = useEffectEvent(rawOnMove); const onMoveEnd = useEffectEvent(rawOnMoveEnd); const moveOptions = useMemo>( () => onMoveStart || onMove || onMoveEnd ? { onMove(evt, moveData): void { const lastData = toData( /* eslint-disable @typescript-eslint/no-non-null-assertion */ { lastData: state.current.lastData!, startData: state.current.startData!, ...moveData, }, /* eslint-enable @typescript-eslint/no-non-null-assertion */ evt, ); state.current.lastData = lastData; onMove?.(evt, lastData); }, onMoveEnd(evt, moveData): void { const lastData = toData( /* eslint-disable @typescript-eslint/no-non-null-assertion */ { lastData: state.current.lastData!, startData: state.current.startData!, ...moveData, }, /* eslint-enable @typescript-eslint/no-non-null-assertion */ evt, ); state.current.startData = null; state.current.lastData = null; onMoveEnd?.(evt, lastData); }, onMoveStart(evt, moveData): void { evt.stopPropagation(); const startData = state.current.data; const lastData = toData({ lastData: startData, startData, ...moveData }, evt); state.current.startData = startData; state.current.lastData = lastData; onMoveStart?.(evt, lastData); }, } : {}, [toData, onMoveStart, onMove, onMoveEnd], ); state.current.data = data; return { moveOptions: { ...moveOptions, ...options } }; }