fix(a11y): SelectLite 键盘高亮自动滚动 + popover 横向 viewport clamp
pr-agent #419 第二轮 review 指出的两个 focus areas: 1. Keyboard scroll:长 dropdown 超过 280px 时 ArrowUp/Down 切换 highlight 不会把高亮项 scroll into view,键盘用户看不到当前选中。新增 useEffect 监听 highlight 改变,找到 data-option-index 对应 DOM 元素后 scrollIntoView({ block: 'nearest' }),已可见的项不会被强行滚动。 2. Viewport clipping:popover 用 trigger.left 作 anchor,没有横向边界 clamp。窗口右边的 select 弹出后可能溢出屏幕。positionPopover 改成测 真实 popover 宽度(首次开打用 trigger 宽度估),然后把 left clamp 到 [8, viewport - width - 8] 区间。 不动 keyboard 导航 / disabled 处理 / vertical flip 等已 verified 行为。
This commit is contained in:
1 parent
70917cb027
commit
6c7598a858
1 file changed
+23
-5
@@ -80,12 +80,18 @@ export function SelectLite({
|
||||
const trigger = triggerRef.current;
|
||||
if (!trigger) return;
|
||||
const rect = trigger.getBoundingClientRect();
|
||||
// 默认在触发器下方;若下方空间放不下 popover(按 maxHeight 280 估),翻转向上以避免被视口裁剪。
|
||||
const popoverMaxHeight = popoverRef.current?.getBoundingClientRect().height ?? 280;
|
||||
const popoverRect = popoverRef.current?.getBoundingClientRect();
|
||||
const popoverHeight = popoverRect?.height ?? 280;
|
||||
const popoverWidth = popoverRect?.width ?? rect.width;
|
||||
// 纵向:默认在触发器下方;若下方空间放不下 popover,翻转向上以避免被视口裁剪。
|
||||
const spaceBelow = window.innerHeight - rect.bottom;
|
||||
const flipUp = spaceBelow < popoverMaxHeight + 8 && rect.top > popoverMaxHeight + 8;
|
||||
const top = flipUp ? rect.top - popoverMaxHeight - 4 : rect.bottom + 4;
|
||||
setAnchor({ left: rect.left, top, width: rect.width });
|
||||
const flipUp = spaceBelow < popoverHeight + 8 && rect.top > popoverHeight + 8;
|
||||
const top = flipUp ? rect.top - popoverHeight - 4 : rect.bottom + 4;
|
||||
// 横向:在窗口右边的 select 可能让 popover 溢出屏幕;clamp 到 [8, viewport - width - 8] 区间。
|
||||
const minLeft = 8;
|
||||
const maxLeft = Math.max(minLeft, window.innerWidth - popoverWidth - 8);
|
||||
const left = Math.min(Math.max(rect.left, minLeft), maxLeft);
|
||||
setAnchor({ left, top, width: rect.width });
|
||||
}, []);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
@@ -100,6 +106,17 @@ export function SelectLite({
|
||||
};
|
||||
}, [open, positionPopover]);
|
||||
|
||||
// 键盘 ArrowUp/Down 改 highlight 后把高亮项 scroll into view —— 长 dropdown 超过
|
||||
// maxHeight 280 时键盘用户能看到当前高亮。{ block: 'nearest' } 避免把已经可见的项
|
||||
// 强行滚到顶部,符合 listbox 滚动惯例。
|
||||
useEffect(() => {
|
||||
if (!open || highlight < 0) return;
|
||||
const target = popoverRef.current?.querySelector(
|
||||
`[data-option-index="${highlight}"]`,
|
||||
) as HTMLElement | null;
|
||||
target?.scrollIntoView({ block: 'nearest' });
|
||||
}, [highlight, open]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const handlePointerDown = (event: MouseEvent) => {
|
||||
@@ -238,6 +255,7 @@ export function SelectLite({
|
||||
return (
|
||||
<div
|
||||
key={option.value || `__opt_${index}`}
|
||||
data-option-index={index}
|
||||
role="option"
|
||||
aria-selected={isSelected}
|
||||
aria-disabled={option.disabled}
|
||||
|
||||
Reference in new issue
Block a user