Visitar URL original
fix(flyout): Keep flyouts above the on-screen keyboard by ajpinedam · Pull Request #26060 · unoplatform/uno · GitHub
Skip to content

fix(flyout): Keep flyouts above the on-screen keyboard - #26060

Merged
ajpinedam merged 26 commits into
unoplatform:masterfrom
ajpinedam:dev/anpi/popup-above-keyboard
Nov 5, 2026
Merged

ajpinedam merged 26 commits into
unoplatform:masterfrom
ajpinedam:dev/anpi/popup-above-keyboard

Conversation

@ajpinedam

@ajpinedam ajpinedam commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

GitHub Issue: closes unoplatform/kahua-private#561

PR Type:

🐞 Bugfix

What changed? 🚀

Before: on mobile web (iPad Safari, Android Chrome), a flyout is placed against the whole window. The on-screen keyboard covers it when its button sits near the bottom, or when the flyout holds a TextBox the user is typing in.

Now:

  • Flyouts are placed above the input pane. FlyoutBase.CalculateAvailableWindowRect subtracts InputPane.OccludedRect, as WinUI does.
  • Open flyouts are placed again when the input pane shows or hides. UnoRootElementLogic stands in for WinUI's RootScrollViewer::NotifyInputPaneStateChange.
    • An app that sets EnsuredFocusedElementInView keeps control, as in WinUI.
    • Hiding only undoes a Showing that the framework handled, matching WinUI's InputPaneHandler.
  • A flyout that fits on no side is shrunk next to its target, and its content scrolls. This is WinUI's ResizeToFit, ported in FlyoutBase.Placement.mux.cs.
    • It applies on every Skia target, with or without a keyboard. A Flyout or MenuFlyout that used to be clipped now scrolls.
    • CalendarDatePicker opts out, as in WinUI.
    • Divergence: when the target itself is behind the keyboard, Uno still shrinks the flyout above the keyboard, while WinUI leaves it unresized. A TODO Uno in PopupPanel.Placement.cs marks this.
  • WebAssembly: keyboard occlusion is reported at most once per animation frame. The visual viewport fires resize and scroll together.
  • WebAssembly on iOS: WebKit blurs the window when the on-screen keyboard is dismissed, then focuses it again on the next tap. Since 4ebc924 (fix(wasm): Report window activation and visibility changes), that blur raised Window.Activated (Deactivated), and PopupRoot closed every light-dismiss popup.
    • A blur that comes while the keyboard is up is no longer reported, and neither is the focus that follows it.
    • Hiding the page still deactivates.
    • Every other blur is reported as before, including the address bar and an iframe host page.
    • Known gap: in iPad Split View or Slide Over, tapping the other app while the keyboard is up doesn't report Deactivated, because the page stays visible.
  • Sample: Windows.UI.ViewManagement/SoftKeyboardFlyoutTests. It shows the keyboard rect, plus the bounds of the open flyout with "Above keyboard: yes/no".

Not covered: only FlyoutBase-hosted popups avoid the keyboard. A bare Popup and the editable ComboBox dropdown are unchanged.

Backports: the iOS activation commits (fix(wasm): Keep popups open on iOS keyboard hide, fix(wasm): Only skip the keyboard blur on iOS) fix a regression from 4ebc924. That commit is on master and servicing/6.8, but not on 6.7, so the 6.7 backport doesn't need them.

Validation

  • Runtime tests, Skia Desktop: Given_InputPane, Given_Flyout and Given_MenuFlyout gave 101 passed, 2 skipped (existing platform conditions), 0 failed.
    • Negative control: with presenter resizing turned off, the new visible-button test and both cases of the existing resize test fail.
  • iPad Safari, real device:
    • Confirmed in landscape and portrait: the search flyout moves and shrinks above the keyboard, and resets when the keyboard hides.
    • Confirmed with the earlier version of the iOS activation fix: hiding the keyboard no longer closes the flyout.
  • Still to do:
    • Re-run the steps below on the iPad with the narrowed iOS change and the per-frame batching.
    • The tall flyout and opening a flyout while the keyboard is up (steps 4–6).
    • Android Chrome.

