Skip to content

Conversation

@passabilities
Copy link
Contributor

@passabilities passabilities commented Jan 27, 2026

Summary

Fixed the text formatting toolbar not working properly on mobile web browsers.

Problem

The toolbar had multiple issues on mobile devices:

  • It would render off-screen or be covered by the virtual keyboard
  • The flag-based rendering system caused visibility issues on mobile
  • Long-press text selection didn't trigger the toolbar
  • Wide toolbars could overflow the viewport

Solution

  • Use fixed positioning at bottom of screen on mobile devices
  • Position toolbar above virtual keyboard using Visual Viewport API
  • Handle toolbar visibility directly via selectionchange event
  • Bypass flag-based rendering system on mobile to avoid rendering issues
  • Add touchend listener to handle long-press text selection
  • Limit toolbar max-width to viewport minus padding
  • Enable horizontal scrolling for overflow content

Test plan

  • Tested on mobile Safari (iOS)
  • Tested on mobile Chrome (Android)
  • Verified desktop browsers still work correctly
  • Verified the toolbar is fixed to the bottom of the screen and above virtual keyboard
  • Verified long-press text selection triggers toolbar

Summary by CodeRabbit

  • Improvements
    • Mobile toolbar now anchors to the bottom, adapts width, and repositions dynamically to stay above on-screen keyboards.
    • Toolbar visibility is context-aware, showing when text is selected and hiding otherwise; touch interactions are handled for reliable toggling.
    • Desktop experience remains unchanged.

✏️ Tip: You can customize this high-level summary in your review settings.

- Use fixed positioning at bottom of screen on mobile devices
- Handle toolbar visibility directly via selectionchange event
- Position toolbar above virtual keyboard using Visual Viewport API
- Bypass flag-based rendering system on mobile to avoid rendering issues
- Add touchend listener to handle long-press text selection
- Limit toolbar max-width to viewport minus padding
- Enable horizontal scrolling for overflow content
- Allow pan-x touch action for toolbar scrolling
@CLAassistant
Copy link

CLAassistant commented Jan 27, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 27, 2026

📝 Walkthrough

Walkthrough

Adds mobile-specific toolbar behavior: when running on mobile the toolbar is appended to the shadow DOM, fixed to the bottom, hidden by default, and shown/positioned based on text selection and Visual Viewport resize/scroll (keyboard) events; desktop behavior is preserved.

Changes

Cohort / File(s) Summary
Mobile Toolbar Positioning & Visibility
blocksuite/affine/widgets/toolbar/src/toolbar.ts
Added IS_MOBILE conditional branch. On mobile: append toolbar to shadow DOM, set position: fixed at bottom, reduced max-width, initially hidden; add updatePosition using Visual Viewport API, attach resize/scroll, selectionchange and touchend listeners (debounced) to toggle visibility and render content; ensure disposables cleanup. Desktop flow retained with early-return guards.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant Document
  participant VisualViewport
  participant Toolbar

  User->>Document: touch / select text
  Document->>Document: dispatch selectionchange / touchend
  Document->>Toolbar: check selection -> request show
  VisualViewport->>Toolbar: provide viewport height/offset (resize/scroll)
  Toolbar->>Toolbar: updatePosition() (compute bottom offset)
  Toolbar->>Document: set style (position: fixed, bottom: Xpx), render content, show
  User->>Document: dismiss keyboard or clear selection
  Document->>Toolbar: request hide
  Toolbar->>Toolbar: hide, cleanup listeners as needed
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped down to the mobile screen,

