Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 13 additions & 0 deletions .changeset/dropdown-component.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@fluentui-react-native/components': minor
---

Add the reviewed Dropdown contract and its macOS (Paper and Fabric), Windows Fabric, and Office Win32 implementation.
Dropdown draws Input's outline or underline chrome on a combobox trigger and presents ListboxItem options in a native
list popup for single or multiple selection, with controlled or uncontrolled selection and presence, a placeholder, and
a count summary when selected labels do not fit. On macOS the list requests a vertical scroller only while its options
overflow, so Paper's legacy scrollers do not inset the options. Dropdown is exported from the package root.

ListboxItem gains a `value` prop. Inside a Dropdown list, an item with a value becomes a bound option that the list
selects, positions, and focuses, and that exposes select, add-to-selection, and remove-from-selection actions. Outside
a list, or without a value, ListboxItem is unchanged.
10 changes: 10 additions & 0 deletions .changeset/macos-popup-qualification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@fluentui-react-native/native-core': patch
---

Keep Paper Callout view creation on the main actor under strict Swift concurrency,
including Office-style builds without a React module. Add a reproducible Apple
integration probe and native popup container-focus, geometry, and expanded-state
regressions for the macOS prerequisites. Callout placement regressions first move
the owner window to the top of its screen, so a small display cannot flip the popup
to the opposite edge.
14 changes: 14 additions & 0 deletions .changeset/menu-component-set.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
'@fluentui-react-native/components': minor
'@fluentui-react-native/native-core': minor
---

Add the reviewed Menu and MenuButton contracts and their macOS implementation: command rows with wrapping arrows,
Home/End, type-ahead, two submenu levels that open on arrows, press, or hover, activation dismissal, and focus return.
MenuButton adds Button chrome with a down chevron, Up/Down entry, and Tab past the trigger. Both are exported from the
package root. MenuItem gains a `submenu` prop and a Menu context that supplies
registration, type-ahead labels, and the platform's disabled-focusable default.

The experimental modern Callout accepts `tabNavigation: 'cycle' | 'dismiss' | 'continue'` and reports `tab-forward` and
`tab-backward` dismissals. On macOS, Tab in a popup family closes its outermost Tab-exiting presentation and can continue
focus past the anchor. Legacy Callout behavior and APIs are unchanged.
13 changes: 13 additions & 0 deletions .changeset/popover-component.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@fluentui-react-native/components': minor
'@fluentui-react-native/native-core': minor
---

Add the reviewed Popover contract and its macOS and Windows implementation: a Button trigger, a token-styled
surface in a native popup, controlled and uncontrolled presence with native dismissal reasons, entry focus, and
focus return to the trigger. Popover is exported from the package root.

On macOS, the experimental modern Callout now presents an unstyled transparent popup window with a native shadow
that follows its content. Pointer input on a live view anchor no longer light-dismisses it, Tab moves within the
popup, and Paper orders modern commands after previously committed view updates. Legacy Callout behavior and APIs
are unchanged.
17 changes: 17 additions & 0 deletions .changeset/popup-focus-prerequisites.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'@fluentui-react-native/components': minor
'@fluentui-react-native/framework-base': minor
'@fluentui-react-native/native-core': minor
---

Add opt-in disabled-focusable pressable targets and MenuItem rows that retain
disabled semantics without enabling activation. Give Input its variant-specific
focus-visible treatment: a shared outline ring or an additive underline
indicator, without a competing native editor ring.
Keep keyboard release cleanup exactly once when disabling a pressed target.

Extend the experimental modern Callout focus operation with native first/last
strategies, report observed dismissal causes without guessing unclassified
notifications, and ignore duplicate close or late ready events for a dismissed
presentation. Rebuild native hosts for the changed modern command schema.
Legacy APIs and prebuilt Win32 commands are unchanged.
7 changes: 7 additions & 0 deletions .changeset/popup-shared-controllers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@fluentui-react-native/components': patch
---

