Skip to content

Support not-blocking pointer inputs outside of focusable Popups#2992

Open
MatkovIvan wants to merge 1 commit intojb-mainfrom
ivan.matkov/block-input-outside-popup
Open

Support not-blocking pointer inputs outside of focusable Popups#2992
MatkovIvan wants to merge 1 commit intojb-mainfrom
ivan.matkov/block-input-outside-popup

Conversation

@MatkovIvan
Copy link
Copy Markdown
Collaborator

CMP-8852 Support not-blocking pointer inputs outside of focusable Popups

Release Notes

Features - Multiple Platforms

  • Add blockPointerInputOutside flag to PopupProperties to support not-blocking pointer inputs outside of focusable Popups

@MatkovIvan MatkovIvan requested review from igordmn and m-sasha April 21, 2026 10:50
@MatkovIvan MatkovIvan force-pushed the ivan.matkov/block-input-outside-popup branch from f3d422e to 679e24d Compare April 21, 2026 11:38
@MatkovIvan MatkovIvan marked this pull request as ready for review April 21, 2026 11:38
usePlatformDefaultWidth = usePlatformDefaultWidth,
usePlatformInsets = usePlatformInsets
usePlatformInsets = usePlatformInsets,
blockPointerInputOutside = blockPointerInputOutside,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
blockPointerInputOutside = blockPointerInputOutside,
consumePointerInputOutside = consumePointerInputOutside,

When I read "block", I think that it doesn't work at all, even in this case:

PopupProperties(dismissOnClickOutside = true, blockPointerInputOutside = true)

But this:

PopupProperties(dismissOnClickOutside = true, consumePointerInputOutside = true)

tells that is still handled, just isn't passed further.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a valid scenario where block/consumePointerInputOutside=false but dismissOnClickOutside=true?

Maybe we need an outsidePointerEventsMode parameter, with possible values:

  • OutsidePointerEventsMode.Block(dismissOnClick: Boolean)
  • OutsidePointerEventsMode.Passthrough

?

Copy link
Copy Markdown
Collaborator Author

@MatkovIvan MatkovIvan Apr 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a valid scenario where block/consumePointerInputOutside=false but dismissOnClickOutside=true?

It won't trigger dismissing. Similar with dismissOnBackPress/focusable combination now

dismissOnClickOutside is a parameter from commonMain, we should not change it

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dismissOnClickOutside is a parameter from commonMain, we should not change it

Since we're changing the API anyway, we could add an overload that takes outsidePointerEventsMode instead of dismissOnClickOutside.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's an extension of the existing common API, let's keep it aligned and simple. Also, there is nothing wrong with such a combination in terms of forcing to avoid it

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed

Copy link
Copy Markdown

@igordmn igordmn Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is okay for me to have 2 parameters, maybe even it is needed all 4 combinations for different cases.

I delegate closing the comment or continuing to discuss to Sasha

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a blocker; just a combination of parameters that doesn't have a use-case.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This combination is not new. it was dismissOnClickOutside=true + focusable=false before.
I've re-checked and this combination works and makes sense - it closes Popup on any interaction with the main content without interrupting this particular interaction

usePlatformDefaultWidth = usePlatformDefaultWidth,
usePlatformInsets = usePlatformInsets
usePlatformInsets = usePlatformInsets,
blockPointerInputOutside = blockPointerInputOutside,
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a valid scenario where block/consumePointerInputOutside=false but dismissOnClickOutside=true?

Maybe we need an outsidePointerEventsMode parameter, with possible values:

  • OutsidePointerEventsMode.Block(dismissOnClick: Boolean)
  • OutsidePointerEventsMode.Passthrough

?

@MatkovIvan MatkovIvan force-pushed the ivan.matkov/block-input-outside-popup branch from 679e24d to 7b043aa Compare April 24, 2026 12:04
@MatkovIvan MatkovIvan force-pushed the ivan.matkov/block-input-outside-popup branch from 7b043aa to 7587c0c Compare April 24, 2026 13:03

public final class androidx/compose/ui/window/PopupProperties {
public static final field $stable I
public fun <init> ()V
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, what happened to it?

Copy link
Copy Markdown
Collaborator Author

@MatkovIvan MatkovIvan Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's caused by our "filter experimental" API rules. It's still there, everything is compatible

* only to the "interested" component "under" the mouse pointer.
*/
private fun redispatchUnconsumedMouseEvent(event: MouseEvent) {
private fun redispatchUnconsumedMouseEvent(event: MouseEvent, target: Component? = null) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

target seems unneeded now that you removed redispatching between layers.

override var consumePointerInputOutside: Boolean
get() = false
set(_) {}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe better to leave it unimplemented here and let each subclass decide?

}

@Test
fun focusableNonBlockingPopup_withComponentLayerType_passesClicksThrough() {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a good place for this test? It doesn't seem to test ComposePanel itself.


/**
* Indicates if pointer input events outside of this layer's bounds should be blocked.
* When set to true, touch events outside of this layer's bounds are not propagated to
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"pointer" instead of "touch".

@m-sasha
Copy link
Copy Markdown

m-sasha commented Apr 24, 2026

Probably worth asking someone from the iOS team to review it too, as there are uikit changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants