Skip to content

Commit

Permalink
Allow partial recomputation of droppable rects
Browse files Browse the repository at this point in the history
Also renamed `recomputeLayouts` to `recomputeRects` and `willRecomputeLayouts` to `willRecomputeRects`
  • Loading branch information
clauderic committed Jan 6, 2022
1 parent f4fdd01 commit dfa11b0
Show file tree
Hide file tree
Showing 5 changed files with 63 additions and 61 deletions.
12 changes: 6 additions & 6 deletions packages/core/src/components/DndContext/DndContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,8 @@ export const DndContext = memo(function DndContext({
}, [droppableContainers]);
const {
rectMap: droppableRects,
recomputeLayouts,
willRecomputeLayouts,
recomputeRects,
willRecomputeRects,
} = useDroppableMeasuring(enabledDroppableContainers, {
dragging: isDragging,
dependencies: [translate.x, translate.y],
Expand Down Expand Up @@ -599,10 +599,10 @@ export const DndContext = memo(function DndContext({
droppableContainers,
droppableRects,
over,
recomputeLayouts,
recomputeRects,
scrollableAncestors,
scrollableAncestorRects,
willRecomputeLayouts,
willRecomputeRects,
windowRect,
};

Expand All @@ -621,10 +621,10 @@ export const DndContext = memo(function DndContext({
droppableContainers,
droppableRects,
over,
recomputeLayouts,
recomputeRects,
scrollableAncestors,
scrollableAncestorRects,
willRecomputeLayouts,
willRecomputeRects,
windowRect,
]);

Expand Down
92 changes: 48 additions & 44 deletions packages/core/src/hooks/utilities/useDroppableMeasuring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {useLazyMemo} from '@dnd-kit/utilities';

import {Rect, getTransformAgnosticClientRect} from '../../utilities/rect';
import type {DroppableContainer, RectMap} from '../../store/types';
import type {ClientRect} from '../../types';
import type {ClientRect, UniqueIdentifier} from '../../types';

interface Arguments {
dragging: boolean;
Expand Down Expand Up @@ -41,14 +41,21 @@ export function useDroppableMeasuring(
containers: DroppableContainer[],
{dragging, dependencies, config}: Arguments
) {
const [willRecomputeLayouts, setWillRecomputeLayouts] = useState(false);
const [recomputeIds, setRecomputeIds] = useState<UniqueIdentifier[] | null>(
null
);
const willRecomputeRects = recomputeIds != null;
const {frequency, measure, strategy} = {
...defaultConfig,
...config,
};
const containersRef = useRef(containers);
const recomputeLayouts = useCallback(() => setWillRecomputeLayouts(true), []);
const recomputeLayoutsTimeoutId = useRef<NodeJS.Timeout | null>(null);
const recomputeRects = useCallback(
(ids: UniqueIdentifier[] = []) =>
setRecomputeIds((value) => (value ? value.concat(ids) : ids)),
[]
);
const recomputeRectsTimeoutId = useRef<NodeJS.Timeout | null>(null);
const disabled = isDisabled();
const rectMap = useLazyMemo<RectMap>(
(previousValue) => {
Expand All @@ -60,70 +67,89 @@ export function useDroppableMeasuring(
!previousValue ||
previousValue === defaultValue ||
containersRef.current !== containers ||
willRecomputeLayouts
recomputeIds != null
) {
const rectMap: RectMap = new Map();

for (let container of containers) {
if (!container) {
continue;
}

if (
recomputeIds &&
recomputeIds.length > 0 &&
!recomputeIds.includes(container.id) &&
container.rect.current
) {
// This container does not need to be recomputed
rectMap.set(container.id, container.rect.current);
continue;
}

const node = container.node.current;
const rect = node ? new Rect(measure(node), node) : null;

container.rect.current = node ? new Rect(measure(node), node) : null;
container.rect.current = rect;

if (rect) {
rectMap.set(container.id, rect);
}
}

return createRectMap(containers);
return rectMap;
}

return previousValue;
},
[containers, dragging, disabled, measure, willRecomputeLayouts]
[containers, dragging, disabled, measure, recomputeIds]
);

useEffect(() => {
containersRef.current = containers;
}, [containers]);

useEffect(() => {
if (willRecomputeLayouts) {
setWillRecomputeLayouts(false);
}
}, [willRecomputeLayouts]);

useEffect(
function recompute() {
if (disabled) {
return;
}

requestAnimationFrame(recomputeLayouts);
requestAnimationFrame(() => recomputeRects());
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[dragging, disabled]
);

useEffect(() => {
if (willRecomputeRects) {
setRecomputeIds(null);
}
}, [willRecomputeRects]);

useEffect(
function forceRecomputeLayouts() {
if (
disabled ||
typeof frequency !== 'number' ||
recomputeLayoutsTimeoutId.current !== null
recomputeRectsTimeoutId.current !== null
) {
return;
}

recomputeLayoutsTimeoutId.current = setTimeout(() => {
recomputeLayouts();
recomputeLayoutsTimeoutId.current = null;
recomputeRectsTimeoutId.current = setTimeout(() => {
recomputeRects();
recomputeRectsTimeoutId.current = null;
}, frequency);
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[frequency, disabled, recomputeLayouts, ...dependencies]
[frequency, disabled, recomputeRects, ...dependencies]
);

return {
rectMap,
recomputeLayouts,
willRecomputeLayouts,
recomputeRects,
willRecomputeRects,
};

function isDisabled() {
Expand All @@ -137,25 +163,3 @@ export function useDroppableMeasuring(
}
}
}

function createRectMap(containers: DroppableContainer[] | null): RectMap {
const rectMap: RectMap = new Map();

if (containers) {
for (const container of containers) {
if (!container) {
continue;
}

const {id, rect} = container;

if (rect.current == null) {
continue;
}

rectMap.set(id, rect.current);
}
}

return rectMap;
}
4 changes: 2 additions & 2 deletions packages/core/src/store/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const Context = createContext<DndContextDescriptor>({
},
scrollableAncestors: [],
scrollableAncestorRects: [],
recomputeLayouts: noop,
recomputeRects: noop,
windowRect: null,
willRecomputeLayouts: false,
willRecomputeRects: false,
});
4 changes: 2 additions & 2 deletions packages/core/src/store/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export interface DndContextDescriptor {
};
scrollableAncestors: Element[];
scrollableAncestorRects: ClientRect[];
recomputeLayouts(): void;
willRecomputeLayouts: boolean;
recomputeRects(ids: UniqueIdentifier[]): void;
willRecomputeRects: boolean;
windowRect: ClientRect | null;
}
12 changes: 5 additions & 7 deletions packages/sortable/src/components/SortableContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ export function SortableContext({
dragOverlay,
droppableRects,
over,
recomputeLayouts,
willRecomputeLayouts,
recomputeRects,
willRecomputeRects,
} = useDndContext();
const containerId = useUniqueId(ID_PREFIX, id);
const useDragOverlay = Boolean(dragOverlay.rect !== null);
Expand All @@ -65,7 +65,6 @@ export function SortableContext({
const isDragging = active != null;
const wasDragging = useRef(false);
const activeIndex = active ? items.indexOf(active.id) : -1;
const isSorting = activeIndex !== -1;
const overIndex = over ? items.indexOf(over.id) : -1;
const previousItemsRef = useRef(items);
const sortedRects = getSortedRects(items, droppableRects);
Expand All @@ -74,11 +73,10 @@ export function SortableContext({
(overIndex !== -1 && activeIndex === -1) || itemsHaveChanged;

useIsomorphicLayoutEffect(() => {
if (itemsHaveChanged && isSorting && !willRecomputeLayouts) {
// To-do: Add partial recomputation of only subset of rects
recomputeLayouts();
if (itemsHaveChanged && isDragging && !willRecomputeRects) {
recomputeRects(items);
}
}, [itemsHaveChanged, isSorting, recomputeLayouts, willRecomputeLayouts]);
}, [itemsHaveChanged, items, isDragging, recomputeRects, willRecomputeRects]);

useEffect(() => {
previousItemsRef.current = items;
Expand Down

0 comments on commit dfa11b0

Please sign in to comment.