Skip to content

fix(native): only keyboard focus holds the Swift tooltip open - #8441

Merged
cixzhang merged 1 commit into
mainfrom
native/swift-tooltip-focus-hold
Oct 25, 2026
Merged

cixzhang merged 1 commit into
mainfrom
native/swift-tooltip-focus-hold

Conversation

@cixzhang

Copy link
Copy Markdown
Contributor

User need

On a Mac, a person clicks an avatar that has a tooltip, then rests the pointer on it. When the pointer moves away, the tooltip should go away, as it does on the web. Today the bubble stays up, because the focus the click left behind still counts as holding it.

Problem and solution fit

  • Only keyboard focus holds the bubble. Focus a click brought no longer keeps a pointer-opened bubble up after the pointer leaves, as core's :focus-visible check keeps such focus from opening or holding the tooltip.
  • The record states two cases it left out:
    • where the bubble goes on its first show, before the window's safe area and the bubble's own size are known: above the view with no slide, moving into place on the next frame;
    • that a scroll or pinch starting on the view closes a tap-opened bubble. INV5's verification row now names it.

Current specification

  • architecture:native-swift-library-tooltip (packages/native/specs/swift/library/tooltip.spec.md), INV1, INV3, INV5.
  • spec:AST-082 FR3: a platform honors a core prop as core does, here core's useTooltip focus handling.

Public delta and caller burden

None. The tooltip is internal.

Design and alternatives

  • A new holdsByFocus(isFocused:focusIsKeyboard:) is the one place that decides whether focus holds the bubble, and update() uses it. It replaces an isShown && isFocused clause that let any focus hold a shown bubble.
  • First show was already correct for its next frame, so only the record changes there.

Evidence

  • TooltipPresenterTests.swift: a clicked view's bubble hides when the pointer leaves, and keyboard focus holds it.
  • Not measured: hover and focus need an app-hosted UI test, which the package does not have yet.

Open-work search

Searched open pull requests for TooltipPresenter, tooltip.spec.md, and focusIsKeyboard. Only the components that use the tooltip (#8306, #8323, #8328) touch it, and none change the presenter.

Scope

  • One capability is added.
  • Unrelated fixes, cleanup, and policy changes were removed or split.
  • Consumer docs, focused tests, and migration evidence ship with the feature.
  • Public text and artifacts contain no internal Meta context.

Writing rubric results

  • R1 narration: none; the record states the behavior.
  • R2 research: none.
  • R3 evidence: bases name AST-082 and core's tooltip; no pull request or commit.
  • R4 rejected alternatives: none.
  • R5 change references: none.
  • R6 open questions: none.

Testing

  • swiftc -typecheck of AstryxNative, Astryx, and the tests passes; check:knowledge passes.
  • The Swift tests run in CI on macOS and on the iOS Simulator.

Every changed file is under packages/native/.

@vercel

vercel Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
astryx Ready Ready Preview Oct 25, 2026 7:28pm UTC

Request Review

@astracat-bot

astracat-bot Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

AI review status for this pull request.

Review status Updated
🟢 Reviewed (for maintainers only) Oct 25, 2026, 7:54 PM UTC

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label Oct 25, 2026
@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 510f8df

@github-actions

github-actions Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

PR Analysis Report

Previews ready: Storybook and Sandbox were built with this PR's docsite. CI evidence will be added when available.

View Storybook for this PR · View Sandbox for this PR

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Changes requested for one sentence: the new INV1 text in tooltip.spec.md says the first show opens above the view, but placesAbove opens it below for a view within 20 points of the window's top, so the record and the code disagree there. Qualifying that sentence (for example, "opens above the view, or below it when even the gap does not fit under the space.s4 floor") is all that is needed; the click-focus fix itself matches core's useTooltip, and its test passes on macOS and the iOS Simulator. Optionally, the new test calls holdsByFocus and wantsBubble directly rather than update(), so moving the composed decision into one helper that update() calls would let it guard the fix.

[Automated review]

Comment on lines +72 to +74
On its first show, before the window's safe area and the bubble's own size
are known, the bubble opens above the view with no slide, and moves into
place on the next frame.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This doesn't hold near the window's top. On the first show the bubble's height and the safe top are still zero, so placesAbove puts the bubble above only when the view's top is at least space.s1 + space.s4 (20 points in the generated themes) below the window's top. Nearer the top it opens below and stays there, which is what INV1's own rule gives. Suggested wording: "the bubble opens above the view, or below it when even the gap does not fit under the space.s4 floor, with no slide, and moves into place on the next frame."

Comment on lines +161 to +170
@Test func aClickedViewsBubbleHidesWhenThePointerLeaves() {
typealias P = TooltipPresenter
// Focus a click brought does not hold the bubble once the pointer leaves.
let held = P.holdsByFocus(isFocused: true, focusIsKeyboard: false)
#expect(!held)
#expect(!P.wantsBubble(hasText: true, isHovering: false, isHoveringBubble: false, isFocused: held, focus: .tabStop))
// Keyboard focus holds it.
#expect(P.holdsByFocus(isFocused: true, focusIsKeyboard: true))
#expect(!P.holdsByFocus(isFocused: false, focusIsKeyboard: true))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Optional: this composes holdsByFocus and wantsBubble the way update() does, but it never calls update(), so putting || (isShown && isFocused) back there would still pass. A static that returns the composed decision, which update() then calls, would let this test guard the fix.

…cord states its first show and scroll-on-view dismissal
@cixzhang
cixzhang force-pushed the native/swift-tooltip-focus-hold branch from 510f8df to 4d90e40 Compare October 25, 2026 19:20
@cixzhang

Copy link
Copy Markdown
Contributor Author

/approve-spec 4d90e40

@astracat-bot astracat-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Approval recommended: the click-focus fix now matches core's useTooltip, and both points from the earlier review are addressed, since the INV1 first-show sentence names the below case and the new test exercises showsBubble, the decision update() calls. The record edits carry your exact-head /approve-spec, and the Swift tests pass on macOS and the iOS Simulator. Nothing else is required here; as a follow-up, the native record and core could settle whether a keyboard-focused bubble should outlast a passing pointer, which core hides today.

[Automated review]

@cixzhang
cixzhang merged commit cce99e4 into main Oct 25, 2026
71 checks passed
@github-actions
github-actions Bot deleted the native/swift-tooltip-focus-hold branch October 26, 2026 06:31

This branch was successfully deployed

1 active deployment
Preview — 4d90e40d Deployed Oct 25, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant