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
5 changes: 5 additions & 0 deletions .changeset/focus-zone-i18n-header.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@fluentui-react-native/native-core': patch
---

Import `RCTI18nUtil.h` in the macOS FocusZone implementation with the canonical `<React/RCTI18nUtil.h>` form instead of by bare, differently cased header name.
5 changes: 5 additions & 0 deletions .changeset/focus-zone-unused-params.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@fluentui-react-native/native-core': patch
---

Mark the unused block parameters in the macOS FocusZone traversal as `__unused` so the file compiles cleanly with `-Wunused-parameter` treated as an error.
6 changes: 6 additions & 0 deletions .changeset/optional-react-import.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@fluentui-react-native/experimental-avatar': patch
'@fluentui-react-native/experimental-native-date-picker': patch
---

Import `React` in iOS Swift sources only when the module is available, so hosts can provide React Native headers through an Objective-C bridging header.
6 changes: 6 additions & 0 deletions .changeset/qualified-react-headers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@fluentui-react-native/experimental-appearance-additions': patch
'@fluentui-react-native/experimental-native-date-picker': patch
---

Import React Native headers in iOS Objective-C sources with the canonical `<React/...>` form instead of by bare header name.
5 changes: 5 additions & 0 deletions .changeset/swift-callout-commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@fluentui-react-native/native-core': patch
---

Implement the macOS Paper Callout `focusWindow` and `blurWindow` commands in Swift so the Objective-C manager no longer imports the generated Swift header, which duplicates the manager's interface when hosts compile Swift with an Objective-C bridging header.
6 changes: 5 additions & 1 deletion packages/agentic/native-core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ to use its host-provided `RCTCallout` and `RCTFocusZone`.

The macOS module-named header at `macos/shared/FRNNativeCore.h` is required by
the generated `FRNNativeCore-Swift.h`; keep it aligned with the pod's
`module_name` when changing native ownership.
`module_name` when changing native ownership. Only Fabric adapters import the
generated header. Hosts that compile the Swift sources with an Objective-C
bridging header also get internal Swift classes declared there, duplicating
Paper `RCT_EXTERN_MODULE` interfaces, so implement Paper manager methods in
Swift and declare them with `RCT_EXTERN_METHOD`.

