callstack / callstack/react-native-paper

TouchableRipple exposes non-interactive surfaces as disabled controls

Đang mở
#5,070 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

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ả

### Current behaviour

`TouchableRipple` treats "no touch handler was passed" as identical to "this control is disabled":

```ts
// src/components/TouchableRipple/TouchableRipple.native.tsx
const disabled = disabledProp || !hasPassedTouchHandler;
```

That single flag is then handed to `Pressable`, which derives `accessibilityState.disabled` from it. The result is that every non-interactive surface built on `TouchableRipple` is announced to assistive tech as a **disabled control**, even though nothing about it is disabled and it was never meant to be a control.

Nothing looks wrong on screen, which is why this has gone unnoticed. Paper never dimmed these views, so a screenshot is identical either way. The defect is only visible in the accessibility tree.

Measured on the example app, `TextInput` screen, with "Leading icon" enabled. The leading magnifier has no `onPress`; the trailing clear button does:

| platform | probe | decorative magnifier | clear button |
| ---------- | ----------------------- | ------------------------------------------------------ | ----------------------- |
| iOS 18.3 | `AXButton.enabled` | `false` | `true` |
| Android 15 | `uiautomator` `enabled` | `"false"` | `"true"` |
| web | DOM | `` | `` |

The web case is the easiest to confirm and the most severe. A decorative icon becomes a genuinely `disabled` HTML `` with `pointer-events: none`.

VoiceOver and TalkBack both read the disabled bit, so a screen reader user hears "search, dimmed, button" on an element that is purely decorative.

This is not limited to one component. It affects any of the 22 components that render `TouchableRipple` when they are used without a touch handler, including `Chip`, `Button`, `List.Item`, `DataTable.Cell`, `Drawer.Item`, `Menu.Item`, `Card` and `IconButton`. Across Paper's own test suite, 80 rendered elements carry the false `disabled` state.

Some consumers work around it by nulling their own handler to fake a disabled state, which stops working the moment the primitive is corrected. `TextInput.Icon` does exactly this:

```ts
// src/components/TextInput/TextInputIcon.tsx
const onPressHandler = disabled ? undefined : onPress;
```

### Expected behaviour

A component with no touch handler should not be exposed as a control at all: no `accessibilityState.disabled`, no keyboard or D-pad focus, no `` on web.

A component whose caller explicitly passed `disabled` should still be announced as a disabled control, exactly as it is today.

The two cases are different and should not share one flag.

### How to reproduce?

1. Run the example app: `cd example && yarn start`
2. Open the **TextInput** screen and turn on the "Leading icon" toggle. That renders a `TextInput.Icon` with no `onPress`, next to a clear button that has one.
3. Inspect the accessibility tree:
- **iOS**: Xcode > Open Developer Tool > Accessibility Inspector, target the simulator, click the magnifier. Traits read `Button, Not Enabled`.
- **Android**: `adb shell uiautomator dump` and read the node with `resource-id="icon-button"`. It reports `enabled="false"`.
- **Web**: `yarn start --web`, then inspect the magnifier in devtools. It is ``.
4. With VoiceOver or TalkBack on, move to the magnifier. It is announced as disabled.

The same thing is visible anywhere a `TouchableRipple`-based component is rendered without a handler, for example `Plain` or a `List.Item` with no `onPress`.

### Preview

Not applicable. There is no visual difference to screenshot, which is the point: Paper applies no dimming to these views on native, and `styles.disabled` on web is only `cursor: auto`. The defect lives entirely in the accessibility tree, so the tables above are the evidence.

### What have you tried so far?

- Confirmed the mechanism in React Native's source. `Pressable.js:235-236` merges the `disabled` prop into `accessibilityState` and it always wins over `aria-disabled`/`accessibilityState`, so passing `undefined` is the only way to leave the key absent.
- Confirmed the blast radius by walking the accessibility tree across component configurations on a real simulator and emulator, plus the DOM on web.
- Looked at the existing narrow fixes. #5011 ("fix: avoid disabled accessibility state for active chips") addresses this same symptom, but only inside `Chip.tsx`, by injecting an empty `onPress` to defeat the primitive. That swaps a false "disabled" for a false "enabled" and leaves the other 21 components untouched. #5002 also modifies `Chip.tsx` in overlapping ways.
- Note for whoever picks this up: two of Paper's own tests currently encode the bug as intended behaviour (`renders disabled button if there is no touch handler passed` and its `Chip` equivalent, both asserting `toBeDisabled()`), so they need splitting into a "no handler" case and a "disabled prop" case rather than updating in place.

### Your Environment

| software | version |
| ------------------ | ---------------------- |
| ios | 18.3 (simulator) |
| android | 15 / API 35 (emulator) |
| react-native | 0.85.3 |
| react-native-paper | 6.0.0-alpha.0 (`main`) |
| node | 24.13.0 |
| npm or yarn | yarn 4.9.1 |
| expo sdk | 56.0.0 |

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu với src/components/TouchableRipple/TouchableRipple.native.tsx và kiểm tra cách cờ disabled dùng chung được truyền đến Pressable, sau đó xem xét src/components/TextInput/TextInputIcon.tsx và các vị trí khác sử dụng TouchableRipple. Chạy các test no-touch-handler và Chip hiện có, hiện đang xác nhận bug. Được xem là hoàn tất khi các surface không có handler bỏ qua trạng thái accessibility disabled và hành vi focus, trong khi các control được vô hiệu hóa rõ ràng vẫn giữ chúng trên native và web.

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, frontend, mobile
Loại issue
Lỗi
Độ 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
Đặc tả rõ ràng
Mức phù hợp với người mới
55/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.