Manual steps

Open ?sample=Windows.UI.ViewManagement/SoftKeyboardFlyoutTests on a tablet, in portrait and in landscape:

# Do Expect
1 Tap Open search flyout. It opens above its button.
2 Tap the search box in the flyout. When the keyboard is up, the whole flyout sits right above it.
3 Hide the keyboard. The flyout stays open and returns next to its button.
4 Close it. Tap Open tall flyout, then its search box. The flyout shrinks to the area above the keyboard, and its list still scrolls to Item 29.
5 Hide the keyboard. It gets its natural height back.
6 Close it. Tap the top field to bring up the keyboard, then tap either button. The flyout opens above the keyboard; the tall one opens shrunk.

PR Checklist ✅

No public API changes. These are behaviour changes:

  • ResizeToFit now applies on every Skia target.
  • A popup placed by PopupPanel (flyouts and tooltips) whose target lies partly beyond the available area is placed against that area's edge, whether or not a keyboard is up. A target scrolled partly out of the visible bounds now has its popup pinned to that edge.
  • On iOS browsers, Window.Activated no longer reports Deactivated when the on-screen keyboard is dismissed.

🤖 Generated with Claude Code

@github-actions github-actions Bot added platform/wasm 🌐 Categorizes an issue or PR as relevant to the WebAssembly platform area/automation Categorizes an issue or PR as relevant to project automation labels Nov 4, 2026
@unodevops

Copy link
Copy Markdown
Contributor

🤖 Your WebAssembly Sample App stage site is ready! Visit it here:

@Xiaoy312 Xiaoy312 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The approach follows WinUI closely and looks right to me. Checked against the WinUI sources: the sticky m_bHandledFocusElementInView, the unconditional input-pane subtraction in CalculateAvailableWindowRect, CalendarDatePicker being the only DisablePresenterResizing caller, and a resized presenter not following its content (OnPresenterSizeChanged is skipped while m_presenterResized) all match. The resize loop also terminates.

CI: Analyze (csharp) fails on two analyzer errors from this PR (inline below). UnoFastDevBuild turns analyzers off, which is why local builds didn't catch them. The Android+NativeAOT failure is NU1903 on the ILCompiler rc.2 package; it fails the same way on master, so it isn't this PR.

Coverage: ResizeToFit now applies to every FlyoutBase on every Skia target, keyboard or not. That includes CommandBarFlyout, TextCommandBarFlyout (the text-selection flyout), and SplitButton/DropDownButton menus, while only Given_InputPane, Given_Flyout and Given_MenuFlyout were run. Could you run the CommandBarFlyout, TextCommandBarFlyout and TextBox context-flyout tests, and check the screenshot-compare results?

The new Given_Flyout.When_Flyout_Fits_On_No_Side_Then_Resized_Inside_Window has no platform condition, so it also runs on native WinUI. That's a useful parity check, but it hasn't been run there yet.

Review based on the diff and the WinUI sources; I didn't build or run it locally.

@ajpinedam

Copy link
Copy Markdown
Contributor Author

Thanks for the review, @Xiaoy312. I've replied inline. Here are answers to the points in the summary.

CI:

  • The two Analyze (csharp) errors are fixed in 41fa3b4. Uno.UI now builds without errors with analyzers enabled.
  • Agreed that the Android+NativeAOT NU1903 failure isn't from this PR.

Coverage: I ran these locally on Skia Desktop (macOS) against the PR head:

  • every runtime test whose name contains Flyout;
  • Given_InputPane, Given_TextBox_ContextMenuPosition and Given_ToolTip;
  • the CalendarDatePicker, DatePicker and TimePicker tests;
  • the SplitButton and DropDownButton tests.

