Skip to content

fix(SelectLite): 下拉位置错位 + 关闭动画 + 滚外部自动收缩 (v1.3.1-7)#426

Merged
appergb merged 1 commit into
betafrom
fix/v1.3.1-7
May 12, 2026
Merged

fix(SelectLite): 下拉位置错位 + 关闭动画 + 滚外部自动收缩 (v1.3.1-7)#426
appergb merged 1 commit into
betafrom
fix/v1.3.1-7

Conversation

@appergb
Copy link
Copy Markdown
Collaborator

@appergb appergb commented May 12, 2026

User description

v1.3.1-6 装机后用户报 macOS LLM provider 下拉 3 个问题。一次性收。

1. 位置错位(根因)

positionPopover useCallback deps []useLayoutEffectopen=true fire 一次 —— 此时 popoverRef.current 还没挂载,popoverWidth 用 trigger.width 兜底。Popover mount 后真实宽度(长选项撑大)≠ trigger 宽,但 effect 不再重算,位置漂掉。

:popoverRef 改 callback ref,每次 mount 时 RAF 推到下一帧重算 anchor,拿到真实宽。Math.max(popoverRect.width, rect.width) 保证 popoverWidth 不低估。

2. 滚外部应自动收缩

之前 scroll 触发 reposition。改成 capture phase 监听 scroll + wheel —— popover 内部 scroll(长列表)保留打开,外部 scroll/wheel 直接 closeMenu()。window resize 也强制关闭。

3. 关闭动画

leaving state,popover unmount 前播 ol-select-pop-out 反向 keyframe(opacity 1→0 + translateY 0→-4 + scale 1→.98,跟入场对偶),140ms 后真正卸载。

Test plan

  • macOS: Settings → 模型与凭据 → LLM 供应商下拉,popover 紧贴 trigger 下方对齐
  • macOS / Windows: 点击 trigger 外区域,popover 平滑淡出 ~140ms 不再瞬时消失
  • macOS / Windows: popover 打开时滚动 Settings 主面板,popover 自动关闭
  • macOS / Windows: popover 内部长列表滚动(如 LLM 提供商列表),popover 关闭
  • Windows: window resize 时 popover 关闭(避免错位的尴尬中间态)

PR Type

Bug fix, Enhancement


Description

  • Fix popover misalignment: callback ref + requestAnimationFrame recalculates position with real popover width

  • Add exit animation: leaving state triggers reverse keyframe, delays unmount by 140ms

  • Auto-close on external scroll/wheel/resize: capture-phase listeners close menu; internal scroll preserved

  • Remove useLayoutEffect repositioning; streamline event handling


Diagram Walkthrough

flowchart LR
  A["Trigger open"] --> B["Callback ref on popover mount"]
  B --> C["RAF reposition with real width"]
  C --> D["Correct popover alignment"]
  B --> E["Add scroll/wheel/resize listeners"]
  E --> F["External event triggers close"]
  F --> G["Leaving state starts exit animation"]
  G --> H["Unmount after 140ms"]
Loading

File Walkthrough

Relevant files
Bug fix
SelectLite.tsx
Fix SelectLite positioning, close animation, and external scroll close

openless-all/app/src/components/ui/SelectLite.tsx

  • Replaced popoverRef with callback ref that uses requestAnimationFrame
    on mount to fix misalignment when popover content is wider than
    trigger
  • Added leaving state to delay unmount until 140ms exit animation
    (ol-select-pop-out) completes
  • Replaced scroll repositioning with capture-phase scroll/wheel
    listeners to close popover when scrolling outside; added resize
    listener to force close
  • Removed useLayoutEffect for positioning and its reflow handler;
    cleaned up imports and comments
+62/-25 
Enhancement
global.css
Add exit animation keyframes for SelectLite popover           

openless-all/app/src/styles/global.css

  • Added @keyframes ol-select-pop-out for reverse exit animation: fades
    out, moves up 4px, scales to 0.98
+6/-0     

用户在 macOS 上反馈 LLM provider 下拉 3 个问题:
1. 位置完全错位(popover 跟 trigger 不对齐)
2. 没有关闭动画
3. 在 popover 外滚动应自动收缩,但当前会跟着 trigger 重定位

[位置错位根因]
positionPopover useCallback deps [],useLayoutEffect 在 open=true 时 fire
一次 —— 此时 popoverRef.current 还没挂载,popoverWidth 用 trigger.width 兜底。
Popover mount 后真实宽度 ≠ trigger 宽(长选项撑大),但 effect 不再重算。

修法:popoverRef 改用 callback ref,每次 popover DOM mount 时 RAF 推到下一帧
重算 anchor,拿到真实 popover 宽。同时 positionPopover 用 max(trigger 宽,
popoverRect 宽) 保证 popoverWidth 不低估。

