Proto-UI / Proto-UI/Proto-UI

[proposal] Select item-aligned positioning policy (design-only)

Open
#496 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

advanced contribution area: prototypes area: spec needs semantic decision
Dominant language
TypeScript
Stars
35
Forks
15
Avg merge
2d 18h
Merged PRs (30d)
103

Description

Design proposal from #495(2026-08-24)。Non-normative:本文不替代 spec/** authority,但可直接驱动 draft entity、可逆实现与 executable evidence。只有 identity、owner、public guarantee 或兼容性仍无权威答案时,才把该具体选择标为 needs semantic decision。镜像记录:internal/records/2026-08-24-select-item-aligned-positioning-proposal.zh-CN.md

问题

固定 shadcn 基线(apps/v4/registry/new-york-v4/ui/select.tsx line 56)默认 position = "item-aligned":Select popover 打开时,当前选中项对齐在 trigger 位置上(原生 macOS <select> 风格),菜单位置由选中项驱动而非 trigger 驱动。

我们的现状:

  • Base Select Content 只实现 popper 风格 anchored dropdown(packages/prototypes/base/src/select/content.proto.tsplacement: 'bottom'sideOffset: 4align: 'center')。
  • P-BASE-SELECT-CONTENT-DEFERRED-SURFACES 已把 item-aligned positioning 排除在当前 Base guarantee 外。
  • P-SHADCN-SELECT-CONTENT-POSITION-PROP 记录 item-aligned | popper、默认 item-aligned,但只保证参数存在——行为欠账已在 catalog 具名。

Authority map

  • 语义 owner:P-BASE-SELECT-CONTENT / P-SHADCN-SELECT-CONTENT-*;selection truth 归 Root(value state + valueChange event)。
  • Item 侧:asCollectionItem() + anatomy role claim;item 暴露 selected derived state。
  • 放置基础设施:C-ANCHORED-POSITIONING-0001(anchor→floating root 盒子放置 + flip/shift 碰撞 + 有界 lease)、web 实现 packages/modules/positioning/src/web/floating-ui-host.ts
  • Overlay 链路:base select content 经 asOverlayanchored: true 走既有 positioning 连接。

信息通路(candidate)

Root value truth(state)
  → 在同一 Select domain 内解析 selected Item(anatomy/collection part view)
    → 解析其渲染 target 元素
      → host 测量:item 几何、viewport 几何、trigger 几何(host-local,不进 author State)
        → 计算 floating placement 使 item 对齐 trigger
          → collision 回退
            → 写坐标(沿用 left/top 非 transform 投影约定)

C-ANCHORED-POSITIONING-0001 的关系:并行的放置策略,不修改其现有保证。载体形态(floating-ui middleware + config 扩展 / 独立 host-cap / overlay config policy)由实现探针和现有 authority 优先收敛;只有仍然存在实质产品分叉时才进入 needs semantic decision

四个重点的设计回应

1. selected Item target lifetime
  • open 触发后 content 经 Portal 挂载,placement 等待"selected item 已注册"的有界 readiness——沿 P-BASE-DROPDOWN-MENU-CONTENT-A11Y entry-focus readiness 先例:请求被保留,target 就绪时消费;不做零延迟重试或私有 expose handshake。
  • value 为空或不匹配任何 item 时无 selected target,回退 popper 式放置(上游空值行为待 pinned revision 核实)。
  • target 引用绑定单次 content view epoch(同 positioning lease 有界 lifetime);detach/rebuild 后旧引用作废。
  • 几何、解析结果、临时对齐量全部 host-local,不进 author State;author 可见的仍只有 value 与 item selected
2. Viewport scroll 与 measure/place/collision 执行顺序

