dnd-kit icon indicating copy to clipboard operation
dnd-kit copied to clipboard

Cancel drag when the the draggable item loses focus

Open marcospassos opened this issue 3 years ago • 3 comments

Currently, when dragging using the keyboard, the item remains active even when focusing out of it. It leads to several issues that can't be solved at the userland.

I tried implementing a custom sensor, but the current design doesn't allow for extension (note the ts-ignore):

/**
 * A custom keyboard sensor cancels the drag when moving the focus out of the draggable element.
 */
class CustomKeyboardSensor extends KeyboardSensor {
    private readonly sensorProps: SensorProps<SensorOptions>;

    constructor(props: KeyboardSensorProps) {
        super(props);

        this.sensorProps = props;

        this.handleDragCancel = this.handleDragCancel.bind(this);

        this.attachSensor();
    }

    private attachSensor(): void {
        this.sensorProps.event.target?.addEventListener('blur', this.handleDragCancel);
    }

    private detachSensor(): void {
        this.sensorProps.event.target?.removeEventListener('blur', this.handleDragCancel);
    }

    private handleDragCancel(): void {
        // There's no other way to notify the parent class to clean up the state.
        // @ts-ignore
        this.detach();
        this.detachSensor();
        this.sensorProps.onCancel();
    }
}

Even ignoring the error, other issues arise. For example, using the CustomKeyboardSensor, when pressing enter to finish dragging, DND kit announces the event as a cancel operation. Even worse, after calling the onCancel callback, in response to a click outside, the item steals the focus back, so you need to click twice to move the focus away from the item.

marcospassos avatar Aug 04 '22 13:08 marcospassos

Would love assistance on this as well. If a non keyboard user accidentally gets into a keyboard drag state, the app will feel like it is broken since they cant cancel the drag by clicking elsewhere.

One thought would be to build a combination sensor that taps into both keyboard and click events?

codewitch avatar Aug 04 '22 21:08 codewitch

For what it's worth, here's something hacky I did that seems to be working fine and not running into the issues mentioned above. Note also that in my application I am using handleEnd instead of handleCancel.

export class CustomKeyboardSensor extends KeyboardSensor {
    constructor(props: KeyboardSensorProps) {
        super(props);

        const handleEnd = (this as any).handleEnd.bind(this);
        const detach = (this as any).detach.bind(this);

        props.activeNode.activatorNode.current?.addEventListener("blur", handleEnd);

        // hack: override the detach method to also remove the blur event
        (this as any).detach = () => {
            detach();
            props.activeNode.activatorNode.current?.removeEventListener("blur", handleEnd);
        };
    }
}

pwang347 avatar Nov 16 '23 21:11 pwang347