callstack / callstack/react-native-paper

refactor(banner): make hidden content inert and fix live region semantics

未关闭
#5,055 0 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
主要语言
TypeScript
星标
14.5k
派生
2.2k
平均合并
5 天 23 小时
30 天内合并 PR
12

描述

Part of #4990

Clean up Banner accessibility so hidden content is actually hidden/inert, live regions behave consistently across platforms, and animation callbacks only fire when a real show/hide transition happens.

Banner doesn't exist in MD3 anymore, but it's still a supported Paper extension, so this is mostly about making its behaviour internally sane and accessible rather than matching the Material spec exactly.

**Problems**

- Hidden Banner content is still mounted and reachable. It's only translated off-screen and clipped — screen readers can still see it and action buttons stay tabbable. Verified in the browser a11y tree: with `visible={false}`, both actions still have `tabIndex: 0` and can receive focus.
- `role="alert"` + `aria-live="polite"` doesn't really make sense. `alert` normally implies assertive + atomic, while Chrome currently ends up with `alert atomic live="polite"`.
- The live region currently does nothing on Android. RN maps `aria-live` to `accessibilityLiveRegion` on `View`, but not on `Text`, so the prop is effectively ignored there. It only works on web today.
- iOS has no live-region equivalent, so the message isn't announced there either.
- `actions` is unlimited and actions always sit below the message. Material allows max two.
- If a focused action disappears after the actions array changes, focus gets dumped at the top of the page.
- `onShowAnimationFinished` / `onHideAnimationFinished` fire on mount and again when `theme.animation.scale` changes, even though no visibility transition happened. Existing tests already call this out as probably a bug.

**Focus Areas**

- Make hidden content properly inert: no pointer interaction, no a11y exposure, no focus.
- Put the live region on a `View` so Android actually supports it, and keep it scoped to the message so action labels don't cause the whole banner to re-announce.
- Make `role` and `aria-live` agree: polite by default, assertive when explicitly requested.
- Announce manually on iOS without double-announcing on Android/web.
- Limit actions to two and let them reflow depending on available width.
- Restore focus if the currently focused action disappears.
- Run show/hide animations and callbacks only for actual `visible` transitions.

**Proposed API**

One new prop, everything else is behavioural:

```jsx

Your payment failed.

```

- `urgent?: boolean`
- `false` (default): `role="status"` + `aria-live="polite"`
- `true`: `role="alert"` + `aria-live="assertive"`; on iOS it interrupts instead of queueing

**Breaking changes**

- Banner children are unmounted once fully hidden, so local child state is lost after hiding.
- More than two actions are ignored, with a dev warning.

**Notes**

- #5016 retokenizes Banner (motion tokens + easing), but doesn't touch any of this accessibility behaviour.
- This should be checked separately with VoiceOver, TalkBack and the browser accessibility tree. They're different enough that testing only one isn't very meaningful.

贡献指南

打开贡献指南

调研方向

从 Banner 实现及其现有的动画测试开始;检查当前如何处理 visible、actions、role/aria-live 和 animation callbacks。完成的标准是:隐藏内容处于 inert 状态,公告在 Web、Android 和 iOS 上表现正确,actions 重新排列并限制为最多两个,焦点得到恢复,并且 callbacks 仅在实际的可见性转换时运行。

由索引模型根据 Issue 内容生成。

评估

技术栈
react-native, typescript
领域
accessibility, mobile
Issue 类型
重构
难度
4/5
预计耗时
3-5 天
活跃度
活跃
描述清晰度
基本清楚
新手友好度
45/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。