Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Click and dragging outside the modal closes it #712

Open
TimBroddin opened this issue Nov 22, 2024 · 5 comments
Open

Click and dragging outside the modal closes it #712

TimBroddin opened this issue Nov 22, 2024 · 5 comments
Assignees

Comments

@TimBroddin
Copy link

Quick summary

When dragging outside a modal (probably any dialog) it gets closed. The click event triggers on mouse up, and has the clientX & clientY from where the mouse was released.

Image

Workaround/fix

I was able to fix this by adding this code inside dialogable.js:

 if (this.options().clickOutside) {
            let isInteractingInside = false;

            // Track mousedown events inside the dialog
            this.el.addEventListener('mousedown', e => {
                if (e.target !== this.el) {
                    isInteractingInside = true;
                }
            });

            // Reset the tracking on mouseup
            window.addEventListener('mouseup', () => {
                setTimeout(() => {
                    isInteractingInside = false;
                }, 0);
            });

            // Clicking outside the dialog should close it...
            this.el.addEventListener('click', e => {
                // Clicking the ::backdrop pseudo-element is treated the same as clicking the <dialog> element itself
                // Therefore, we can dissregard clicks on any element other than the <dialog> element itself...
                if (e.target !== this.el) return

                // Don't close if the interaction started inside the modal
                if (isInteractingInside) return

                // Again, because we can't listen for clicks on ::backdrop, we have to test for intersection
                // between the click and the visible parts of the dialog elements...
                if (clickHappenedOutside(this.el, e)) {
                    this.hide()

                    e.preventDefault(); e.stopPropagation()
                }
            })
        }
@joshhanley
Copy link
Member

joshhanley commented Nov 28, 2024

@calebporzio just tested this with the default modal from the docs and can confirm the issue.

Mousing down anywhere inside the modal (inputs, whitespace, anywhere) and then dragging the mouse outside of the modal and then releasing causes the modal to close.

Volt component

<?php

use Livewire\Volt\Component;

new class extends Component {
    //
};

?>

<div>
    <flux:modal.trigger name="edit-profile">
        <flux:button>Edit profile</flux:button>
    </flux:modal.trigger>

    <flux:modal name="edit-profile" class="md:w-96 space-y-6">
        <div>
            <flux:heading size="lg">Update profile</flux:heading>
            <flux:subheading>Make changes to your personal details.</flux:subheading>
        </div>

        <flux:input label="Name" placeholder="Your name" />

        <flux:input label="Date of birth" type="date" />

        <div class="flex">
            <flux:spacer />

            <flux:button type="submit" variant="primary">Save changes</flux:button>
        </div>
    </flux:modal>
</div>

@danielrona
Copy link

In this discussion there have already been a couple of inputs, I considered this to be a breaking change and would have loved an actual a flag to restore the previous behavior but there have been no updates to that discussion since.

@jeffchown
Copy link

@danielrona Have you tried adding :dismissible="false" to your modal? (https://fluxui.dev/components/modal#disable-click-outside)

@joshhanley
Copy link
Member

joshhanley commented Nov 29, 2024

Yeah I can confirm that using :dismissible="false" stops the issue but it then means you can't then use dismissible 😆

@joshhanley joshhanley changed the title [1.0.23] Modal - Dragging outside the modal closes it Click and dragging outside the modal closes it Dec 19, 2024
@joshhanley
Copy link
Member

joshhanley commented Jan 8, 2025

Ok I've worked out what the issue is and submitted a PR with a fix!

PR description below

The scenario

Currently if a user mouses down inside a modal, drags their mouse outside, and then releases their mouse button, the modal closes.

Image

<?php

use Livewire\Volt\Component;

new class extends Component {
    //
};

?>

<div>
    <flux:modal.trigger name="edit-profile">
        <flux:button>Edit profile</flux:button>
    </flux:modal.trigger>

    <flux:modal name="edit-profile" class="md:w-96 space-y-6">
        <div>
            <flux:heading size="lg">Update profile</flux:heading>
            <flux:subheading>Make changes to your personal details.</flux:subheading>
        </div>

        <flux:input label="Name" placeholder="Your name" />

        <flux:input label="Date of birth" type="date" />

        <div class="flex">
            <flux:spacer />

            <flux:button type="submit" variant="primary">Save changes</flux:button>
        </div>
    </flux:modal>
</div>

The problem

Dialog elements don't have a native click outside. So we have a click listener on the dialog element that checks to see if the click originated outside of the dialog, to determine whether it should be closed or not.

if (this.options().clickOutside) {
    // Clicking outside the dialog should close it...
    this.el.addEventListener('click', e => {
        // Clicking the ::backdrop pseudo-element is treated the same as clicking the <dialog> element itself
        // Therefore, we can dissregard clicks on any element other than the <dialog> element itself...
        if (e.target !== this.el) return

        // Again, because we can't listen for clicks on ::backdrop, we have to test for intersection
        // between the click and the visible parts of the dialog elements...
        if (clickHappenedOutside(this.el, e)) {
            this.cancel()

            e.preventDefault(); e.stopPropagation()
        }
    })
}

The problem is that this check happens at the time the click listener is fired, which is when the mouse up event happens. It is detecting that the mouse is outside of the dialog at that point in time and closing the modal.

The solution

To fix this, we need to keep track of whether the mouse down event was fired outside of the dialog as well as detecting if the click happened outside of the dialog.

This way we can ensure that the click both started and ended outside of the dialog, and if so, then close it.

let startedOutside = false

// If a user mouses down inside the dialog, and then drags the mouse outside the dialog before releasing the mouse button,
// the dialog should not close so we need to track whether the mouse down was initially triggered outside the dialog...
this.el.addEventListener('mousedown', e => {
    // Clicking the ::backdrop pseudo-element is treated the same as clicking the <dialog> element itself
    // Therefore, we can dissregard clicks on any element other than the <dialog> element itself...
    if (e.target !== this.el) return

    startedOutside = clickHappenedOutside(this.el, e)
})

// Clicking outside the dialog should close it...
this.el.addEventListener('click', e => {
    // Clicking the ::backdrop pseudo-element is treated the same as clicking the <dialog> element itself
    // Therefore, we can dissregard clicks on any element other than the <dialog> element itself...
    if (e.target !== this.el) return

    // Again, because we can't listen for clicks on ::backdrop, we have to test for intersection
    // between the click and the visible parts of the dialog elements...
    if (startedOutside && clickHappenedOutside(this.el, e)) {
        this.cancel()

        e.preventDefault(); e.stopPropagation()
    }

    // Reset started outside ready for the next click...
    startedOutside = false
})

Fixes #712

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants