@hello-pangea/dnd
Version:
Beautiful and accessible drag and drop for lists with React
146 lines (122 loc) • 3.45 kB
text/typescript
import type { Position } from 'css-box-model';
import type {
DraggableId,
DroppableId,
DraggableDescriptor,
Published,
DraggableDimension,
DroppablePublish,
DroppableIdMap,
DraggableIdMap,
} from '../../types';
import type {
DroppableEntry,
Registry,
DraggableEntry,
DraggableEntryMap,
} from '../registry/registry-types';
import * as timings from '../../debug/timings';
import { origin } from '../position';
export interface WhileDraggingPublisher {
add: (entry: DraggableEntry) => void;
remove: (entry: DraggableEntry) => void;
stop: () => void;
}
interface Staging {
additions: DraggableEntryMap;
removals: DraggableIdMap;
modified: DroppableIdMap;
}
interface Callbacks {
publish: (args: Published) => unknown;
collectionStarting: () => unknown;
}
interface Args {
registry: Registry;
callbacks: Callbacks;
}
const clean = (): Staging => ({
additions: {},
removals: {},
modified: {},
});
const timingKey = 'Publish collection from DOM';
export default function createPublisher({
registry,
callbacks,
}: Args): WhileDraggingPublisher {
let staging: Staging = clean();
let frameId: AnimationFrameID | null = null;
const collect = () => {
if (frameId) {
return;
}
callbacks.collectionStarting();
frameId = requestAnimationFrame(() => {
frameId = null;
timings.start(timingKey);
const { additions, removals, modified } = staging;
const added: DraggableDimension[] = Object.keys(additions)
.map(
// Using the origin as the window scroll. This will be adjusted when processing the published values
(id: DraggableId): DraggableDimension =>
registry.draggable.getById(id).getDimension(origin),
)
// Dimensions are not guarenteed to be ordered in the same order as keys
// So we need to sort them so they are in the correct order
.sort(
(a: DraggableDimension, b: DraggableDimension): number =>
a.descriptor.index - b.descriptor.index,
);
const updated: DroppablePublish[] = Object.keys(modified).map(
(id: DroppableId) => {
const entry: DroppableEntry = registry.droppable.getById(id);
const scroll: Position = entry.callbacks.getScrollWhileDragging();
return {
droppableId: id,
scroll,
};
},
);
const result: Published = {
additions: added,
removals: Object.keys(removals),
modified: updated,
};
staging = clean();
timings.finish(timingKey);
callbacks.publish(result);
});
};
const add = (entry: DraggableEntry) => {
const id: DraggableId = entry.descriptor.id;
staging.additions[id] = entry;
staging.modified[entry.descriptor.droppableId] = true;
if (staging.removals[id]) {
delete staging.removals[id];
}
collect();
};
const remove = (entry: DraggableEntry) => {
const descriptor: DraggableDescriptor = entry.descriptor;
staging.removals[descriptor.id] = true;
staging.modified[descriptor.droppableId] = true;
if (staging.additions[descriptor.id]) {
delete staging.additions[descriptor.id];
}
collect();
};
const stop = () => {
if (!frameId) {
return;
}
cancelAnimationFrame(frameId);
frameId = null;
staging = clean();
};
return {
add,
remove,
stop,
};
}