fixed my paws where keyboards lean.
When you select, I pop in view,
I shift and snug — just for you! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title 'fix(mobile): fixed toolbar position' is misleading. The actual PR summary describes comprehensive mobile browser support including visibility handling, virtual keyboard positioning, and long-press selection support—not just toolbar positioning. Update the title to better reflect the main changes, such as 'fix(mobile): add mobile browser support for toolbar' or 'fix(toolbar): improve mobile web toolbar handling' to accurately represent all the key improvements.
✅ Passed checks (2 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@blocksuite/affine/widgets/toolbar/src/toolbar.ts`:
- Around line 312-315: The code calls sel.getRangeAt(0) which can throw if
sel.rangeCount is 0; update the selection check to ensure sel.rangeCount > 0
before calling getRangeAt (e.g., compute hasSelection as sel && sel.rangeCount >
0 && !sel.isCollapsed && sel.toString().length > 0) or wrap getRangeAt in a safe
conditional/try-catch; modify the variables in this block (sel, hasSelection,
range) so range is only assigned when the rangeCount guard passes.
- Around line 328-335: The pending timeouts created for selectionchange and
touchend (the local timeout variable and the anonymous setTimeout in
disposables.addFromEvent(host, 'touchend', ...)) are not cleared on component
disconnect, risking updateToolbar running after teardown; store the touchend
timeout reference alongside the existing timeout variable and ensure both are
cleared inside the component's dispose/cleanup (where disposables are disposed)
by calling clearTimeout on each reference before nulling them so updateToolbar
is never invoked after disconnect.
- Around line 275-287: The toolbar loses theme-specific styles when appended to
document.body because darkToolbarStyles/lightToolbarStyles are scoped inside
AffineToolbarWidget's shadow DOM and no data-app-theme is set on the toolbar;
update the logic that appends the toolbar (the code that sets styles and calls
document.body.append(toolbar)) to either set the toolbar's data-app-theme
attribute to the current theme (e.g., copy from AffineToolbarWidget or document
root) or inject the darkToolbarStyles/lightToolbarStyles into document.head so
the toolbar can pick up theme rules; locate references to AffineToolbarWidget,
darkToolbarStyles, lightToolbarStyles, and the toolbar element to implement the
chosen fix.
🧹 Nitpick comments (1)
blocksuite/affine/widgets/toolbar/src/toolbar.ts (1)

272-338: Consider consolidating mobile checks to avoid unnecessary flag operations.

The current implementation toggles flags (e.g., Flag.Text, Flag.Block) on mobile even though the effects that consume them early-return. While this works correctly, it results in unnecessary signal updates and subscription callbacks.

For a cleaner separation, consider wrapping the flag-dependent subscription blocks (lines 342-591) in a !IS_MOBILE check, or use a more unified approach where mobile has its own dedicated handlers.

This is not blocking since the current approach is functionally correct.

- Keep toolbar in shadowRoot so theme styles naturally apply
- Add top: auto to override default positioning from static styles
- Add rangeCount check to prevent DOMException when accessing selection range
- Clear timeouts on component disconnect to prevent stale updates
@codecov
Copy link

codecov bot commented Jan 28, 2026

Codecov Report

❌ Patch coverage is 2.77778% with 35 lines in your changes missing coverage. Please review.
✅ Project coverage is 55.62%. Comparing base (5041578) to head (5383703).
⚠️ Report is 1 commits behind head on canary.

Files with missing lines Patch % Lines
blocksuite/affine/widgets/toolbar/src/toolbar.ts 2.77% 32 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           canary   #14329      +/-   ##
==========================================
- Coverage   56.05%   55.62%   -0.43%     
==========================================
  Files        2809     2809              
  Lines      147733   147768      +35     
  Branches    22216    22119      -97     
==========================================
- Hits        82805    82193     -612     
- Misses      62295    62924     +629     
- Partials     2633     2651      +18     
Flag Coverage Δ
server-test 74.51% <ø> (-0.87%) ⬇️
unittest 32.40% <2.77%> (-0.02%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@passabilities passabilities changed the title fix(toolbar): add mobile browser support fix(mobile): fixed toolbar position on mobile browser Jan 28, 2026
@passabilities passabilities changed the title fix(mobile): fixed toolbar position on mobile browser fix(mobile): fixed toolbar position Jan 28, 2026
@passabilities
Copy link
Contributor Author

I've updated the description to include screenshots of the problem I'm having on mobile

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

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants