@@ -132,6 +132,13 @@ const NOT_ENABLED_TRAIT = 1n << 8n;
132132 */
133133const SELECTED_TRAIT = 1n << 3n ;
134134
135+ /**
136+ * The private is-editing trait UIKit sets on the text input that holds keyboard focus. XCTest
137+ * derives `hasKeyboardFocus` from this word, and the runner path answers `focused: true` from that
138+ * key, so the bridge derives the fact from this bit. Like `selected`, it is published only when set.
139+ */
140+ const KEYBOARD_FOCUS_TRAIT = 1n << 21n ;
141+
135142/**
136143 * A WebKit page — Safari's, or a `WKWebView`'s — lives in a WebContent process and reaches UIKit's
137144 * tree as an `AXRemoteElement` under the web view, with its children in that other process. The
@@ -247,6 +254,7 @@ function nodeFacts(
247254 // Publishes `selected: true` only when the selected bit is set and omits it otherwise — the same
248255 // shape the XCTest tree produces, so a `selected:` selector cannot tell the producers apart.
249256 const selected = traits === undefined || ( traits & SELECTED_TRAIT ) === 0n ? undefined : true ;
257+ const focused = traits === undefined || ( traits & KEYBOARD_FOCUS_TRAIT ) === 0n ? undefined : true ;
250258 const userInteractionEnabled = optionalBoolean ( value [ ATTRIBUTE . userInteractionEnabled ] ) ;
251259 // Trimmed like the runner's `placeholderText`: a whitespace placeholder is no placeholder.
252260 const placeholder = optionalString ( value [ ATTRIBUTE . placeholder ] ) ?. trim ( ) ;
@@ -271,6 +279,7 @@ function nodeFacts(
271279 ...( frame ? { rect : frame } : { } ) ,
272280 ...( enabled === undefined ? { } : { enabled } ) ,
273281 ...( selected === undefined ? { } : { selected } ) ,
282+ ...( focused === undefined ? { } : { focused } ) ,
274283 ...( userInteractionEnabled === undefined ? { } : { userInteractionEnabled } ) ,
275284 depth,
276285 } ;
@@ -391,7 +400,7 @@ function optionalScalar(value: unknown): string | undefined {
391400
392401/**
393402 * The guest sends the uint64 traits word as a decimal string so no bit is lost to a double. One
394- * parse feeds every trait fact the tree publishes — `enabled` and `selected ` — so a malformed word
403+ * parse feeds every trait fact the tree publishes — `enabled`, `selected`, and `focused ` — so a malformed word
395404 * fails the same way no matter which fact is read.
396405 */
397406function traitsFromGuest ( value : unknown ) : bigint | undefined {
0 commit comments