## Public entrypoints

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,19 @@ class CalloutManager: RCTViewManager {
override class func requiresMainQueueSetup() -> Bool {
return true
}

// React Native calls these on the module's method queue; views can only be looked up on the main queue.
@objc func focusWindow(_ viewTag: NSNumber) {
nonisolated(unsafe) let manager = self
DispatchQueue.main.async {
(manager.bridge?.uiManager?.view(forReactTag: viewTag) as? CalloutView)?.focusWindow()
}
}

@objc func blurWindow(_ viewTag: NSNumber) {
nonisolated(unsafe) let manager = self
DispatchQueue.main.async {
(manager.bridge?.uiManager?.view(forReactTag: viewTag) as? CalloutView)?.blurWindow()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#import "FRNCalloutManager.h"

#import "FRNNativeCore-Swift.h"

@implementation RCTConvert (FRNCalloutAdditions)

// RCTConvert does not properly convert a JS screenRect into a native CGRect/NSRect,
Expand Down Expand Up @@ -39,27 +37,9 @@ + (NSRect)screenRect:(id)json

@interface RCT_EXTERN_MODULE(RCTCalloutManager, RCTViewManager)

RCT_EXPORT_METHOD(focusWindow : (nonnull NSNumber *)viewTag)
{
dispatch_async(dispatch_get_main_queue(), ^{
NSView *view = [self.bridge.uiManager viewForReactTag:viewTag];

if ([view isKindOfClass:[FRNCalloutView class]]) {
[(FRNCalloutView *)view focusWindow];
}
});
}
RCT_EXTERN_METHOD(focusWindow : (nonnull NSNumber *)viewTag)

RCT_EXPORT_METHOD(blurWindow : (nonnull NSNumber *)viewTag)
{
dispatch_async(dispatch_get_main_queue(), ^{
NSView *view = [self.bridge.uiManager viewForReactTag:viewTag];

if ([view isKindOfClass:[FRNCalloutView class]]) {
[(FRNCalloutView *)view blurWindow];
}
});
}
RCT_EXTERN_METHOD(blurWindow : (nonnull NSNumber *)viewTag)

RCT_EXPORT_VIEW_PROPERTY(target, NSNumber)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#import "KeyCodes.h"
#import <React/RCTBaseTextInputView.h>
#import "RCTFocusZone.h"
#import "RCTi18nUtil.h"
#import <React/RCTI18nUtil.h>

typedef enum {
FocusZoneActionNone,
Expand Down Expand Up @@ -560,7 +560,7 @@ - (NSView *)nextViewToFocusInHierarchyForAction:(FocusZoneAction)action
AppendFocusableViewsWithin(self, focusableViews);

NSView *firstResponder = GetFirstResponder([self window]);
NSUInteger currentIndex = [focusableViews indexOfObjectPassingTest:^BOOL(NSView *candidateView, NSUInteger index, BOOL *stop) {
NSUInteger currentIndex = [focusableViews indexOfObjectPassingTest:^BOOL(NSView *candidateView, __unused NSUInteger index, __unused BOOL *stop) {
return candidateView == firstResponder
|| [candidateView isDescendantOf:firstResponder]
|| [firstResponder isDescendantOf:candidateView];
Expand Down
16 changes: 12 additions & 4 deletions packages/agentic/native-core/tests/native-ownership.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,18 @@ test('macOS Swift interoperability uses the consolidated pod module name', async
assert.equal(moduleName, 'FRNNativeCore');
await readFile(new URL(`macos/shared/${moduleName}.h`, packageRoot), 'utf8');

for (const adapter of ['fabric/RCTCalloutComponentView.mm', 'paper/FRNCalloutManager.m']) {
const source = await readFile(new URL(`macos/components/Callout/${adapter}`, packageRoot), 'utf8');
assert.ok(source.includes(`#import "${moduleName}-Swift.h"`));
assert.doesNotMatch(source, /FRNCallout-Swift\.h/);
const fabric = await readFile(new URL('macos/components/Callout/fabric/RCTCalloutComponentView.mm', packageRoot), 'utf8');
assert.ok(fabric.includes(`#import "${moduleName}-Swift.h"`));
assert.doesNotMatch(fabric, /FRNCallout-Swift\.h/);

// Bridging-header builds also declare internal Swift classes in the generated header, duplicating the Paper
// manager's RCT_EXTERN_MODULE interface, so Paper commands are implemented in Swift instead.
const paper = await readFile(new URL('macos/components/Callout/paper/FRNCalloutManager.m', packageRoot), 'utf8');
const manager = await readFile(new URL('macos/components/Callout/paper/CalloutManager.swift', packageRoot), 'utf8');
assert.doesNotMatch(paper, /-Swift\.h/);
for (const command of ['focusWindow', 'blurWindow']) {
assert.match(paper, new RegExp(`RCT_EXTERN_METHOD\\(${command} : \\(nonnull NSNumber \\*\\)viewTag\\)`));
assert.match(manager, new RegExp(`@objc func ${command}\\(_ viewTag: NSNumber\\)`));
}
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#import "FRNAppearanceAdditions.h"
#import "RCTUIManager.h"
#import <React/RCTUIManager.h>

#import <React/RCTBridgeModule.h>
#import <React/RCTConstants.h>
Expand Down
2 changes: 2 additions & 0 deletions packages/experimental/Avatar/ios/AvatarViewManager.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import Foundation
import FluentUI
#if canImport(React)
import React
#endif
import UIKit

@objc(FRNAvatarViewManager)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import FluentUI
import Foundation
#if canImport(React)
import React
#endif

class DatePickerDelegate: DateTimePickerDelegate {
weak var manager: DatePickerManager?
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import FluentUI
import Foundation
#if canImport(React)
import React
#endif

@objc(FRNDatePickerManager)
public class DatePickerManager: NSObject {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#import "RCTBridgeModule.h"
#import <React/RCTBridgeModule.h>
#import <React/RCTLog.h>
#import <React/RCTComponent.h>
#import <React/RCTViewManager.h>
Expand Down
Loading