That's 348 tests across 49 classes: 332 passed, 15 skipped (existing platform conditions) and 1 failed.

  • They include:
    • CommandBarFlyoutTests (9) and Given_CommandBarFlyout (4);
    • the TextBox context and selection flyout tests (18 in Given_TextBox), which cover TextCommandBarFlyout;
    • Given_RichEditBox (6) and Given_TextBlock (3);
    • MenuFlyoutIntegrationTests (34) and Given_SplitMenuFlyoutItem (16);
    • SplitButtonTests (9) and DropDownButtonTests (1).
  • The failure is CalendarDatePickerIntegrationTests.CanCloseFlyoutBySelectingADate, which expects 10/21/2000 or 21/10/2000. The test sets the date in code rather than tapping it, so placement doesn't affect it. This Mac's locale is en_CA, and the test passes when run with LC_ALL=en_US.UTF-8.
  • Screenshot compare: the CI run on the new head hasn't produced results yet. I'll check them when it finishes.
  • Native WinUI: Given_Flyout.When_Flyout_Fits_On_No_Side_Then_Resized_Inside_Window hasn't been run there yet. I don't have a Windows machine in this setup, and the WinAppSDK CI job only builds it.

@unodevops

Copy link
Copy Markdown
Contributor

🤖 Your WebAssembly Sample App stage site is ready! Visit it here:

@unodevops

Copy link
Copy Markdown
Contributor

🤖 Your WebAssembly Sample App stage site is ready! Visit it here:

agneszitte added a commit that referenced this pull request Nov 4, 2026
Brings 5686c7d (mobile CI flyout tests) and d565b0b (WinUI resize test)
from #26060.

The FindDescendant<T> helper also gets a class constraint: on 6.x,
DependencyObject is an interface, so "as T" doesn't compile without it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@unodevops

Copy link
Copy Markdown
Contributor

🤖 Your WebAssembly Sample App stage site is ready! Visit it here:

@nventive-devops

Copy link
Copy Markdown
Contributor

The build 243758 found UI Test snapshots differences: skia-linux-screenshots: 2458, skia-windows-screenshots: 2458

Details
  • skia-linux-screenshots: 2458 changed over 2494

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • AndroidWindowInsets.png
    • AppBarButtonTest.png-dark
    • ApplicationViewMode.png-dark
    • Application_DispatcherShutdownMode.png
    • AppXamlDefinedResources.png
    • Attributed_text_Supserscript.png-dark
    • AutoBorderStretchwithleftmargin.png-dark
    • AutomationProperties_Landmark.png-dark
    • AutoSizedPathCentered.png-dark
    • AutoSizedPathCentered.png
    • AutoSuggestBox_Keyboard.png-dark
    • AutoSuggestBox_Keyboard.png
    • BasicEnability.png-dark
    • BasicEnability.png
    • BeginTime_MultipleAnimations.png-dark
    • BeginTime_MultipleAnimations.png
    • BitmapIcon_Generic.png-dark
    • BitmapIcon_Generic.png
    • BorderCenteredwithmargins.png-dark
    • BorderCenteredwithmargins.png
  • skia-windows-screenshots: 2458 changed over 2494

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • AnimatedBackVisualSourceSamplePage.png-dark
    • AppBarButtonWithIconTest.png
    • ApplicationViewSizing.png-dark
    • AppWindowClosing.png-dark
    • AppWindowPresenters.png-dark
    • ArcSegment.png-dark
    • AuthenticationBroker_Demo.png-dark
    • AutomationProperties_AutomationId.png-dark
    • AutomationProperties_Name.png-dark
    • AutomationProperties_Name.png
    • AutoSuggestBoxChosenSuggestion.png-dark
    • AutoSuggestBoxChosenSuggestion.png
    • BackGesture.png-dark
    • BackGesture.png
    • Basics.png-dark
    • Basics.png
    • BezierSegment.png-dark
    • BezierSegment.png
    • BitmapIcon_Monochromatic.png-dark
    • BitmapIcon_Monochromatic.png

@ajpinedam

Copy link
Copy Markdown
Contributor Author

