Filter invalid nested drag targets before collision selection
This commit is contained in:
1 parent
88207cdd48
commit
ca2fcb4016
3 files changed
+27
-4
No files matched your search
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@wrnexus/dnd",
|
||||
"version": "0.1.3",
|
||||
"version": "0.1.4",
|
||||
"type": "module",
|
||||
"description": "Stable accessible drag-and-drop primitives for WRNexus.",
|
||||
"exports": {
|
||||
|
||||
@@ -121,7 +121,15 @@ export function createDndManager(options: DndManagerOptions): DndManager {
|
||||
element: registration.element,
|
||||
handle: registration.handle,
|
||||
type: registration.type,
|
||||
accept: registration.accepts,
|
||||
accept: (source) => {
|
||||
const origin = entries.get(source.id)?.current.element;
|
||||
// A nested ancestor overlaps its own descendants and must not steal
|
||||
// their next keyboard/pointer target just because its box is larger.
|
||||
if (source.id !== registration.id && origin && registration.element.contains?.(origin)) return false;
|
||||
if (registration.accepts && !registration.accepts.some(type => type === source.type)) return false;
|
||||
const target = entries.get(registration.id)?.sortable;
|
||||
return options.accept?.([source.id], { group: target?.group ?? registration.group, index: target?.index ?? registration.index }) ?? true;
|
||||
},
|
||||
disabled: registration.disabled,
|
||||
transition: reducedMotion ? null : undefined,
|
||||
data: { ...registration.data, direction },
|
||||
|
||||
@@ -12,7 +12,7 @@ const compiled = new Bun.Transpiler({ loader: "ts" })
|
||||
function fixture(overrides: Partial<DndManagerOptions> = {}) {
|
||||
const listeners: Record<string, (event: any) => any> = {};
|
||||
const announcements: DndAnnouncement[] = [];
|
||||
const instances: Array<{ destroyed: number }> = [];
|
||||
const instances: Array<{ destroyed: number; input: any }> = [];
|
||||
let managerDestructions = 0;
|
||||
class Backend {
|
||||
monitor = {
|
||||
@@ -26,7 +26,7 @@ function fixture(overrides: Partial<DndManagerOptions> = {}) {
|
||||
}
|
||||
class Sortable {
|
||||
destroyed = 0;
|
||||
constructor() {
|
||||
constructor(public input: any) {
|
||||
instances.push(this);
|
||||
}
|
||||
destroy() {
|
||||
@@ -94,6 +94,21 @@ test("late drop resolution cannot clear or announce a newer gesture", async () =
|
||||
]);
|
||||
});
|
||||
|
||||
test("collision candidates exclude overlapping ancestors and invalid destinations before sorting", () => {
|
||||
const f = fixture({ accept: (_ids, to) => to.group !== "blocked" });
|
||||
const child = {} as HTMLElement;
|
||||
f.manager.registerSortable({ id: "child", group: "content", index: 0, element: child, type: "text" });
|
||||
f.manager.registerSortable({ id: "parent", group: "root", index: 0, element: { contains: (element: unknown) => element === child } as HTMLElement });
|
||||
f.manager.registerSortable({ id: "sibling", group: "content", index: 1, element: {} as HTMLElement, accepts: ["text"] });
|
||||
f.manager.registerSortable({ id: "invalid", group: "blocked", index: 0, element: {} as HTMLElement });
|
||||
const source = { id: "child", type: "text" };
|
||||
expect(f.instances[1]!.input.accept(source)).toBe(false);
|
||||
expect(f.instances[2]!.input.accept(source)).toBe(true);
|
||||
expect(f.instances[2]!.input.accept({ ...source, type: "image" })).toBe(false);
|
||||
expect(f.instances[3]!.input.accept(source)).toBe(false);
|
||||
f.manager.destroy();
|
||||
});
|
||||
|
||||
test("empty drop zones use explicit locations for acceptance and commit", async () => {
|
||||
const commits: unknown[] = [];
|
||||
const f = fixture({ accept: (_ids, to) => to.group === "empty", onMoveIntent: intent => { commits.push(intent.to); } });
|
||||
|
||||
Reference in new issue
Block a user