[滚动外部 → 关闭]
之前 useLayoutEffect 监听 scroll 事件做 reposition。改成监听 'scroll' + 'wheel'
capture phase,target 在 popover 内(popoverRef.contains)保留打开(长列表
内部 scroll 不关),target 在外部 → closeMenu。同时 window resize 也强制关闭。

[关闭动画]
加 leaving state 让 popover 在 unmount 前播 ol-select-pop-out 反向 keyframe
(opacity 1→0 + translateY 0→-4 + scale 1→.98,跟入场对偶)。140ms 后真正
unmount + 清 anchor / highlight。
@appergb appergb merged commit db86a14 into beta May 12, 2026
4 checks passed
@appergb appergb deleted the fix/v1.3.1-7 branch May 12, 2026 11:14
appergb pushed a commit that referenced this pull request May 12, 2026
…ing 黑底

[bump] v1.3.1-6 → v1.3.1-7。本版本含 PR #426 merged(SelectLite 位置/动画/
滚外关)+ 本 commit 的两件用户实机 surface:

[Capsule 磨砂玻璃 Windows 路径打开]
之前 Capsule.tsx Windows 路径 useBackdrop = os !== 'win' 强制关掉 backdrop-filter,
让 Windows 胶囊看上去就是白色 pill 没玻璃感。WebView2 现在支持 backdrop-filter,
打开让 Windows 玻璃感对齐 macOS:
- Pill: backdrop-filter blur(28px) saturate(180%)
- CircleButton (cancel): blur(12px) saturate(160%)

[Thinking 字体重做]
- 字号 17 保持,字重 700 → 600 稍细
- 底字浅亮黄 #FCD34D → var(--ol-ink) 黑(用户:"亮黄太显眼,黑底更稳")
- 中段扫光 var(--ol-blue) 深蓝不变(继续作为高对比"滑动"标志)
@github-actions
Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Popover never opens

anchor is still required for rendering, but it is now only set from the popover ref callback. On the first open anchor is null, so the portal never mounts, the ref callback never runs, and the menu cannot appear.

// popover ref callback:每次 popover DOM mount/unmount 调一次。
// 关键:mount 时拿到真实 popover 宽(content 撑大),requestAnimationFrame
// 推到下一帧 paint 前再重算 anchor —— 修复"first paint 用 trigger 宽 fallback 后
// popover 位置漂掉"的 bug。
const setPopoverRef = useCallback(
  (node: HTMLDivElement | null) => {
    popoverRef.current = node;
    if (node) {
      requestAnimationFrame(() => positionPopover());
    }
  },
  [positionPopover],
);
Exit timer race

closeMenu() schedules a timeout but never cancels any pending one. If the menu is reopened before the 140ms delay expires, the old timer still fires and closes it again; the same delayed state update can also hit after the component unmounts.

const closeMenu = () => {
  if (!open) return;
  setLeaving(true);
  window.setTimeout(() => {
    setOpen(false);
    setLeaving(false);
    setHighlight(-1);
    setAnchor(null);
  }, EXIT_ANIM_MS);

appergb pushed a commit that referenced this pull request May 12, 2026
[CRITICAL fix: SelectLite 所有下拉打不开]
v1.3.1-7 (PR #426) 我引入了 deadlock:
- anchor 初始 null + portal 条件 `open && anchor && createPortal(...)`
- 我删了 useLayoutEffect 在 open=true 时调 positionPopover
- 改成 popoverRef callback ref(只在 DOM mount 时 fire)
- 但 popover DOM 挂载条件依赖 anchor 非 null → anchor 永 null → 死锁

用户报"所有竖向切换横幅的界面全都打不开了"——LLM 供应商 / ASR 提供商 /
paste shortcut / mirror / language / polish mode 等 SelectLite 下拉全部
点击无反应。这是我的 review 失误,深表歉意。

修:加回 useLayoutEffect 在 open=true 时调 positionPopover 一次(用 trigger
宽 fallback 立即设 anchor),保留 callback ref RAF 在 popover mount 后用
真实宽精确重定位。两步合并:anchor 立即不为 null → portal 渲染 → callback
ref 触发 → 重新精确定位。

[玻璃统一处理]
用户:"现在完全没有磨砂玻璃的效果,应该把底下的细控组件、边栏以及盖栏
等设置的边栏采用同一种方式处理"。

之前 main content (`<main>`) 用 `var(--ol-surface)` 完全不透明白底,跟透明
sidebar 视觉割裂。改成 `rgba(255,255,255,0.62)` + `backdrop-filter blur(18px)
saturate(170%)`,跟 sidebar 一起坐在 WindowChrome 磨砂底板上,全应用一块
连续玻璃。

[版本]
1.3.1-7 → 1.3.1-8,6 处版本号同步。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant