callstack / callstack/react-native-paper
refactor(banner): make hidden content inert and fix live region semantics
- Ngôn ngữ chính
- TypeScript
- Star
- 14.5k
- Fork
- 2.2k
- Merge trung bình
- 5 ngày 23 giờ
- Pull request đã merge (30 ngày)
- 12
Mô tả
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.
Hướng dẫn đóng góp
Hướng nghiên cứu
Bắt đầu từ phần triển khai Banner và các bài kiểm thử animation hiện có; kiểm tra cách visible, actions, role/aria-live và animation callbacks hiện đang được xử lý. Được xem là hoàn tất khi nội dung bị ẩn không hoạt động, thông báo hoạt động đúng trên web, Android và iOS, actions tự xuống dòng và được giới hạn ở hai, focus được khôi phục, và callbacks chỉ chạy khi thực sự có chuyển đổi khả năng hiển thị.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- react-native, typescript
- Lĩnh vực
- accessibility, mobile
- Loại issue
- Tái cấu trúc
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức độ hoạt động
- Sôi nổi
- Độ rõ ràng
- Khá rõ ràng
- Mức phù hợp với người mới
- 45/100