diff --git a/resources/views/docs/mobile/4/edge-components/text-input.md b/resources/views/docs/mobile/4/edge-components/text-input.md
index 2b568b22..dd806d53 100644
--- a/resources/views/docs/mobile/4/edge-components/text-input.md
+++ b/resources/views/docs/mobile/4/edge-components/text-input.md
@@ -48,6 +48,9 @@ All three variants accept the same shared prop set. The bare variant adds a `col
- `placeholder` - Placeholder shown when empty (optional, string)
- `label` - Label rendered above the field (optional, string)
- `supporting` - Helper text rendered below the field (optional, string)
+- `selection` - Where the caret goes when your component
+ changes the field's text: a code-point offset for a caret, or `[$start, $end]` to select a range (optional, int,
+ array or null). See [Placing the caret](#placing-the-caret)
### State
@@ -280,9 +283,12 @@ A few contract details:
- `@selectionChange` is never emitted for `secure` inputs. The callback isn't even serialized when `secure` is set,
and both renderers additionally refuse to emit — so caret telemetry can't leak password-field context.
-- When PHP pushes a new `value` onto the input, the field is replaced wholesale and the caret drops at the end.
- Both platforms report that immediately as a single `(text, length, length)` event, bypassing the debounce — a
- handler that rewrites the bound model will see one follow-up event.
+- When PHP pushes a new `value` onto the input, the field is replaced wholesale and the caret drops at the end, or
+ where [`selection`](#placing-the-caret) says. Both platforms report that immediately as a single event, bypassing
+ the debounce — a handler that rewrites the bound model will see one follow-up event.
+- Pressing a button or pressable while the field is
+ focused sends any pending selection event first, so the press handler sees where the caret is now, not where it was
+ a keystroke ago.
- Discontiguous selections (multi-range, iOS) are reported as a single span from the lowest start to the highest
end, so the range can cover text the user did not select.
- `read-only` inputs don't report on iOS, where read-only implies disabled and the field never focuses; they do on
@@ -294,6 +300,71 @@ Caret and selection reporting requires `nativephp/mobile` 4.0+, which ships the
+### Placing the caret
+
+
+
+The other direction: when your component rewrites the field's text, `selection` says where the caret goes. Without
+it, a value you set puts the caret at the end. That's wrong when you've changed something in the middle, such as
+swapping the `@ja` the user typed for the handle they picked from the suggestions.
+
+@verbatim
+```blade
+@php $message = ''; $caret = null; @endphp
+
+
+```
+@endverbatim
+
+```php
+public string $message = '';
+
+public ?int $caret = null;
+
+/** Where the "@" being typed is, and what follows it, as onCaretMove() found it. */
+public ?array $mention = null;
+
+public function pickMention(string $handle): void
+{
+ $start = $this->mention['start'];
+ $before = mb_substr($this->message, 0, $start).'@'.$handle.' ';
+ $after = mb_substr($this->message, $start + 1 + mb_strlen($this->mention['query']));
+
+ $this->message = $before.$after;
+ $this->caret = mb_strlen($before); // right after "@handle "
+ $this->mention = null;
+}
+```
+
+Offsets are Unicode code points, the same as `@selectionChange` reports. A caret is one offset; pass
+`[$start, $end]` to select a range instead, say a placeholder you've just inserted for the user to type over. Offsets
+past the end are clamped to it, and on iOS an offset inside a character made of several code points (a flag, a
+skin-toned emoji) moves back to the start of that character.
+
+The field only reads `selection` at the moment it takes a new value from your component. It never moves the caret
+while the user types or moves it themselves, even though the attribute stays set, and an echo of what the user typed
+coming back through `native:model` doesn't count as a new value. A field also doesn't read it when it first appears
+with a value: its caret starts at the end.
+
+Set the property back to `null` once the field has the new text, so a value you set later (clearing the field after
+a send, say) puts the caret at the end again. The `@selectionChange` event the field sends after taking your text is
+a good place: when its text matches your property, the caret is where you put it.
+
+
+
+`selection` is ignored on `secure` inputs.
+
## Per-platform icons
@@ -503,6 +574,9 @@ All three elements share the same fluent API (defined on `BaseTextInput`):
- `font(string $name)` - Custom font (file token or config alias)
- `a11yLabel(string $value)`, `a11yHint(string $value)`
- `syncMode(string $mode)`, `debounceMs(int $ms)`
+- `selection(?int $start, ?int $end = null)` - Where the
+ caret goes, or the range to select, when the field takes a new value from your component. `null` leaves it at the
+ end. See [Placing the caret](#placing-the-caret)
- `selectionDebounceMs(int $ms)` - Coalescing window for `@selectionChange` events (150ms when unset; `0` or less
means the default, positive values floored at 16ms)
- `onChange(string $method)`, `onSubmit(string $method)`, `onSelectionChange(string $method)`