Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/components-pointer-focus.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@fluentui-react-native/components': patch
---

Use Framework Base's `pointerFocus` mode for Tab. FocusZone stories request
focus through focus targets so React Native macOS does not keep an unmounted
item as its focused input.
9 changes: 9 additions & 0 deletions .changeset/desktop-driver-atomic-click.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@fluentui-react-native/desktop-driver': patch
---

Make Windows physical clicks robust to another absolute pointing device, such as a remote-desktop or virtual-machine
viewer, that moves the cursor back after injection. Pointer placement now confirms the cursor reached the target and
retries before pressing, element clicks inject the press and release as one coordinate-carrying batch, and pointer
actions carry their coordinate on every press and release. Previously such a device could redirect the press to its
own cursor position, activating another window and leaving text inputs unfocused.
19 changes: 19 additions & 0 deletions .changeset/desktop-driver-paper-session.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
'@fluentui-react-native/desktop-driver': patch
---

Report macOS elements that do not publish `AXEnabled`, such as React Native
Paper views without an explicit disabled state, as enabled so they can be
clicked and queried. Diagnose a locked or switched-out macOS login session:
launch, attach, window discovery, and new physical input fail immediately with
an explicit session diagnosis, and `doctor --permissions` reports the session.

Record macOS window notifications and recent helper commands in a bounded
diagnostic timeline exposed through `furn/window-events`, publish
`window-closed` events, and describe transient windows by role, frame, and
WindowServer identity. Activate macOS targets through Accessibility instead of
waiting about a second for cooperative workspace activation, and name the
occluder of an intercepted click. Add `captureDesktopFailureEvidence`, which
captures window events first and then the current and other open windows.
Attached WebdriverIO sessions now use explicit wait defaults so `waitUntil`
polls until its timeout instead of stopping after the first poll.
6 changes: 6 additions & 0 deletions .changeset/desktop-driver-transient-subtree.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@fluentui-react-native/desktop-driver': patch
---

Retry a Windows accessibility subtree read when a descendant disappears mid-read, such as an owned popup island
closing, while its root remains available. Only an unavailable root reports a stale element.
8 changes: 8 additions & 0 deletions .changeset/framework-base-host-focus-record.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@fluentui-react-native/framework-base': patch
---

Clear React Native macOS's focused-input record when a focus target blurs or
detaches. A View focused through `requestFocus()` previously stayed recorded
after unmounting, so a later ScrollView touch dispatched `blur` to the removed
view and raised a Paper exception.
10 changes: 10 additions & 0 deletions .changeset/framework-base-pointer-focus.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@fluentui-react-native/framework-base': minor
---

Replace `useFocusablePressable`'s `focusOnPress` boolean with a `pointerFocus`
mode. `platform` (the default) keeps the existing behavior: Windows and Win32
focus a pressed target, and macOS follows AppKit by leaving keyboard focus in
place. `always` and `never` select an explicit behavior on every platform.
Export `PointerFocusMode`, `FocusablePressableOptions`, and
`requestsFocusOnPointerPress`.
5 changes: 5 additions & 0 deletions .changeset/input-pointer-focus-story.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@fluentui-react-native/components': patch
---

Cover native focus from a physical pointer click on Input.
9 changes: 9 additions & 0 deletions .changeset/native-core-fabric-focuszone-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@fluentui-react-native/native-core': patch
---

Fix macOS Fabric FocusZone inside popups. Opening a Callout whose content
starts with a FocusZone no longer aborts the app, and Escape and other
unhandled keys now continue past the zone, so they reach the popup and dismiss
it. Add Menu composition stories for the default and explicit `pointerFocus`
trigger behaviors.
23 changes: 23 additions & 0 deletions .changeset/native-core-modern-preview.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
'@fluentui-react-native/native-core': minor
---

Add platform-scoped React 19 Callout and FocusZone previews built as phased
components, with live targets, confirmed cancellable native commands, a shared
hook-free Callout host, Swift macOS focus/placement services, and a
cross-machine validation plan. Implement the new macOS Paper Callout commands
in Swift behind header-independent bridges. Preserve Win32 legacy
registrations without new managed transport.

Honor explicit Windows FocusZone destinations without entry-focus redirection
and cover live-target refusal, cancellation, and detached command handles in
native stories.

Fix the Windows Callout crash on popup creation caused by an unsupported weak
light-dismiss revoker, and release each mount's popup, window, and
subscriptions on unmount. Windows Callout now dismisses on outside pointer
input and Escape, honors legacy `dismissBehaviors`, returns keyboard input to
the owner after a focused presentation closes, and keeps the requested gap
when placement flips. Windows FocusZone requests inside owned popups are
accepted while the popup's owner is active, enabling flat Menu and Popover
composition.
7 changes: 7 additions & 0 deletions .changeset/storybook-desktop-failure-evidence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@fluentui-react-native/storybook-desktop': patch
---

Capture the Desktop Driver's shared failure evidence for Storybook `wdio`
callbacks, including the window-event timeline, a failure screenshot, and the
screenshot and source of other open windows.
4 changes: 2 additions & 2 deletions apps/storybook/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,8 @@ arbitrary locations.
build and registration, Metro launch, full indexed-story traversal, and ownership-safe cleanup. Use
`--mode stories-and-tests` to run the component-authored desktop-e2e plans after the complete traversal.
- Keep the Windows story-pattern overrides in `storybook.config.mts`; the
current Accordion and Callout Fabric stories still fail-fast the RNW 0.81
host during traversal.
current Accordion Fabric stories still fail-fast the RNW 0.81 host during
traversal. Native-core Callout and FocusZone stories run on Windows.
- WinAppDriver screenshots are not a reliable capture path for WinAppSDK Composition content. After selecting a story
through the Storybook control channel, use the agent host's desktop screenshot tool when visual evidence is required.
- Build logs, automation evidence, visual trees, screenshots, and session manifests belong under ignored
Expand Down
7 changes: 4 additions & 3 deletions apps/storybook/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,9 +181,10 @@ exact app process launched by the smoke lifecycle. After traversal, the Windows
Fabric lifecycle restarts only that owned app and rewrites its lease before
running authored tests against the warm Metro bundle. Logs are written
beneath `artifacts/windows/smoke-logs`.
The Accordion and Callout stories remain excluded from the Windows catalog
because the current RNW 0.81 Fabric host still fail-fasts while traversing
them. Win32 continues to exercise Callout through its Paper endpoint.
The Accordion stories remain excluded from the Windows catalog because the
current RNW 0.81 Fabric host still fail-fasts while traversing them. Native-core
Callout and FocusZone stories, legacy and modern, run on Windows; Win32 also
exercises the legacy Callout through its Paper endpoint.

