1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//! Text cursor and selection synthesis for accessible text inputs.
//!
//! Provides:
//! - [`TextPosition`] — a byte-offset cursor position in a UTF-8 string.
//! - [`TextSelection`] — a selection range (anchor + active) or collapsed cursor.
//! - [`build_text_input_a11y`] — synthesise an [`crate::tree::A11yNode`] for a
//! text input field, pre-populated with content and a selection description.
//! - [`update_text_cursor`] — update an existing text input node with a new
//! cursor/selection position.
//!
//! # Name collision note
//!
//! `crate::props` already exports a `TextSelection { anchor: usize, focus: usize }`
//! for internal byte-offset bookkeeping used by [`crate::tree::synthesize_text_run_children`].
//! The types in *this* module use an explicit [`TextPosition`] newtype wrapper
//! and the field name `active` (rather than `focus`) to follow the AT-SPI /
//! ARIA convention and to avoid confusion with the lower-level props type.
//! Import explicitly via `oxiui_accessibility::text_a11y::*` — do **not**
//! glob-import at the crate root alongside `props::TextSelection`.
use NodeId;
use crate;
// ── TextPosition ──────────────────────────────────────────────────────────────
/// A byte offset into the UTF-8 content of a text field.
///
/// Offsets are 0-based indices into the raw bytes of the field's content
/// string. Callers are responsible for providing offsets that land on valid
/// UTF-8 character boundaries.
;
// ── TextSelection ─────────────────────────────────────────────────────────────
/// A text selection range, expressed as a pair of [`TextPosition`] byte offsets.
///
/// When `anchor == active` the selection is *collapsed* (a bare cursor with no
/// selected text). When they differ, the selection spans `start()..end()`.
///
/// The `anchor` is the fixed end (where the selection *started*) and `active`
/// is the moving end (where the cursor currently is). Either end may be
/// numerically larger.
// ── Internal helper ───────────────────────────────────────────────────────────
/// Build a human-readable description of `selection` for screen readers.
///
/// Returns strings of the form `"cursor at position 5"` or
/// `"selected text: bytes 3..8"`.
// ── Public synthesis functions ────────────────────────────────────────────────
/// Synthesise an [`A11yNode`] for a text input field with cursor/selection.
///
/// The returned node has:
/// - Role [`WidgetRole::TextInput`] when `is_editable` is `true`, otherwise
/// [`WidgetRole::Label`] (read-only text).
/// - `text_content` set to `content`.
/// - `props.description` set to a human-readable selection description
/// (e.g. `"cursor at position 5"` or `"selected text: bytes 3..8"`).
///
/// The node is minted with `NodeId(0)`. Callers that embed this node in a
/// larger id space must renumber it (the same convention used by
/// [`crate::tree::build_table_a11y`]).
/// Update the cursor/selection description on an existing text input node.
///
/// Reads `node.text_content` for the content (no change) and overwrites
/// `node.props.description` with a fresh description derived from `selection`.