Skip to content

perf(mobile): debounce keyboard inset writes, fast-path focusout - #239

Merged
MaplumeX merged 1 commit into
mainfrom
fix/mobile-calendar-transition-jank
Oct 19, 2026
Merged

MaplumeX merged 1 commit into
mainfrom
fix/mobile-calendar-transition-jank

Conversation

@MaplumeX

Copy link
Copy Markdown
Owner

Problem

installKeyboardInset wrote --kb-inset on every visualViewport resize/scroll event. During the keyboard open/close animation the viewport fires nearly every frame, so each frame forced a full style recalculation on the root element and a re-layout of AppShell — competing with the dialog / expand animations running at the same time and causing dropped frames. On the way back up, the inset also stayed stale until the keyboard animation finished, so the layout returned to full height late.

Changes

packages/mobile/src/keyboard-inset.ts

  • Debounced viewport updates. resize/scroll now schedule a write that only fires after the viewport has been quiet for KEYBOARD_SETTLE_MS (120 ms), so the animation produces a single jump instead of one write per frame.
  • Deduplicated writes. A current cache skips setProperty when the value is unchanged.
  • focusout fast path. When focus leaves a keyboard-capable element and does not land on another one, the keyboard is definitely closing, so the inset is written to 0 immediately — dialogs opening or tasks collapsing no longer wait for the keyboard animation to finish. Window blur (!document.hasFocus()) is ignored, since focusout also fires when the app is backgrounded and the keyboard may still be up.
  • New isKeyboardTarget() helper. Classifies textarea, contenteditable, and text-like inputs as keyboard targets, excluding non-text input types (button, checkbox, radio, range, file, color, etc.).
  • The focusout listener is removed and the pending timer cleared in the returned cleanup function.

packages/ui/src/components/common/FieldPicker.tsx

  • Added transition-[top,max-height] to the dialog card so it glides to the new centred position when --kb-inset jumps once at the end of the animation, instead of tracking the keyboard frame by frame.

Tests

packages/mobile/src/keyboard-inset.test.ts now uses fake timers and a shared visualViewport stub, and covers:

  • initial write and cleanup (value removed, listeners unbound);
  • multi-frame viewport churn writing nothing until KEYBOARD_SETTLE_MS elapses, then a single write;
  • scroll going through the same debounced path;
  • immediate zeroing when focus leaves an input for a non-input;
  • no zeroing when focus moves between two inputs;
  • no zeroing on focusout caused by window blur;
  • isKeyboardTarget for text inputs, textarea, contenteditable, buttons, checkboxes, and null.

@MaplumeX
MaplumeX merged commit eff6883 into main Oct 19, 2026
4 checks passed
@MaplumeX
MaplumeX deleted the fix/mobile-calendar-transition-jank branch October 19, 2026 04:01
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

Successfully merging this pull request may close these issues.

1 participant