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",
|
"name": "@wrnexus/dnd",
|
||||||
"version": "0.1.3",
|
"version": "0.1.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Stable accessible drag-and-drop primitives for WRNexus.",
|
"description": "Stable accessible drag-and-drop primitives for WRNexus.",
|
||||||
"exports": {
|
"exports": {
|
||||||
|
|||||||
@@ -121,7 +121,15 @@ export function createDndManager(options: DndManagerOptions): DndManager {
|
|||||||
element: registration.element,
|
element: registration.element,
|
||||||
handle: registration.handle,
|
handle: registration.handle,
|
||||||
type: registration.type,
|
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,
|
disabled: registration.disabled,
|
||||||
transition: reducedMotion ? null : undefined,
|
transition: reducedMotion ? null : undefined,
|
||||||
data: { ...registration.data, direction },
|
data: { ...registration.data, direction },
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ const compiled = new Bun.Transpiler({ loader: "ts" })
|
|||||||
function fixture(overrides: Partial<DndManagerOptions> = {}) {
|
function fixture(overrides: Partial<DndManagerOptions> = {}) {
|
||||||
const listeners: Record<string, (event: any) => any> = {};
|
const listeners: Record<string, (event: any) => any> = {};
|
||||||
const announcements: DndAnnouncement[] = [];
|
const announcements: DndAnnouncement[] = [];
|
||||||
const instances: Array<{ destroyed: number }> = [];
|
const instances: Array<{ destroyed: number; input: any }> = [];
|
||||||
let managerDestructions = 0;
|
let managerDestructions = 0;
|
||||||
class Backend {
|
class Backend {
|
||||||
monitor = {
|
monitor = {
|
||||||
@@ -26,7 +26,7 @@ function fixture(overrides: Partial<DndManagerOptions> = {}) {
|
|||||||
}
|
}
|
||||||
class Sortable {
|
class Sortable {
|
||||||
destroyed = 0;
|
destroyed = 0;
|
||||||
constructor() {
|
constructor(public input: any) {
|
||||||
instances.push(this);
|
instances.push(this);
|
||||||
}
|
}
|
||||||
destroy() {
|
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 () => {
|
test("empty drop zones use explicit locations for acceptance and commit", async () => {
|
||||||
const commits: unknown[] = [];
|
const commits: unknown[] = [];
|
||||||
const f = fixture({ accept: (_ids, to) => to.group === "empty", onMoveIntent: intent => { commits.push(intent.to); } });
|
const f = fixture({ accept: (_ids, to) => to.group === "empty", onMoveIntent: intent => { commits.push(intent.to); } });
|
||||||
|
|||||||
Reference in new issue
Block a user