import { createMachine, assign, send, spawn, actions } from 'xstate'; const { choose } = actions; import { debounceMachine } from './debounce.machine'; import type { BehaviorSubject } from 'rxjs'; export interface DragDropStateScaffold { idle: any; dragging: { initial: string, states: { inside: any, outside: any } }; } export type DragDropContext = { debounceMachine: any; broadcast: BehaviorSubject, itemBeingDragged: any; itemBelow: any; dx: number; dy: number; pointerX: number; pointerY: number; subscribers: { name: string, action: Function }[]; items: any[] } export type DragDropState = { value: 'idle'; context: DragDropContext; } | { value: 'dragging'; context: DragDropContext; }; export const dragDropMachine = createMachine( { tsTypes: {} as import("./dragDrop.machine.typegen").Typegen0, schema: { context: {} as DragDropContext, events: {} as { type: 'LOAD_SUBSCRIBERS'; items: string[] } | { type: 'PICK'; item: any, x: number, y: number } | { type: 'START_DRAG', value: any } | { type: 'MOVE'; x: number, y: number } | { type: 'ENTER'; item: any } | { type: 'LEAVE'; item: any } | { type: 'DROP' } | { type: 'UNPICK' } }, context: { debounceMachine: null, itemBeingDragged: null, itemBelow: null, broadcast: null, dx: 0, dy: 0, pointerX: 0, pointerY: 0, subscribers: [], items: [] }, id: 'dragAndDrop', initial: 'idle', states: { idle: { entry: [ 'reset', 'assignDebounceMachine', ], on: { PICK: { actions: [ 'startDebounce' ] }, DROP: { actions: [ 'reset', 'stopDebounce' ] }, START_DRAG: { target: 'dragging', actions: [ 'assignPicked' ], cond: 'eventHasValue' } } }, dragging: { initial: 'outside', states: { inside: { entry: [ 'assignEnter' ], on: { LEAVE: { target: 'outside', actions: [ 'assignLeave' ] }, DROP: { target: 'outside', actions: [ 'reportToParent', 'unpick' ] } } }, outside: { on: { ENTER: { target: 'inside', cond: 'itemIsDraggedItem' }, DROP: { actions: [ 'unpick' ] } } } }, on: { MOVE: { actions: [ 'assignMove' ] }, UNPICK: { target: 'idle' } } } } }, { guards: { eventHasValue: (context, event) => !!event.value, itemIsDraggedItem: (context, event) => context.itemBeingDragged !== !!event.item }, actions: { assignDebounceMachine: assign({ debounceMachine: (context, event) => spawn( debounceMachine.withContext({ ...debounceMachine.context, id: 'debounceMachine', debounceFor: 2000, action: { name: 'START_DRAG', payload: 'value'} }), { name: 'debounceMachine' } ) }), assignPicked: assign({ itemBeingDragged: (context, event) => event.value.item, pointerX: (context, event) => event.value.x, pointerY: (context, event) => event.value.y }), assignMove: assign({ dx: (context, event ) => event.x - context.pointerX, dy: (context, event ) => event.y - context.pointerY }), assignEnter: assign({ itemBelow: (context, event ) => event.item }), assignLeave: assign({ itemBelow: (context, event ) => event.item }), startDebounce: send( (context) => ({ type: 'GO', value: event }), { to: (context) => context.debounceMachine } ), stopDebounce: send( (context) => ({ type: 'GO', value: null }), { to: (context) => context.debounceMachine } ), reset: assign({ itemBeingDragged: null, itemBelow: null, broadcast: (context, event) => { console.log("in reset: ", context.broadcast); !!context.broadcast && (context.broadcast as any).next({itemBeingDragged: null,itemBelow: null}); return context.broadcast; }, dx: 0, dy: 0, pointerX: 0, pointerY: 0 }), reportToParent: choose([ { cond: (context, event) => !context.broadcast, actions: [ 'notifyParent' ] }, { cond: (context, event) => !!context.broadcast, actions: [ (context, event) => context.broadcast.next({ itemBeingDragged: context.itemBeingDragged, itemBelow: context.itemBelow }) ] } ]), unpick: send('UNPICK'), // notifyParent: sendParent((context: DragDropContext, event: any) => { // return { // type: action.name, // [action.payload]: { // itemBeingDragged: context.itemBeingDragged, // itemBelow: context.itemBelow // } // }; // }) } });