import type { Position } from 'css-box-model'; import { invariant } from '../../invariant'; import type { DimensionMarshal, Callbacks, StartPublishingResult, } from './dimension-marshal-types'; import createPublisher from './while-dragging-publisher'; import type { WhileDraggingPublisher } from './while-dragging-publisher'; import getInitialPublish from './get-initial-publish'; import type { Registry, DroppableEntry, DraggableEntry, Subscriber, Unsubscribe, RegistryEvent, } from '../registry/registry-types'; import type { DroppableId, DroppableDescriptor, LiftRequest, Critical, DraggableDescriptor, } from '../../types'; import { warning } from '../../dev-warning'; interface Collection { critical: Critical; unsubscribe: Unsubscribe; } function shouldPublishUpdate( registry: Registry, dragging: DraggableDescriptor, entry: DraggableEntry, ): boolean { // do not publish updates for the critical draggable if (entry.descriptor.id === dragging.id) { return false; } // do not publish updates for draggables that are not of a type that we care about if (entry.descriptor.type !== dragging.type) { return false; } const home: DroppableEntry = registry.droppable.getById( entry.descriptor.droppableId, ); if (home.descriptor.mode !== 'virtual') { warning(` You are attempting to add or remove a Draggable [id: ${entry.descriptor.id}] while a drag is occurring. This is only supported for virtual lists. See https://github.com/hello-pangea/dnd/blob/main/docs/patterns/virtual-lists.md `); return false; } return true; } export default (registry: Registry, callbacks: Callbacks) => { let collection: Collection | null = null; const publisher: WhileDraggingPublisher = createPublisher({ callbacks: { publish: callbacks.publishWhileDragging, collectionStarting: callbacks.collectionStarting, }, registry, }); const updateDroppableIsEnabled = (id: DroppableId, isEnabled: boolean) => { invariant( registry.droppable.exists(id), `Cannot update is enabled flag of Droppable ${id} as it is not registered`, ); // no need to update the application state if a collection is not occurring if (!collection) { return; } // At this point a non primary droppable dimension might not yet be published // but may have its enabled state changed. For now we still publish this change // and let the reducer exit early if it cannot find the dimension in the state. callbacks.updateDroppableIsEnabled({ id, isEnabled }); }; const updateDroppableIsCombineEnabled = ( id: DroppableId, isCombineEnabled: boolean, ) => { // no need to update if (!collection) { return; } invariant( registry.droppable.exists(id), `Cannot update isCombineEnabled flag of Droppable ${id} as it is not registered`, ); callbacks.updateDroppableIsCombineEnabled({ id, isCombineEnabled }); }; const updateDroppableScroll = (id: DroppableId, newScroll: Position) => { // no need to update the application state if a collection is not occurring if (!collection) { return; } invariant( registry.droppable.exists(id), `Cannot update the scroll on Droppable ${id} as it is not registered`, ); callbacks.updateDroppableScroll({ id, newScroll }); }; const scrollDroppable = (id: DroppableId, change: Position) => { if (!collection) { return; } registry.droppable.getById(id).callbacks.scroll(change); }; const stopPublishing = () => { // This function can be called defensively if (!collection) { return; } // Stop any pending dom collections or publish publisher.stop(); // Tell all droppables to stop watching scroll // all good if they where not already listening const home: DroppableDescriptor = collection.critical.droppable; registry.droppable .getAllByType(home.type) .forEach((entry: DroppableEntry) => entry.callbacks.dragStopped()); // Unsubscribe from registry updates collection.unsubscribe(); // Finally - clear our collection collection = null; }; const subscriber: Subscriber = (event: RegistryEvent) => { invariant( collection, 'Should only be subscribed when a collection is occurring', ); // The dragging item can be add and removed when using a clone // We do not publish updates for the critical item const dragging: DraggableDescriptor = collection.critical.draggable; if (event.type === 'ADDITION') { if (shouldPublishUpdate(registry, dragging, event.value)) { publisher.add(event.value); } } if (event.type === 'REMOVAL') { if (shouldPublishUpdate(registry, dragging, event.value)) { publisher.remove(event.value); } } }; const startPublishing = (request: LiftRequest): StartPublishingResult => { invariant( !collection, 'Cannot start capturing critical dimensions as there is already a collection', ); const entry: DraggableEntry = registry.draggable.getById( request.draggableId, ); const home: DroppableEntry = registry.droppable.getById( entry.descriptor.droppableId, ); const critical: Critical = { draggable: entry.descriptor, droppable: home.descriptor, }; const unsubscribe = registry.subscribe(subscriber); collection = { critical, unsubscribe, }; return getInitialPublish({ critical, registry, scrollOptions: request.scrollOptions, }); }; const marshal: DimensionMarshal = { // Droppable changes updateDroppableIsEnabled, updateDroppableIsCombineEnabled, scrollDroppable, updateDroppableScroll, // Entry startPublishing, stopPublishing, }; return marshal; };