callstack / callstack/react-native-paper
refactor(banner): make hidden content inert and fix live region semantics
- 主要语言
- 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