SIGN IN SIGN UP

input: Add readonly mode for Input, Textarea and Editor (#2705)

Fixes #2702

A read-only editor previously had to use `disabled`, which dims the
whole editor with `opacity(0.5)`. That is not what a file preview (e.g.:
a Git application) should look like.

`readonly` keeps the normal appearance and only rejects the changes made
by the user. The name follows the HTML `readonly` attribute.

```rust
Editor::new(&state).readonly(true)
Textarea::new(&state).readonly(true)
Input::new(&state).readonly(true)
```

## What a read-only input keeps

| | `disabled` | `readonly` |
| --- | --- | --- |
| Normal colors (no `opacity(0.5)`) | ✗ | ✓ |
| Focus, caret, selection | ✗ | ✓ |
| Copy, Select All | ✗ | ✓ |
| Search (`Find`) | ✓ | ✓ |
| Go to Definition, hover | ✗ | ✓ |
| Replace, Cut, Paste, Code Actions | ✗ | ✗ |
| Typing, IME, undo/redo, indent | ✗ | ✗ |
| `set_value`, `insert`, `replace` | ✓ | ✓ |

## Implementation

- `InputBaseState` gets a `readonly` field, `set_readonly()`, and a
single `is_editable()` predicate (`!disabled && !readonly`) that all the
write paths now go through: `replace_text_in_range`,
`replace_and_mark_text_in_range` (IME), and the edit action
registration.
- The scattered "temporarily clear `disabled`" blocks in `insert` /
`replace` / `replace_text` are folded into `with_edits_allowed()`, so a
programmatic write is never blocked by either mode.
- `is_replaceable()` becomes `replaceable && is_editable()`, so the
search panel offers find-only in read-only mode.
- The context menu disables Cut / Paste / Show Code Actions, but keeps
Copy and Go to Definition — the two a code preview needs.
- The clear button is hidden, and the OS autofill content type is no
longer synced for a read-only input.

## Testing

`crates/story/examples/editor.rs` — the status bar toggle at the bottom
is now `Read only` instead of `Disabled`. The Editor story has a `Read
only` switch, and the Input story shows a read-only field beside the
disabled one.

Added `test_readonly_rejects_user_edits_only`: user input (including
IME) is rejected, programmatic writes still apply, and editing resumes
after leaving the mode.

---

Written with Claude Code, reviewed and adjusted by hand.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
J
Jason Lee committed
bc1b70d6cdfbb39fc952d95b8e10394a1258d157
Parent: 7cf072b
Committed by GitHub <noreply@github.com> on 8/14/2026, 7:18:24 AM