Skip to content

fix: anchor popovers to the final projected action - #258

Closed
rdlabo wants to merge 6 commits into
fix/vertical-bars-toolbar-actionsfrom
fix/projected-popover-anchor
Closed

rdlabo wants to merge 6 commits into
fix/vertical-bars-toolbar-actionsfrom
fix/projected-popover-anchor

Conversation

@rdlabo

@rdlabo rdlabo commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

Popovers opened by Vertical Bars actions anchor to the hidden source button instead of the visible control. This change forwards the final projection bounds and uses them in the existing popoverEnterAnimation.

Forwarded clicks retain their original source target and expose projectionFrame in Web viewport CSS pixels plus the rail's projectionEdge. Web actions measure their visible clone; native actions convert their UIKit bounds into WebView coordinates. A temporary layout anchor compensates for page offsets and scaling without overriding source DOM geometry. Stock or custom animations do not automatically consume this metadata.

Default bottom callouts on a Vertical Bar open inward, centered on the button, so their arrows can point at the control. Other explicit sides retain their existing behavior. Positioning and animation continue to use the shared popover implementation.

While an overlay retires native controls, Web toolbar/back-button replacements retain the native action bounds. Source groups remain hidden and empty action toolbars stay collapsed. Dismissal restores native projection. The demo header action and documentation cover this flow.

Dependency and design review

Draft: depends on the numeric offset proposal in ionic-theme-utils #9. The previous boolean-based changes (#7 and #8) and release preparation were reverted on the shared library's main branch in 040f464. The already-published 0.1.3 package remains published, but this PR still depends on 0.1.2 and does not adopt it.

The proposed caller passes verticalOffset: 0 only for horizontal popovers on a projected Vertical Bar; ordinary popovers retain the library default. No merge or release is authorized by this draft. Integration and visual revalidation with the replacement utility are pending.

Validation before the offset API redesign

The results and screenshot below document the previous candidate, not verification of the new numeric-offset proposal.

  • Ten projection regression cases passed: left/right rails, trigger/event references, offset/scaled pages, disabled actions, and native-to-Web overlay handoff.
  • The handoff regression checks replacement host and rendered button bounds, inward popover alignment, hidden source controls, and native restoration after dismissal.
  • TypeScript/CSS builds, root lint, demo production build, and Duo native build passed.
  • Duo simulator verification passed; the screenshot shows the callout arrow centered on the projected action. Investigation scripts and simulator tests remain outside the repository.
  • The shared utility's 20 tests and CI passed.

Closes #253.

Stacked on #254 to keep the popover changes separate.


Devin Review

iPhone Duo: the popover arrow points at the projected action while Web replacements retain native bounds

@devin-ai-integration devin-ai-integration Bot 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.

Devin Review found 3 potential issues.

Devin Review

Comment thread src/projection/click.ts
clientY: frame.y + frame.height / 2,
});
Object.defineProperty(event, 'projectionFrame', { value: frame });
target.dispatchEvent(event);

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.

🟡 無効化直後の投影ボタンが操作を実行する

Web 投影の再同期前に元ボタンが無効化されても、クローンからクリックを転送できます。合成イベントは無効なボタンにも配信され、従来の .click() と異なり操作ハンドラが動きます。

Learn more

Web のレールボタンは、クリック時に元の要素へ操作を転送します。元ボタンの状態が変わっても、クローンの再同期は次の更新まで遅れることがあります。isCurrentToolbarAction は表示と投影資格を確認しますが、disabled 状態は確認しません。以前の HTMLElement.click() は無効なネイティブボタンを起動しませんでした。一方、dispatchEvent は合成クリックを直接配信するため、無効化済みの元ボタンのハンドラが実行されます。

Example: 保存ボタンを無効化した直後、まだ有効に見えるレールのクローンを押すと、元の無効なボタンに登録された保存ハンドラが動きます。従来は押しても保存されませんでした。

Recommended fix: projectedClick で元のターゲットとそのホストの disabled 状態を確認し、無効ならイベントを送らないようにしてください。Web 側の転送経路でも再同期前の状態を検証してください。

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment thread src/projection/popover.ts Outdated
Comment on lines +26 to +27
anchor.style.left = `${frame.x + (frame.x - placed.x)}px`;
anchor.style.top = `${frame.y + (frame.y - placed.y)}px`;

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.

🟡 拡大縮小したページではポップオーバーの基準位置がずれる

元ページに scale() があると、固定アンカーの補正量とサイズにも倍率がかかります。投影ボタンの座標と一致せず、ポップオーバーがボタンから離れて表示されます。

Learn more

レールのボタンは元ページの表示位置とは独立して配置されます。ポップオーバーを置く際、このコードは元ページに固定アンカーを追加し、測定した位置の差をそのまま CSS の left と top に足します。親ページが scale() されていると、CSS 座標の増分にも倍率がかかるため、一度の補正では目的の位置に届きません。アンカー自体の幅と高さも倍率で変わります。縦レールの候補は祖先の変換だけを理由に除外されない設計です(readVisible)。

Example: 元ページが scale(0.8) で、アンカーの測定位置を右へ 100px 補正する場合、ビューポート上では 80px しか移動しません。幅 40px の投影ボタンに対してアンカーの見かけの幅も 32px になります。

Recommended fix: 変換行列の逆行列でビューポートの矩形をアンカーのローカル座標に変換するか、変換されないコンテナにアンカーを置きつつ既存のペイン境界処理を維持してください。位置だけでなく幅と高さも実測した矩形に合わせてください。

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +409 to +410
let roots = (verticalBars.map { [$0.view] } ?? []) + Array(controls.values)
guard let button = roots.lazy.compactMap({ find($0) }).first else { return nil }

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.

🔍 検索アクションの投影位置はフォールバックする可能性がある

検索の開閉イベントは present から送られます。対応する UIView の識別子が見つからない場合は従来のクリック経路になります。検索アクションも位置補正の対象か確認してください。

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

@rdlabo
rdlabo force-pushed the fix/projected-popover-anchor branch from eeea047 to 396bef6 Compare September 30, 2026 15:26
@rdlabo

rdlabo commented Oct 2, 2026

Copy link
Copy Markdown
Contributor Author

I was wrong about this approach! Even after further refinement, the interaction still feels like an imitation rather than a natural native experience. This needs a different approach, so I'm closing this PR.

@rdlabo rdlabo closed this Oct 2, 2026
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