git.iko.soy/rowtype-yoga/purescript-yoga-react-native
on main help sign in
Auto-size popover from RN content, switch onPress to RCTDirectEventBlock
commit3cb74d0b9a7de1cc9b02e39da75ad1e8f7797e2f authorMark <mark.eibes@gmail.com> dateThu 19 Feb 2026, 10:13 (7 months ago) parent8344eed
2 files changed+46−27
example-macos/macos/YogaReactExample-macOS/RCTMacOSControlsManager.m+39−11 example-macos/src/Demo/Chat.purs+7−16
example-macos/macos/YogaReactExample-macOS/RCTMacOSControlsManager.m+39−11
@@ -1,6 +1,7 @@
11#import <React/RCTViewManager.h>
22#import <React/RCTBridgeModule.h>
33#import <React/RCTBridge.h>
4+
45#import <AppKit/AppKit.h>
56#import <WebKit/WebKit.h>
67#import <AVKit/AVKit.h>
@@ -22,7 +23,7 @@
2223
2324@interface RCTNativeButtonView : NSView
2425@property (nonatomic, strong) NSButton *button;
25−@property (nonatomic, copy) RCTBubblingEventBlock onPress;
26+@property (nonatomic, copy) RCTDirectEventBlock onPress;
2627@property (nonatomic, copy) NSString *title;
2728@property (nonatomic, copy) NSString *sfSymbol;
2829@property (nonatomic, copy) NSString *bezelStyle;
@@ -116,7 +117,7 @@ RCT_EXPORT_VIEW_PROPERTY(bezelStyle, NSString)
116117RCT_EXPORT_VIEW_PROPERTY(destructive, BOOL)
117118RCT_EXPORT_VIEW_PROPERTY(primary, BOOL)
118119RCT_EXPORT_VIEW_PROPERTY(buttonEnabled, BOOL)
119−RCT_EXPORT_VIEW_PROPERTY(onPress, RCTBubblingEventBlock)
120+RCT_EXPORT_VIEW_PROPERTY(onPress, RCTDirectEventBlock)
120121@end
121122
122123// ============================================================
@@ -2434,6 +2435,7 @@ RCT_EXPORT_VIEW_PROPERTY(radius, CGFloat)
24342435@property (nonatomic, strong) NSPopover *popover;
24352436@property (nonatomic, strong) NSViewController *popoverVC;
24362437@property (nonatomic, strong) NSView *contentContainer;
2438+
24372439@property (nonatomic, strong) NSMutableArray<NSView *> *reactChildren;
24382440@property (nonatomic, assign) BOOL visible;
24392441@property (nonatomic, copy) NSString *preferredEdge;
@@ -2458,6 +2460,7 @@ RCT_EXPORT_VIEW_PROPERTY(radius, CGFloat)
24582460 _popover.animates = YES;
24592461 _popover.delegate = self;
24602462 _preferredEdge = @"bottom";
2463+
24612464 }
24622465 return self;
24632466}
@@ -2466,19 +2469,15 @@ RCT_EXPORT_VIEW_PROPERTY(radius, CGFloat)
24662469 _visible = visible;
24672470 if (visible) {
24682471 if (!_popover.isShown && self.window) {
2469− CGFloat w = _popoverWidth > 0 ? _popoverWidth : 200;
2470− CGFloat h = _popoverHeight > 0 ? _popoverHeight : 40;
2472+ // Start with explicit size if provided, otherwise a generous default
2473+ CGFloat w = _popoverWidth > 0 ? _popoverWidth : 300;
2474+ CGFloat h = _popoverHeight > 0 ? _popoverHeight : 60;
24712475 _popover.contentSize = NSMakeSize(w, h);
2472− // Position RN child centered vertically in the unflipped container
2473− for (NSView *child in _contentContainer.subviews) {
2474− CGRect cf = child.frame;
2475− CGFloat y = (h - cf.size.height) / 2.0;
2476− if (y < 0) y = 0;
2477− child.frame = CGRectMake(cf.origin.x, y, cf.size.width, cf.size.height);
2478− }
24792476 NSRectEdge edge = [self edgeFromString:_preferredEdge];
24802477 NSView *anchor = _reactChildren.count > 0 ? _reactChildren[0] : self;
24812478 [_popover showRelativeToRect:anchor.bounds ofView:anchor preferredEdge:edge];
2479+ // After RN has laid out children, auto-size and center
2480+ [self performSelector:@selector(autoSizePopover) withObject:nil afterDelay:0.02];
24822481 }
24832482 } else {
24842483 if (_popover.isShown) [_popover close];
@@ -2537,6 +2536,35 @@ RCT_EXPORT_VIEW_PROPERTY(radius, CGFloat)
25372536 }
25382537}
25392538
2539+- (void)autoSizePopover {
2540+ // Measure the union of all RN content children
2541+ CGRect contentRect = CGRectZero;
2542+ for (NSView *child in _contentContainer.subviews) {
2543+ contentRect = CGRectUnion(contentRect, child.frame);
2544+ }
2545+ if (contentRect.size.width < 1 || contentRect.size.height < 1) return;
2546+
2547+ // Only auto-size if no explicit size was provided
2548+ CGFloat padding = 8.0;
2549+ if (_popoverWidth <= 0) {
2550+ CGFloat newW = contentRect.size.width + padding * 2;
2551+ _popover.contentSize = NSMakeSize(newW, _popover.contentSize.height);
2552+ }
2553+ if (_popoverHeight <= 0) {
2554+ CGFloat newH = contentRect.size.height + padding;
2555+ _popover.contentSize = NSMakeSize(_popover.contentSize.width, newH);
2556+ }
2557+
2558+ // Center children vertically in the (possibly resized) container
2559+ CGFloat containerH = _contentContainer.bounds.size.height;
2560+ for (NSView *child in _contentContainer.subviews) {
2561+ CGRect cf = child.frame;
2562+ CGFloat y = (containerH - cf.size.height) / 2.0;
2563+ if (y < 0) y = 0;
2564+ child.frame = CGRectMake(cf.origin.x + padding, y, cf.size.width, cf.size.height);
2565+ }
2566+}
2567+
25402568- (void)didUpdateReactSubviews {
25412569}
25422570
example-macos/src/Demo/Chat.purs+7−16
@@ -25,7 +25,6 @@ import Yoga.React.Native.MacOS.RichTextLabel (EmojiMap, nativeRichTextLabel, emo
2525import Yoga.React.Native.MacOS.Button (nativeButton)
2626import Yoga.React.Native.MacOS.HoverView (nativeHoverView)
2727import Yoga.React.Native.MacOS.Popover (nativePopover)
28−import Yoga.React.Native.Pressable (pressable)
2928import Yoga.React.Native.MacOS.PatternBackground (nativePatternBackground)
3029import Yoga.React.Native.MacOS.ScrollView (nativeScrollView)
3130import Yoga.React.Native.MacOS.Sidebar (sidebarLayout)
@@ -162,7 +161,6 @@ chatDemo = component "ChatDemo" \dp -> React.do
162161 scrollY /\ setScrollY <- useState' 0.0
163162 scrollTrigger /\ setScrollTrigger <- useState 0
164163 hoveredIdx /\ setHoveredIdx <- useState' (Nothing :: Maybe Int)
165− hoveredEmoji /\ setHoveredEmoji <- useState' (Nothing :: Maybe Int)
166164 let
167165 selectRoom rid = do
168166 setActiveRoom (Just rid)
@@ -251,8 +249,6 @@ chatDemo = component "ChatDemo" \dp -> React.do
251249 { visible: reactPopover == Just idx
252250 , preferredEdge: T.bottom
253251 , behavior: T.transient
254− , popoverWidth: 214.0
255− , popoverHeight: 40.0
256252 , onClose: setReactPopover Nothing
257253 , style: Style.style { opacity: if showSmiley idx then 1.0 else 0.0 }
258254 }
@@ -267,18 +263,13 @@ chatDemo = component "ChatDemo" \dp -> React.do
267263 }
268264 , view { style: tw "flex-row items-center" }
269265 ( mapWithIndex
270− ( \i emoji ->
271− let
272− hoverBg = if hoveredEmoji == Just i then (if dp.isDark then "#555555" else "#D8D8D8") else "transparent"
273− in
274− pressable
275− { onPress: handler_ (reactToMessage idx emoji)
276− , style: tw "items-center justify-center rounded-full mx-0.5"
277− <> Style.style { width: 32.0, height: 32.0, backgroundColor: hoverBg }
278− , onMouseEnter: handler_ (setHoveredEmoji (Just i))
279− , onMouseLeave: handler_ (setHoveredEmoji Nothing)
280− }
281− [ text { style: Style.style { fontSize: 20.0 } } emoji ]
266+ ( \_ emoji ->
267+ nativeButton
268+ { title: emoji
269+ , bezelStyle: T.inline
270+ , onPress: reactToMessage idx emoji
271+ , style: Style.style { height: 28.0, width: 28.0, marginLeft: 2.0, marginRight: 2.0 }
272+ }
282273 )
283274 reactionEmoji
284275 )