Share one native popup host, presence latch, and list-popup trigger controller across Popover, Menu, MenuButton, and
Dropdown, along with the popup layout styles and ref and handler composition helpers. Input and Dropdown share Input's
visual-state resolution. Behavior is unchanged.
17 changes: 17 additions & 0 deletions .changeset/win32-popover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
'@fluentui-react-native/components': patch
'@fluentui-react-native/native-core': minor
'@fluentui-react-native/desktop-driver': patch
---

Present Popover on Office Win32 through a new `Callout` adapter in native-core's `win32` entrypoint. The adapter
implements the modern props and presentation handle over the host's existing Callout registration and sends it no
new commands. It supports view anchors only, and its popup sits flush against the anchor. The host draws the surface
fill and corner radius, because its popup window has no per-pixel transparency. On Win32 the Popover trigger also
declares `Expand` and `Collapse` accessibility actions, so UI Automation exposes its expanded state.

On Windows, the modern Callout popup window now has rounded corners and the platform popup shadow. A press on the
live view anchor no longer light-dismisses it before the anchor handles that press.

The Windows desktop driver no longer reactivates a window whose owned popup is in the foreground. It also retries a
subtree walk when a closing popup's UI Automation provider reports a transient failure.
19 changes: 19 additions & 0 deletions .changeset/windows-win32-menu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
---
'@fluentui-react-native/components': patch
'@fluentui-react-native/native-core': minor
'@fluentui-react-native/desktop-driver': patch
---

Present Menu and MenuButton on Windows and Office Win32. On Windows, the modern Callout now implements
`tabNavigation`: Tab or Shift+Tab in any popup of a nested family closes the whole family and either keeps focus at
the owner (`dismiss`) or moves it past the anchor (`continue`). Closing an outer Callout also closes the Callouts
nested in it, and focus requests treat a nested popup's owner window as active.

Native-core's `win32` entrypoint adds a `FocusZone` adapter over the host's FocusZone registration. Its `Callout`
adapter adds `initialFocus`, keeps the popup's focus and key events from bubbling to its owner, and closes the
outermost popup on Tab. On Win32, Menu handles Home and End itself, and MenuButton returns Tab to its trigger.
Because the Win32 host reports a pointer leave together with its enter, a pending submenu change there survives the
leave until another row is hovered.