Storybook's development bundle intentionally contains separate `pretty-format` and `react-is`
versions used by its internal tooling. They are excluded from the duplicate-module enforcement;
Expand Down
4 changes: 2 additions & 2 deletions apps/storybook/storybook.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ export default makeDesktopStorybookConfig({
'@fluentui-react-native/native-core',
{
platformSettings: {
windows: {
storyPatterns: [],
win32: {
storyPatterns: ['src/legacy/**/*.stories.?(ts|tsx)'],
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,10 @@ export const FocusManagement: Story = {
}
},
'releases pressed feedback after an intervening key without a stale activation': async (context) => {
if (context.platform === 'win32') {
context.skip('Quarantined until #4373 is fixed: Win32 does not forward onPressOut after Space with an intervening key.');
return;
}
const { requireDesktopFocus, focusByTab } = await import('../../common/desktopFocus.wdio.ts');
if (!requireDesktopFocus(context)) return;
const { browser, expect, platform } = context;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,8 @@ Tab. macOS activates Return/Space on keydown, while Windows/Win32 activate on
keyup. Releasing a key or cancelling a started press clears pressed feedback
without another activation, including after an intervening key or self-blur.
The macOS all-controls Tab lane requires Keyboard navigation enabled.

The intervening-key release case is temporarily skipped on Win32 pending
[#4373](https://github.com/microsoft/fluentui-react-native/issues/4373):
pressed feedback clears without a stale activation, but `onPressOut` is not
forwarded. Windows Fabric and macOS retain the full assertion.
10 changes: 10 additions & 0 deletions packages/agentic/components/src/components/input/input.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,16 @@ export const FocusManagement: Story = {
await expectNativeState(browser, 'focus-input-after', 'focused', true);
await expectNativeState(browser, 'focus-input-disabled', 'focused', false);
},
'takes native focus from a physical pointer click': async (context) => {
const { requireDesktopFocus, expectNativeState, expectWindowsPointerFocus } = await import('../../common/desktopFocus.wdio.ts');
if (!requireDesktopFocus(context)) return;
const { browser } = context;
await (await browser.$('~focus-input-after')).click();
await expectWindowsPointerFocus(context, 'focus-input-after');
await (await browser.$('~focus-input')).click();
await expectWindowsPointerFocus(context, 'focus-input');
await expectNativeState(browser, 'focus-input-after', 'focused', false);
},
},
};

Expand Down
2 changes: 1 addition & 1 deletion packages/agentic/components/src/components/tab/useTab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export function useTab_unstable(props: TabProps): TabState {
onPress?.(event);
},
},
{ focusOnPress: !tabList },
{ pointerFocus: tabList ? 'never' : 'platform' },
);

const { focusTarget } = focusBinding;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as React from 'react';
import { Platform, Pressable, StyleSheet, Text, View } from 'react-native';
import type { StyleProp, ViewStyle } from 'react-native';

import { useFocusablePressable, useFocusTarget } from '@fluentui-react-native/framework-base';
import type { WdioStory } from '@fluentui-react-native/storybook-desktop/testing';
import { FocusZone } from '@fluentui-react-native/native-core/legacy';
import type { FocusZoneDirection, FocusZoneProps, FocusZoneTabNavigation } from '@fluentui-react-native/native-core/legacy';
Expand Down Expand Up @@ -69,27 +70,29 @@ type FocusZoneStoryButtonProps = {
testID: string;
};

const FocusZoneStoryButton = ({ content, focused, nativeID, onFocus, style, testID }: FocusZoneStoryButtonProps) => {
const localRef = React.useRef<View>(null);
/** AppKit keeps keyboard focus where it was on click, but these examples navigate from the clicked item. */
const ClickFocusedStoryButton = ({ content, focused, nativeID, onFocus, style, testID }: FocusZoneStoryButtonProps) => {
const [pressableProps, , { focusTargetRef }] = useFocusablePressable({ onFocus: () => onFocus() }, { pointerFocus: 'always' });
return (
<Pressable
{...pressableProps}
accessibilityLabel={content}
accessibilityRole="button"
nativeID={nativeID}
ref={focusTargetRef}
style={({ pressed }) => [style, focused && styles.focused, pressed && styles.pressed]}
testID={testID}
>
<Text style={styles.itemText}>{content}</Text>
</Pressable>
);
};

const FocusZoneStoryButton = (props: FocusZoneStoryButtonProps) => {
if (Platform.OS === 'macos') {
return (
<Pressable
accessibilityLabel={content}
accessibilityRole="button"
focusable
nativeID={nativeID}
onFocus={onFocus}
onPress={() => localRef.current?.focus()}
ref={localRef}
style={({ pressed }) => [style, focused && styles.focused, pressed && styles.pressed]}
testID={testID}
>
<Text style={styles.itemText}>{content}</Text>
</Pressable>
);
return <ClickFocusedStoryButton {...props} />;
}

const { content, nativeID, onFocus, style, testID } = props;
return <Button accessibilityLabel={content} content={content} nativeID={nativeID} onFocus={onFocus} style={style} testID={testID} />;
};

Expand All @@ -101,6 +104,12 @@ type FocusZoneExampleProps = FocusZoneProps & {

const FocusZoneExample = ({ columns = 3, defaultToCenter = false, itemCount = 9, ...props }: FocusZoneExampleProps) => {
const focusZoneRef = React.useRef<View>(null);
// Request zone focus through a focus target so React Native's focused-input record is released when the zone unmounts.
const { focusTarget: zoneTarget, focusTargetRef: zoneTargetRef } = useFocusTarget();
React.useLayoutEffect(() => {
const cleanup = zoneTargetRef(focusZoneRef.current);
return typeof cleanup === 'function' ? cleanup : () => zoneTargetRef(null);
}, [zoneTargetRef]);
const [focusedItem, setFocusedItem] = React.useState('Outside before');
const items = Array.from({ length: itemCount }, (_, index) => index + 1);
const centerIndex = Math.floor(itemCount / 2);
Expand Down Expand Up @@ -152,7 +161,7 @@ const FocusZoneExample = ({ columns = 3, defaultToCenter = false, itemCount = 9,
style={styles.outsideButton}
testID="focus-zone-after"
/>
<Button content="Focus the zone" onPress={() => focusZoneRef.current?.focus()} style={styles.focusButton} />
<Button content="Focus the zone" onPress={() => zoneTarget.requestFocus()} style={styles.focusButton} />
<Text accessibilityLiveRegion="polite" style={styles.status} testID="focus-zone-status">
Focused: {focusedItem}
</Text>
Expand Down
8 changes: 8 additions & 0 deletions packages/agentic/desktop-driver/native/PROTOCOL.md
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,14 @@ events:
Queries remain authoritative. Events can invalidate state or wake waits but do
not replace a liveness or state query.

Helpers that advertise the `windowEvents` feature also accept a `windowEvents`
command with the target `lease` and return a bounded diagnostic log of recent
window notifications and helper commands, and publish a `window-closed` event
with `{ "windowId": "..." }` when the platform reports a tracked window
destroyed. Helpers without the feature reject the command as
`unsupported-operation`; Node never requests the log unless the feature is
negotiated.

### `cancel` and `cancelled`

When a command's `AbortSignal` fires, Node sends:
Expand Down
Loading
Loading