核心难点:selected item 相对 floating root 的位置取决于 viewport 内部 scrollTop。

  • 策略 A(scroll-first):先设 scrollTop 至规范位 → 测量 → 整体放置 → 碰撞调整。测量即所得,缩放/变换祖先下不易漂移;代价是打开瞬间可能一帧内部滚动跳变。
  • 策略 B(math-first):用 scroll 无关偏移(offsetTop 链)直接算 placement 数学解,再设 scrollTop 对齐。无中间帧,但 transformed/zoomed 祖先下 offsetTop 不可靠。

倾向 A 首轮。碰撞回退级联(candidate):完整对齐 → 垂直翻转侧 → 退回 popper 式 bottom/top → 既有 flip/shift。阈值由 executable evidence 与跨宿主验证收敛。

3. 异步挂载与 open 期间 selection 变化
  • 首开放:placement 一次计算,发生在 content mounted + items registered 之后。
  • open 期间 selection 变化:基线预期不重新放置(Radix/shadcn open 后 selection 变化只改选中态与高亮;待 pinned revision 核实)。若成立,只需 open-time 单次放置 + 既有 autoUpdate 布局观察,不需要 selection-follow 重放置。
  • late item 迟到按第 1 点 readiness 处理,超时回退 popper 式并保留诊断。
4. host 能力缺失时的 fallback

策略表达为宿主中立语义(anchor 点、child 相对偏移、碰撞边界);Web 用 DOM rect/scrollTop 实现;非 Web 宿主若无等价几何能力,降级到现有 anchored popper 行为。因此该策略是 Base guarantee 之上的可选能力(opt-in policy 或下游 styled 层配置),默认路径与今天完全一致,P-BASE-SELECT-CONTENT-DEFERRED-SURFACES 维持成立。

负边界

  • 不拥有 selection truth:Root 继续独家持有 value / valueChange;本策略只读。
  • 不改 C-ANCHORED-POSITIONING-0001 任何现有 criterion。
  • 无第二个 part 获得 ownership;item 的 a11y(aria-selected 等)不变。
  • 不做虚拟化、typeahead;不动 #377 排期;styled-only 层保持 style-only。

开放实现问题(证据优先收敛)

  1. 载体形态:floating-ui middleware + connection config 扩展 vs 独立 host-cap vs overlay config policy。
  2. 策略 A/B 取舍及碰撞回退阈值。
  3. open 期间 selection 变化是否永不重放置(依赖上游 pinned revision 核实)。
  4. 是否现在做一项准备性 spec 细化:把 P-SHADCN-SELECT-CONTENT-COMPATIBILITY-SUBSET / Q-UPSTREAM-DIFFERENCES 的定位差异从泛化表述细化为具名 item-aligned 差异项(单独授权项)。

可执行证据与 falsification

  • falsify「需要新 host-cap」:若纯 floating-ui middleware + config 能同时满足四个重点且不违反 C-ANCHORED-POSITIONING-0001-D/E,则不需要新 host-cap。
  • falsify「策略 A」:transformed/scaled 祖先 + 打开瞬间采样,若 scroll-first 出现可见跳动而 math-first 没有,则重估。
  • 单测:mount→register→measure→place→collision 断言序列;unmatched value 回退;late-item readiness 超时;controlled 模式不受影响。
  • Browser journey(三 runtime):中部选项打开 → selected item 覆盖 trigger;首/尾选项边界钳制;resize/滚动后重定位;dark/light 无关性。

只能由实现或第二消费者验证的结论

  • floating-ui 是否能干净表达该策略(决定载体形态)。
  • 真实碰撞阈值的体感校准。
  • Combobox/Command 未来是否复用同一 contract(通用化提取的门,届时另议)。

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading packages/prototypes/base/src/select/content.proto.ts, packages/modules/positioning/src/web/floating-ui-host.ts, and the cited positioning and dropdown-menu authorities. Use the proposed mount/register/measure/place/collision checks and browser journeys to investigate the unresolved carrier, strategy, thresholds, and selection behavior. Done means the evidence narrows those choices into an implementable policy without changing existing guarantees.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
design, frontend
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
32/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.