Follow-up on CI for e84d9e0, @Xiaoy312:

  • Native WinUI: Given_Flyout.When_Flyout_Fits_On_No_Side_Then_Resized_Inside_Window now passes.
    • The first run showed what WinUI does. It shrinks the flyout to the room below the button (392px in a 480px window), then adds its 4px FlyoutMargin (FlyoutBase_partial.cpp 2634-2646), so the flyout runs past the window bottom by 4px.
    • Uno's ResizeToFit leaves room for that margin, so it stays inside the window. The test allows the 4px only on WinAppSDK.
  • Mobile: the new InputPane flyout tests failed on the Android emulator and iOS simulator. Focusing a TextBox brings up their real keyboard, whose occlusion replaces the simulated one. Those tests now skip SkiaMobile, and the keyboard position is relative to the flyout's height for tall windows.
  • Screenshot compare: the job passes. The bot's 2458/2494 differences on linux and windows are identical on perf(wasm): Repaint only damaged regions on WebGL #26041 and perf(wasm): Load ICU break dictionaries on demand #26055, so the baseline shifted.
  • Remaining reds, none from this PR:
    • WinAppSDK: an access violation in the WinUI app during the later Given_RichEditBox tests, after the flyout tests passed.
    • Desktop Linux: UNOBLD004, the package mirror didn't respond.
    • Android groups 0 and 4: Given_TextBox flakes that also fail on master.
    • Android+NativeAOT: NU1903.

