Skip to content

Commit

Permalink
feat: select deepest candidate when using nested droppables
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisvxd committed Apr 6, 2024
1 parent 9296074 commit e531a89
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 2 deletions.
3 changes: 3 additions & 0 deletions src/state/droppable/get-droppable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ interface Args {
page: BoxModel;
closest?: Closest | null;
transform: Transform | null;
parents: DroppableDescriptor[];
}

export default ({
Expand All @@ -44,6 +45,7 @@ export default ({
page,
closest,
transform,
parents,
}: Args): DroppableDimension => {
const frame: Scrollable | null = (() => {
if (!closest) {
Expand Down Expand Up @@ -98,6 +100,7 @@ export default ({
frame,
subject,
transform,
parents,
};

return dimension;
Expand Down
72 changes: 70 additions & 2 deletions src/state/get-droppable-over.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,67 @@ function getFurthestAway({
return sorted[0] ? sorted[0].id : null;
}

/**
* normalizeFamilies
*
* Groups all items that share a common root `parent`, and selects the deepest item
* in that group that contains the center point of the dragged item to represent
* the "family".
*/
function normalizeFamilies(
pageBorderBox: Rect,
candidates: DroppableDimension[],
) {
const families = candidates.reduce<Record<string, DroppableDimension[]>>(
(acc, candidate) => {
const familyName = candidate.parents[0]?.id || candidate.descriptor.id;
const family = acc[familyName] || [];

const generation = candidate.parents.length;

let chosenCandidateId: string | null = candidate.descriptor.id;

if (family[generation]) {
// Overlapping with 2 items at the same generation. get furthest away.

chosenCandidateId = getFurthestAway({
pageBorderBox,
draggable,
candidates: [candidate, family[generation]],
});
}

if (chosenCandidateId) {
family[generation] = candidate;
}

return {
...acc,
[familyName]: family,
};
},
{},
);

return Object.keys(families).map((familyName) => {
const family = families[familyName];

const reversedFamily = [...family].reverse();

// Get first member of family that contains the draggable
const chosenMember = reversedFamily.find((member) => {
return (
pageBorderBox.center.x < member.page.borderBox.right &&
pageBorderBox.center.x > member.page.borderBox.left &&
pageBorderBox.center.y > member.page.borderBox.top &&
pageBorderBox.center.y < member.page.borderBox.bottom
);
});

return chosenMember || family[0];
});
}

export default function getDroppableOver({
pageBorderBox,
draggable,
Expand Down Expand Up @@ -146,12 +207,19 @@ export default function getDroppableOver({
return candidates[0].descriptor.id;
}

// Multiple options returned
// Select the best candidate from each group that share a common root ancestor
const normalizedCandidates = normalizeFamilies(pageBorderBox, candidates);

// All candidates were in the same family
if (normalizedCandidates.length === 1) {
return normalizedCandidates[0].descriptor.id;
}

// Should only occur with really large items
// Going to use fallback: distance from home
return getFurthestAway({
pageBorderBox,
draggable,
candidates,
candidates: normalizedCandidates,
});
}
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ export interface DroppableDimension {
// what is visible through the frame
subject: DroppableSubject;
transform: Transform | null;
parents: DroppableDescriptor[];
}
export interface DraggableLocation {
droppableId: DroppableId;
Expand Down
32 changes: 32 additions & 0 deletions src/view/use-droppable-publisher/get-dimension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import getIframeOffset from '../iframe/get-iframe-offset';
import { applyOffsetBox } from '../iframe/apply-offset';
import { Transform, applyTransformBox, getTransform } from '../transform';
import { Offset } from '../iframe/offset-types';
import { prefix } from '../data-attributes';

const getClient = (
targetRef: HTMLElement,
Expand Down Expand Up @@ -96,6 +97,34 @@ interface Args {
transform: Transform | null;
}

const getParents = (ref: HTMLElement) => {
const contextId = ref.getAttribute(`${prefix}-droppable-context-id`);

const parentDescriptors: DroppableDescriptor[] = [];

if (!contextId) return [];

let currentEl: HTMLElement | null | undefined = ref;

while (currentEl) {
currentEl = currentEl.parentElement?.closest(
`[${prefix}-droppable-context-id="${contextId}"]`,
);

const id = currentEl?.getAttribute(`${prefix}-droppable-id`);

if (id) {
parentDescriptors.push({
id,
mode: 'standard',
type: 'DEFAULT',
});
}
}

return parentDescriptors;
};

export default ({
ref,
descriptor,
Expand Down Expand Up @@ -132,6 +161,8 @@ export default ({
};
})();

const parents = getParents(ref);

const dimension: DroppableDimension = getDroppableDimension({
descriptor,
isEnabled: !isDropDisabled,
Expand All @@ -142,6 +173,7 @@ export default ({
page,
closest,
transform,
parents,
});

return dimension;
Expand Down

0 comments on commit e531a89

Please sign in to comment.