The Windows desktop driver injects a character that one unmodified key of the foreground keyboard layout types as
that virtual key, so key-based handlers such as type-ahead match it.
4 changes: 3 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@
*.bat text eol=crlf
*.cmd text eol=crlf
*.ps1 text eol=crlf
*.pbxproj -text
*.pbxproj -text
# Unified diffs contain significant single-space context lines.
.yarn/patches/*.patch whitespace=-blank-at-eol
144 changes: 144 additions & 0 deletions .yarn/patches/react-native-macos-npm-0.81.9-aa583a0648.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
diff --git a/Libraries/Text/TextInput/Singleline/RCTUITextField.mm b/Libraries/Text/TextInput/Singleline/RCTUITextField.mm
index 137d0ee9f51616ea27c397a8bf595fe2c0ff5e91..c4afef20fcdffb743f976df3b26a353f0bc69b3a 100644
--- a/Libraries/Text/TextInput/Singleline/RCTUITextField.mm
+++ b/Libraries/Text/TextInput/Singleline/RCTUITextField.mm
@@ -391,18 +391,36 @@

- (BOOL)isEditable
{
+#if TARGET_OS_OSX // [macOS
+ return [super isEditable];
+#else
return self.isEnabled;
+#endif
}

- (void)setEditable:(BOOL)editable
{
#if TARGET_OS_OSX // [macOS
- // on macos the super must be called otherwise its NSTextFieldCell editable property doesn't get set.
[super setEditable:editable];
-#endif // macOS]
+ self.selectable = YES;
+#else
self.enabled = editable;
+#endif // macOS]
+}
+
+#if TARGET_OS_OSX // [macOS
+- (BOOL)acceptsFirstResponder
+{
+ return self.isEnabled && self.isSelectable;
}

+- (void)setAccessibilityState:(NSDictionary<NSString *, id> *)accessibilityState
+{
+ [super setAccessibilityState:accessibilityState];
+ self.enabled = ![accessibilityState[@"disabled"] boolValue];
+}
+#endif // macOS]
+
#if !TARGET_OS_OSX // [macOS]
- (void)setSecureTextEntry:(BOOL)secureTextEntry
{
diff --git a/React/Base/RCTConvert.mm b/React/Base/RCTConvert.mm
index 09822246f92767208a7d2ad561d4b2954c0fef33..6e5fce575e507ed0c3e3f6794115dd767c62c2d5 100644
--- a/React/Base/RCTConvert.mm
+++ b/React/Base/RCTConvert.mm
@@ -1664,6 +1664,7 @@ RCT_ENUM_CONVERTER(
@"tab": NSAccessibilityButtonRole, // no exact match on macOS
@"tablist": NSAccessibilityTabGroupRole,
@"text": NSAccessibilityStaticTextRole, // also a legacy iOS accessibilityTraits
+ @"textbox": NSAccessibilityTextFieldRole,
@"timer": NSAccessibilityStaticTextRole, // no exact match on macOS
@"toolbar": NSAccessibilityToolbarRole,
// Roles/traits that are macOS specific and are used by some of the core components (Lists):
diff --git a/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm b/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm
index e7254907271b70783e886d378c7824df46e04e93..fbe1d5d7e834afb4553adb03253e34f6b9ce69d7 100644
--- a/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm
+++ b/React/Fabric/Mounting/ComponentViews/TextInput/RCTTextInputComponentView.mm
@@ -255,6 +255,14 @@ static NSSet<NSNumber *> *returnKeyTypesSet;

#pragma mark - RCTViewComponentView overrides

+#if TARGET_OS_OSX // [macOS
+- (BOOL)canBecomeKeyView
+{
+ // The backed editor, not its structural Fabric wrapper, owns the Tab stop.
+ return NO;
+}
+#endif // macOS]
+
- (NSObject *)accessibilityElement
{
return _backedTextInputView;
@@ -305,6 +313,13 @@ static NSSet<NSNumber *> *returnKeyTypesSet;
_backedTextInputView.editable = newTextInputProps.traits.editable;
}

+#if TARGET_OS_OSX // [macOS
+ if ([_backedTextInputView isKindOfClass:[RCTUITextField class]]) {
+ const auto state = newTextInputProps.accessibilityState.value_or(AccessibilityState{});
+ ((RCTUITextField *)_backedTextInputView).enabled = !state.disabled;
+ }
+#endif // macOS]
+
#if !TARGET_OS_OSX // [macOS]
if (newTextInputProps.multiline &&
newTextInputProps.traits.dataDetectorTypes != oldTextInputProps.traits.dataDetectorTypes) {
diff --git a/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm
index 6e20d03f64385e3729a7517c6fb8b31f6177d52d..6710864c9e9252556f7fad00caf1bbea2e80192b 100644
--- a/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm
+++ b/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm
@@ -35,6 +35,7 @@
#endif

#if TARGET_OS_OSX // [macOS
+#import <React/RCTConvert.h>
#import <React/RCTCursor.h>
#import <React/RCTViewKeyboardEvent.h>
#import <React/RCTUtils.h>
@@ -489,14 +490,39 @@ const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f;
self.accessibilityElement.accessibilityTraits =
RCTUIAccessibilityTraitsFromAccessibilityTraits(newViewProps.accessibilityTraits);
#else // [macOS
- // On macOS, accessibilityElement returns self (NSView*) which doesn't have accessibilityTraits.
- // Set role directly on self based on traits.
RCTUIAccessibilityTraits traits = RCTUIAccessibilityTraitsFromAccessibilityTraits(newViewProps.accessibilityTraits);
- self.accessibilityRole = RCTAccessibilityRoleFromTraits(traits);
- self.accessibilityEnabled = (traits & RCTUIAccessibilityTraitNotEnabled) == 0;
+ self.accessibilityElement.accessibilityRole = RCTAccessibilityRoleFromTraits(traits);
#endif // macOS]
}

+#if TARGET_OS_OSX // [macOS
+ if (oldViewProps.role != newViewProps.role ||
+ oldViewProps.accessibilityRole != newViewProps.accessibilityRole ||
+ oldViewProps.accessibilityTraits != newViewProps.accessibilityTraits) {
+ if (newViewProps.role != Role::None || !newViewProps.accessibilityRole.empty()) {
+ const bool useAriaMappings = newViewProps.role != Role::None;
+ NSString *role = RCTNSStringFromString(
+ useAriaMappings ? toString(newViewProps.role) : newViewProps.accessibilityRole);
+ self.accessibilityElement.accessibilityRole =
+ [RCTConvert accessibilityRoleFromTraits:role useAriaMappings:useAriaMappings];
+ } else {
+ self.accessibilityElement.accessibilityRole = RCTAccessibilityRoleFromTraits(
+ RCTUIAccessibilityTraitsFromAccessibilityTraits(newViewProps.accessibilityTraits));
+ }
+ }
+
+ if (oldViewProps.accessibilityState != newViewProps.accessibilityState ||
+ oldViewProps.accessibilityTraits != newViewProps.accessibilityTraits) {
+ const auto state = newViewProps.accessibilityState.value_or(AccessibilityState{});
+ const auto traits = RCTUIAccessibilityTraitsFromAccessibilityTraits(newViewProps.accessibilityTraits);
+ self.accessibilityElement.accessibilityEnabled =
+ !state.disabled && (traits & RCTUIAccessibilityTraitNotEnabled) == 0;
+ self.accessibilityElement.accessibilitySelected =
+ state.selected || (traits & RCTUIAccessibilityTraitSelected) != 0;
+ self.accessibilityElement.accessibilityExpanded = state.expanded.value_or(false);
+ }
+#endif // macOS]
+
#if !TARGET_OS_OSX // [macOS]
// `accessibilityState`
if (oldViewProps.accessibilityState != newViewProps.accessibilityState) {
3 changes: 2 additions & 1 deletion apps/storybook/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ arbitrary locations.
Win32 chrome would regress addon controls and responsive behavior while
increasing local maintenance.
- Keep the Win32 story-pattern override in `storybook.config.mts`. It intentionally excludes ListItem and Accordion
because their Paper implementations fail-fast crash REX 0.81.1.
because their Paper implementations fail-fast crash REX 0.81.1. Remove those exclusions when they no longer crash;
all desktop endpoints must ship together.
- Keep the Win32 window title distinct from the Windows Fabric title so
automation never attaches to the wrong endpoint.
- Use `yarn storybook smoke --win32 --mode stories` for the package-owned bundle, launch, native desktop-chrome checks,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"morgan@npm:1.11.0": "1.12.1",
"path-to-regexp@npm:8.3.0": "^8.4.2",
"proxy-addr@npm:^2.0.7": "2.0.8",
"react-native-macos": "patch:react-native-macos@npm%3A0.81.9#~/.yarn/patches/react-native-macos-npm-0.81.9-aa583a0648.patch",
"react-native-windows": "0.81.35",
"sharp@npm:0.35.3": "0.35.5",
"shell-quote": "1.11.0",
Expand Down
7 changes: 1 addition & 6 deletions packages/agentic/components/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,7 @@
}
},
"furn": {
"jestPlatform": "macos",
"knip": {
"ignoreDependencies": [
"@fluentui-react-native/native-core"
]
}
"jestPlatform": "macos"
},
"rnx-kit": {
"kitType": "library",
Expand Down
Loading
Loading