agneszitte added a commit that referenced this pull request Nov 4, 2026
Brings e84d9e0 (WinUI's flyout margin overflow) from
#26060.

FindDescendant<T> now uses pattern matching instead of a class constraint:
DependencyObject is an interface on 6.x but a class on WinAppSDK, where
"where T : class, DependencyObject" fails with CS0450.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ajpinedam
ajpinedam force-pushed the dev/anpi/popup-above-keyboard branch from e84d9e0 to 6135433 Compare November 5, 2026 09:55
@unodevops

Copy link
Copy Markdown
Contributor

🤖 Your WebAssembly Sample App stage site is ready! Visit it here:

@ajpinedam
ajpinedam enabled auto-merge November 5, 2026 10:52
ajpinedam pushed a commit that referenced this pull request Nov 5, 2026
Manual servicing/6.8 backport of #26060
at c52767a, adapted to the 6.x layout:

- The InputPane change goes into InputPane.skia.cs, where 6.x keeps it.
- Paths: Uno.UI.Runtime.Skia.WebAssembly.Browser, Uno.UI.RuntimeTests/Tests,
  SamplesApp.Samples/Windows_UI_ViewManagement.
- Boxer.Box (7.0 only) becomes a plain SetValue.
- The iOS native PickerFlyoutPopupPanel override takes the new minSize parameter.
- The new Given_Flyout test skips the 6.x native heads, which master doesn't have.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ajpinedam pushed a commit that referenced this pull request Nov 5, 2026
Brings 5686c7d (mobile CI flyout tests) and d565b0b (WinUI resize test)
from #26060.

The FindDescendant<T> helper also gets a class constraint: on 6.x,
DependencyObject is an interface, so "as T" doesn't compile without it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ajpinedam pushed a commit that referenced this pull request Nov 5, 2026
Brings e84d9e0 (WinUI's flyout margin overflow) from
#26060.

FindDescendant<T> now uses pattern matching instead of a class constraint:
DependencyObject is an interface on 6.x but a class on WinAppSDK, where
"where T : class, DependencyObject" fails with CS0450.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nventive-devops

Copy link
Copy Markdown
Contributor

The build 243921 found UI Test snapshots differences: skia-linux-screenshots: 2458, skia-windows-screenshots: 2458

Details
  • skia-linux-screenshots: 2458 changed over 2494

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • AndroidWindowInsets.png
    • AnimatedIconPage.png
    • AppBarToggleButtonTest.png-dark
    • ApplicationViewSizing.png
    • AppWindowFeatures.png-dark
    • AppWindowTitleBar Properties.png
    • Attributed_text_Simple.png-dark
    • AuthenticationBroker_Demo.png-dark
    • AutoBorderStretchwithrightmargin.png-dark
    • AutoBorderStretchwithrightmargin.png
    • AutoSizedPathCentered.png-dark
    • AutoSizedPathCentered.png
    • AutoSuggestBox_Icons.png-dark
    • AutoSuggestBox_Icons.png
    • BasicEnability.png-dark
    • BasicEnability.png
    • BenchmarkDotNet.png-dark
    • BenchmarkDotNet.png
    • BitmapIcon_Generic.png-dark
    • BitmapIcon_Generic.png
  • skia-windows-screenshots: 2458 changed over 2494

    🚨🚨 Comparison Details (first 20) 🚨🚨
    • AndroidWindowInsets.png
    • AnimatedBackVisualSourceSamplePage.png
    • AppBarToggleButtonTest.png-dark
    • ApplicationViewSpanningRectsPage.png
    • AppWindowPositionAndSize.png-dark
    • ArcSegment.png-dark
    • AuthenticationBroker_Cancel.png-dark
    • AutoBorderStretchwithtopmargin.png-dark
    • AutomationProperties_Landmark.png
    • AutomationProperties_Name.png-dark
    • AutoSuggestBox_Description.png-dark
    • AutoSuggestBox_Description.png
    • BadgeNotificationTests.png-dark
    • BadgeNotificationTests.png
    • AndroidWindowInsets.png-dark
    • Basics_Automated.png-dark
    • AppBarButtonTest.png-dark
    • Basics_Automated.png
    • AppBar_KeyBoard.png-dark
    • BenchmarkDotNet.png-dark

@mergify

mergify Bot commented Nov 5, 2026 •

Copy link
Copy Markdown
Contributor

This pull request does not currently match the merge queue conditions, so it cannot be queued from here. The box comes back if it matches again.

@ajpinedam
ajpinedam merged commit ee6cfc6 into unoplatform:master Nov 5, 2026
49 checks passed
@ajpinedam
ajpinedam deleted the dev/anpi/popup-above-keyboard branch November 5, 2026 18:19
ajpinedam added a commit that referenced this pull request Nov 5, 2026
fix(flyout): Keep flyouts above the on-screen keyboard (backport #26060)
mikernet pushed a commit to Singulink/uno that referenced this pull request Nov 6, 2026
…latform#26060)

Manual servicing/6.8 backport of unoplatform#26060
at c52767a, adapted to the 6.x layout:

- The InputPane change goes into InputPane.skia.cs, where 6.x keeps it.
- Paths: Uno.UI.Runtime.Skia.WebAssembly.Browser, Uno.UI.RuntimeTests/Tests,
  SamplesApp.Samples/Windows_UI_ViewManagement.
- Boxer.Box (7.0 only) becomes a plain SetValue.
- The iOS native PickerFlyoutPopupPanel override takes the new minSize parameter.
- The new Given_Flyout test skips the 6.x native heads, which master doesn't have.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit a0dd2ae)
mikernet pushed a commit to Singulink/uno that referenced this pull request Nov 6, 2026
…m#26060

Brings 5686c7d (mobile CI flyout tests) and d565b0b (WinUI resize test)
from unoplatform#26060.

The FindDescendant<T> helper also gets a class constraint: on 6.x,
DependencyObject is an interface, so "as T" doesn't compile without it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 90dc007)
mikernet pushed a commit to Singulink/uno that referenced this pull request Nov 6, 2026
Brings e84d9e0 (WinUI's flyout margin overflow) from
unoplatform#26060.

FindDescendant<T> now uses pattern matching instead of a class constraint:
DependencyObject is an interface on 6.x but a class on WinAppSDK, where
"where T : class, DependencyObject" fails with CS0450.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit cfd3944)
agneszitte added a commit that referenced this pull request Nov 10, 2026
…6.7/pr-26062

fix(flyout): Keep flyouts above the on-screen keyboard (backport #26060) (backport #26062)
mikernet added a commit to Singulink/uno that referenced this pull request Nov 14, 2026
Takes Uno's servicing/6.8 from 6951c2c up to 5254ace (42 pull
requests). Conflicts, by area:

IME and TextBox (unoplatform#25984, backport of unoplatform#25804; unoplatform#25952, backport of unoplatform#25619;
unoplatform#25976, backport of unoplatform#25818)
- Takes upstream's composition design (unoplatform#25804): the browser's input is
  the source of truth for the text, compositions included. This drops
  the composition parts of the unoplatform#24502 port (90746b8): its
  TypeScript composition handling, the StartIndex/TextChangePending
  event arguments, the IsReadOnly forwarding to the hidden input, and
  its Given_TextBox.BrowserSoftKeyboard tests.
- Keeps the non-IME browser fixes on top of it, in the form their
  master pull requests have on unoplatform#25804: Enter raised once per press and
  tracked by key code (unoplatform#25174, with Given_TextBox.BrowserEnterKey from
  master in place of the Enter tests that lived in the dropped file),
  the iOS refocus on tap (unoplatform#25185), pastes without text and isIOS from
  BrowserInputHelper.
- Moves the unoplatform#26005 MaxLength fixes onto unoplatform#25804's native input path, as
  master has them: a refused browser edit keeps the TextBox's selection,
  TextBoxView.UpdateTextFromNative reports whether the edit went in, and
  the IME keeps its range on the applied text (master be85699, which
  d226897 had ported onto the unoplatform#24502 IME). Adds that commit's browser
  and platform-applied IME tests.
- TextBox.pointers.skia.cs: upstream's file (unoplatform#25619 iOS touch editing,
  unoplatform#25818, unoplatform#25804), with the unoplatform#25126 port (the hold acts when it starts,
  a touch right-tap keeps the caret) in the form master's unoplatform#25126 branch
  has it on unoplatform#25619, including focusing before the iOS hold timer acts
  and its iOS not-focusable test.

Pointers (unoplatform#25976, backport of unoplatform#25818)
- Tapped and RightTapped are raised once the PointerReleased bubbled
  (unoplatform#25818), with the unoplatform#25732 port's window coordinates (2-argument
  routed event args, RawPosition for ContextRequested), as master's
  unoplatform#25732 branch has them.
- The release wrapper of unoplatform#25818 keeps the unoplatform#25126 port's
  StopContextMenuTimer at the start of the release.

DependencyObject (unoplatform#26142, backport of unoplatform#26123)
- Takes upstream, then the code of unoplatform#25839 (97a2f17), which replaces
  this branch's unoplatform#25102 ports (800247f, be41da6, fd40bf9,
  7546379). DependencyObjectStore.Binder.cs, .alc.cs,
  Application.Alc.cs, ResourceDictionary.alc.cs and their tests now
  match unoplatform#25839; this drops 800247f's own
  SingleParentNonFE_Parent_Removes_Value_Then_Next_Parent_Starts_Clean
  test, which unoplatform#25839 doesn't have.

Imaging (unoplatform#25597, unoplatform#25915, unoplatform#25911, unoplatform#25897)
- Keeps this branch's side: it has the later forms of these fixes
  (unoplatform#25124, unoplatform#25559, unoplatform#25633 and unoplatform#26370 ports), where upstream has the
  6.8-only unoplatform#25597 design and earlier snapshots. Upstream's shared
  download, IgnoreImageCache and failed-load fixes are covered by
  SharedImageDecode. Adds upstream's When_Source_Outlives_Brushes test.

ToolTip (unoplatform#26098, backport of unoplatform#25137 and unoplatform#26096)
- Keeps this branch's unoplatform#25137 port and adds unoplatform#26096 in master's form: the
  service releases only a ToolTip it opened, and leaves one the app
  opened during the show delay to the app. Keeps both sides' tests.

Flyout (unoplatform#26062, backport of unoplatform#26060)
- This branch had the same commits already; keeps the "Keep
  non-resizable flyouts in window" port and its tests.

Also removes a helper pair in Given_AccessibleTextBox that both sides
added.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/automation Categorizes an issue or PR as relevant to project automation platform/wasm 🌐 Categorizes an issue or PR as relevant to the WebAssembly platform

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants